From: jk7744.park Date: Tue, 8 Sep 2015 13:27:49 +0000 (+0900) Subject: tizen 2.3.1 release X-Git-Tag: submit/tizen_2.3.1/20150915.081238^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=10d8c7c2b57632a0708f30fc5e417cccbb8f2a66;hp=e69270ac86b363e8806a1f81b56ec2593a972b44;p=framework%2Fmultimedia%2Fgst-plugins-ext0.10.git tizen 2.3.1 release --- diff --git a/Makefile.am b/Makefile.am index 3115751..e75ed8c 100755 --- a/Makefile.am +++ b/Makefile.am @@ -5,27 +5,37 @@ aclocaldir = $(datadir)/aclocal SUBDIRS = common -if GST_EXT_USE_EXT_ENCODEBIN -SUBDIRS += encodebin +if GST_EXT_USE_EXT_PIFFDEMUX +SUBDIRS += piffdemux endif -if GST_EXT_USE_EXT_AVSYSTEM -SUBDIRS += avsystem +if GST_EXT_USE_EXT_ENCODEBIN +SUBDIRS += encodebin endif if GST_EXT_USE_EXT_EVASIMAGESINK SUBDIRS += evasimagesink endif +if GST_EXT_USE_EXT_EVASPIXMAPSINK +SUBDIRS += evaspixmapsink +endif + +if GST_EXT_USE_EXT_XVIMAGESRC +SUBDIRS += xvimagesrc +endif + if GST_EXT_USE_EXT_DRMSRC SUBDIRS += drmsrc endif - +if GST_EXT_USE_EXT_SUBMUX +SUBDIRS += submux +endif if GST_EXT_USE_EXT_TOGGLE SUBDIRS += toggle endif -if GST_EXT_USE_EXT_AVSYSTEM +if GST_EXT_USE_EXT_PD_PUSHSRC SUBDIRS += pdpushsrc endif @@ -33,39 +43,61 @@ if GST_EXT_USE_EXT_AUDIOTP SUBDIRS += audiotp endif +if GST_EXT_USE_EXT_AUDIOEQ +SUBDIRS += audioeq +endif + if GST_EXT_USE_EXT_SSDEMUX -#SUBDIRS += ssdemux +SUBDIRS += ssdemux endif +if GST_EXT_USE_EXT_DASHDEMUX +SUBDIRS += dashdemux +endif +if GST_EXT_USE_EXT_HLSDEMUX2 +SUBDIRS += hlsdemux2 +endif DIST_SUBDIRS = common -if GST_EXT_USE_EXT_ENCODEBIN -DIST_SUBDIRS += encodebin +if GST_EXT_USE_EXT_PIFFDEMUX +DIST_SUBDIRS += piffdemux endif -if GST_EXT_USE_EXT_AVSYSTEM -DIST_SUBDIRS += avsystem +if GST_EXT_USE_EXT_ENCODEBIN +DIST_SUBDIRS += encodebin endif if GST_EXT_USE_EXT_EVASIMAGESINK DIST_SUBDIRS += evasimagesink endif +if GST_EXT_USE_EXT_XVIMAGESRC +DIST_SUBDIRS += xvimagesrc +endif + if GST_EXT_USE_EXT_DRMSRC DIST_SUBDIRS += drmsrc endif - +if GST_EXT_USE_EXT_SUBMUX +DIST_SUBDIRS += submux +endif if GST_EXT_USE_EXT_TOGGLE DIST_SUBDIRS += toggle endif if GST_EXT_USE_EXT_SSDEMUX -#DIST_SUBDIRS += ssdemux +DIST_SUBDIRS += ssdemux endif +if GST_EXT_USE_EXT_DASHDEMUX +DIST_SUBDIRS += dashdemux +endif +if GST_EXT_USE_EXT_HLSDEMUX2 +DIST_SUBDIRS += hlsdemux2 +endif EXTRA_DIST = \ gstreamer.spec gstreamer.spec.in \ diff --git a/audioeq/Makefile.am b/audioeq/Makefile.am new file mode 100755 index 0000000..308a09c --- /dev/null +++ b/audioeq/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/audioeq/src/Makefile.am b/audioeq/src/Makefile.am new file mode 100755 index 0000000..6981e78 --- /dev/null +++ b/audioeq/src/Makefile.am @@ -0,0 +1,28 @@ +# plugindir is set in configure + +############################################################################## +# change libgstplugin.la to something more suitable, e.g. libmysomething.la # +############################################################################## +plugin_LTLIBRARIES = libgstaudioeq.la + +############################################################################## +# for the next set of variables, rename the prefix if you renamed the .la, # +# e.g. libgstplugin_la_SOURCES => libmysomething_la_SOURCES # +# libgstplugin_la_CFLAGS => libmysomething_la_CFLAGS # +# libgstplugin_la_LIBADD => libmysomething_la_LIBADD # +# libgstplugin_la_LDFLAGS => libmysomething_la_LDFLAGS # +############################################################################## + +# sources used to compile this plug-in +libgstaudioeq_la_SOURCES = gstaudioeq.c + +# flags used to compile this plugin +# add other _CFLAGS and _LIBS as needed +libgstaudioeq_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CONTROLLER_CFLAGS) +libgstaudioeq_la_LIBADD = $(GST_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) $(GST_BASE_LIBS) $(GST_CONTROLLER_LIBS) $(GST_LIBS) $(LIBM) -lgstbase-0.10 -lglib-2.0 -lgstcontroller-0.10 +libgstaudioeq_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +libgstequalizer_la_LIBTOOLFLAGS = --tag=disable-static + +# headers we need but don't want installed +noinst_HEADERS = gstaudioeq.h diff --git a/audioeq/src/gstaudioeq.c b/audioeq/src/gstaudioeq.c new file mode 100755 index 0000000..3c36e4a --- /dev/null +++ b/audioeq/src/gstaudioeq.c @@ -0,0 +1,1313 @@ +/* + * audioeq + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Aditi Narula + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstaudioeq.h" + +GST_DEBUG_CATEGORY_STATIC (gst_audioeq_debug); +#define GST_CAT_DEFAULT gst_audioeq_debug + +#define AUDIOEQ_ENABLE_DUMP +#define AUDIOEQ_REDUCE_MEMCPY + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_FILTER_ACTION, + PROP_CUSTOM_EQ, + PROP_CUSTOM_EQ_NUM, + PROP_CUSTOM_EQ_FREQ, + PROP_CUSTOM_EQ_WIDTH, +}; + +enum FilterActionType +{ + FILTER_NONE, + FILTER_PRESET, + FILTER_ADVANCED_SETTING +}; + +enum SampleRate +{ + SAMPLERATE_48000Hz, + SAMPLERATE_44100Hz, + SAMPLERATE_32000Hz, + SAMPLERATE_24000Hz, + SAMPLERATE_22050Hz, + SAMPLERATE_16000Hz, + SAMPLERATE_12000Hz, + SAMPLERATE_11025Hz, + SAMPLERATE_8000Hz, + + SAMPLERATE_NUM +}; + +#define DEFAULT_SAMPLE_SIZE 2 +#define DEFAULT_VOLUME 15 +#define DEFAULT_GAIN 1 +#define DEFAULT_SAMPLE_RATE SAMPLERATE_44100Hz +#define DEAFULT_CHANNELS 2 +#define DEFAULT_FILTER_ACTION FILTER_NONE +#define DEFAULT_CUSTOM_EQ_NUM 7 + +static GstStaticPadTemplate sinktemplate = + GST_STATIC_PAD_TEMPLATE( + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "audio/x-raw-int, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16, " + "channels = (int) [1,2]" + ) + ); + +static GstStaticPadTemplate srctemplate = + GST_STATIC_PAD_TEMPLATE( + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "audio/x-raw-int, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (boolean) true, " + "width = (int) 16, " + "depth = (int) 16, " + "channels = (int) [1,2]" + ) + ); + +static void gst_iir_equalizer_child_proxy_interface_init (gpointer g_iface, + gpointer iface_data); + +static void gst_iir_equalizer_finalize (GObject * object); + +static gboolean gst_iir_equalizer_setup (GstAudioFilter * filter, + GstRingBufferSpec * fmt); + +static void +_do_init (GType object_type) +{ + const GInterfaceInfo child_proxy_interface_info = { + (GInterfaceInitFunc) gst_iir_equalizer_child_proxy_interface_init, + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + g_type_add_interface_static (object_type, GST_TYPE_CHILD_PROXY, + &child_proxy_interface_info); +} + +GST_BOILERPLATE_FULL(Gstaudioeq, gst_audioeq, GstBaseTransform, GST_TYPE_BASE_TRANSFORM,_do_init); + +static void gst_audioeq_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_audioeq_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +#ifdef AUDIOEQ_REDUCE_MEMCPY +static GstFlowReturn gst_audioeq_transform_ip (GstBaseTransform * base, GstBuffer * buf); +#else +static GstFlowReturn gst_audioeq_transform (GstBaseTransform * base, GstBuffer * inbuf, GstBuffer * outbuf); +#endif +static gboolean gst_audioeq_set_caps (GstBaseTransform * base, GstCaps * incaps, GstCaps * outcaps); + +static GstStateChangeReturn +gst_audioeq_change_state (GstElement * element, GstStateChange transition) +{ + GST_DEBUG ("gst_audioeq_change_state"); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + Gstaudioeq *audioeq = GST_AUDIOEQ (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + audioeq->need_update_filter = TRUE; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + default: + break; + } + + return ret; +} + +static void +gst_audioeq_base_init (gpointer gclass) +{ + + GST_DEBUG ("gst_audioeq_base_init"); + static GstElementDetails element_details = { + "Audio Equalizer", + "Filter/Effect/Audio", + "Set equalisation effect on audio/raw streams", + "Samsung Electronics " + }; + + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get (&srctemplate)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get (&sinktemplate)); + + gst_element_class_set_details(element_class, &element_details); +} + +static void +gst_audioeq_class_init (GstaudioeqClass * klass) +{ + GST_DEBUG ("gst_audioeq_class_init"); + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseTransformClass *basetransform_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + basetransform_class = GST_BASE_TRANSFORM_CLASS(klass); + + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_audioeq_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_audioeq_get_property); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR(gst_audioeq_change_state); + + g_object_class_install_property(gobject_class, PROP_FILTER_ACTION, + g_param_spec_uint("filter-action", "filter action", "(0)none (1)preset (2)advanced setting", + 0, 2, DEFAULT_FILTER_ACTION, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_CUSTOM_EQ, + g_param_spec_pointer("custom-eq", "custom eq", + "pointer for 9 bands of EQ array", G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CUSTOM_EQ_NUM, + g_param_spec_uint("custom-eq-num", "custom eq num", "number of custom EQ bands", + 0, 9, DEFAULT_CUSTOM_EQ_NUM, G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_CUSTOM_EQ_FREQ, + g_param_spec_pointer("custom-eq-freq", "custom eq freq", "pointer for EQ bands central frequency(Hz) array", + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, PROP_CUSTOM_EQ_WIDTH, + g_param_spec_pointer("custom-eq-width", "custom eq width", "pointer for EQ bands width(Hz) array", + G_PARAM_READABLE)); + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_iir_equalizer_finalize); + +/* It is possible to reduce memcpy by setting output same as input of AudioEq_InOutConfig */ +#ifdef AUDIOEQ_REDUCE_MEMCPY + basetransform_class->transform_ip = GST_DEBUG_FUNCPTR(gst_audioeq_transform_ip); +#endif + basetransform_class->set_caps = GST_DEBUG_FUNCPTR(gst_audioeq_set_caps); +} + +static void +gst_audioeq_init (Gstaudioeq * audioeq, GstaudioeqClass * gclass) +{ + GST_DEBUG ("gst_audioeq_init"); + audioeq->samplerate = DEFAULT_SAMPLE_RATE; + audioeq->channels = DEAFULT_CHANNELS; + + audioeq->filter_action = DEFAULT_FILTER_ACTION; + memset(audioeq->custom_eq, 0x00, sizeof(gint) * CUSTOM_EQ_BAND_MAX); + audioeq->need_update_filter = TRUE; + audioeq->equ.bands_lock = g_mutex_new (); + audioeq->equ.need_new_coefficients = TRUE; + gst_iir_equalizer_compute_frequencies (audioeq, DEFAULT_CUSTOM_EQ_NUM); +} +/* equalizer implementation */ + +static void +gst_iir_equalizer_finalize (GObject * object) +{ + GST_DEBUG ("gst_iir_equalizer_finalize"); + Gstaudioeq *audioeq = GST_AUDIOEQ(object); + GstIirEqualizer *equ = &audioeq->equ; + gint i; + + for (i = 0; i < equ->freq_band_count; i++) { + if (equ->bands[i]) + gst_object_unparent (GST_OBJECT (equ->bands[i])); + equ->bands[i] = NULL; + } + equ->freq_band_count = 0; + + g_free (equ->bands); + g_free (equ->history); + + g_mutex_free (equ->bands_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +#define BANDS_LOCK(equ) g_mutex_lock(equ->bands_lock) +#define BANDS_UNLOCK(equ) g_mutex_unlock(equ->bands_lock) + +/* child object */ + +enum +{ + PROP_GAIN = 1, + PROP_FREQ, + PROP_BANDWIDTH, + PROP_TYPE +}; + +typedef enum +{ + BAND_TYPE_PEAK = 0, + BAND_TYPE_LOW_SHELF, + BAND_TYPE_HIGH_SHELF +} GstIirEqualizerBandType; + +#define GST_TYPE_IIR_EQUALIZER_BAND_TYPE (gst_iir_equalizer_band_type_get_type ()) +static GType +gst_iir_equalizer_band_type_get_type (void) +{ + GST_DEBUG ("gst_iir_equalizer_band_type_get_type"); + static GType gtype = 0; + + if (gtype == 0) { + static const GEnumValue values[] = { + {BAND_TYPE_PEAK, "Peak filter (default for inner bands)", "peak"}, + {BAND_TYPE_LOW_SHELF, "Low shelf filter (default for first band)", + "low-shelf"}, + {BAND_TYPE_HIGH_SHELF, "High shelf filter (default for last band)", + "high-shelf"}, + {0, NULL, NULL} + }; + + gtype = g_enum_register_static ("GstIirEqualizerBandType", values); + } + return gtype; +} + + +typedef struct _GstIirEqualizerBandClass GstIirEqualizerBandClass; + +#define GST_TYPE_IIR_EQUALIZER_BAND \ + (gst_iir_equalizer_band_get_type()) +#define GST_IIR_EQUALIZER_BAND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IIR_EQUALIZER_BAND,GstIirEqualizerBand)) +#define GST_IIR_EQUALIZER_BAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IIR_EQUALIZER_BAND,GstIirEqualizerBandClass)) +#define GST_IS_IIR_EQUALIZER_BAND(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IIR_EQUALIZER_BAND)) +#define GST_IS_IIR_EQUALIZER_BAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IIR_EQUALIZER_BAND)) + +struct _GstIirEqualizerBand +{ + GstObject object; + + /*< private > */ + /* center frequency and gain */ + gdouble freq; + gdouble gain; + gdouble width; + GstIirEqualizerBandType type; + + /* second order iir filter */ + gdouble b1, b2; /* IIR coefficients for outputs */ + gdouble a0, a1, a2; /* IIR coefficients for inputs */ +}; + +struct _GstIirEqualizerBandClass +{ + GstObjectClass parent_class; +}; + +static GType gst_iir_equalizer_band_get_type (void); + +static void +gst_iir_equalizer_band_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GST_DEBUG ("gst_iir_equalizer_band_set_property"); + GstIirEqualizerBand *band = GST_IIR_EQUALIZER_BAND (object); + Gstaudioeq *audioeq = GST_AUDIOEQ (gst_object_get_parent (GST_OBJECT (band))); + GstIirEqualizer *equ = &audioeq->equ; + + switch (prop_id) { + case PROP_GAIN:{ + gdouble gain; + + gain = g_value_get_double (value); + GST_DEBUG_OBJECT (band, "gain = %lf -> %lf", band->gain, gain); + if (gain != band->gain) { + BANDS_LOCK (equ); + equ->need_new_coefficients = TRUE; + band->gain = gain; + BANDS_UNLOCK (equ); + GST_DEBUG_OBJECT (band, "changed gain = %lf ", band->gain); + } + break; + } + case PROP_FREQ:{ + gdouble freq; + + freq = g_value_get_double (value); + GST_DEBUG_OBJECT (band, "freq = %lf -> %lf", band->freq, freq); + if (freq != band->freq) { + BANDS_LOCK (equ); + equ->need_new_coefficients = TRUE; + band->freq = freq; + BANDS_UNLOCK (equ); + GST_DEBUG_OBJECT (band, "changed freq = %lf ", band->freq); + } + break; + } + case PROP_BANDWIDTH:{ + gdouble width; + + width = g_value_get_double (value); + GST_DEBUG_OBJECT (band, "width = %lf -> %lf", band->width, width); + if (width != band->width) { + BANDS_LOCK (equ); + equ->need_new_coefficients = TRUE; + band->width = width; + BANDS_UNLOCK (equ); + GST_DEBUG_OBJECT (band, "changed width = %lf ", band->width); + } + break; + } + case PROP_TYPE:{ + GstIirEqualizerBandType type; + + type = g_value_get_enum (value); + GST_DEBUG_OBJECT (band, "type = %d -> %d", band->type, type); + if (type != band->type) { + BANDS_LOCK (equ); + equ->need_new_coefficients = TRUE; + band->type = type; + BANDS_UNLOCK (equ); + GST_DEBUG_OBJECT (band, "changed type = %d ", band->type); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + gst_object_unref (audioeq); +} + +static void +gst_iir_equalizer_band_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GST_DEBUG ("gst_iir_equalizer_band_get_property"); + GstIirEqualizerBand *band = GST_IIR_EQUALIZER_BAND (object); + + switch (prop_id) { + case PROP_GAIN: + g_value_set_double (value, band->gain); + break; + case PROP_FREQ: + g_value_set_double (value, band->freq); + break; + case PROP_BANDWIDTH: + g_value_set_double (value, band->width); + break; + case PROP_TYPE: + g_value_set_enum (value, band->type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_iir_equalizer_band_class_init (GstIirEqualizerBandClass * klass) +{ + GST_DEBUG ("gst_iir_equalizer_band_class_init"); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gst_iir_equalizer_band_set_property; + gobject_class->get_property = gst_iir_equalizer_band_get_property; + + g_object_class_install_property (gobject_class, PROP_GAIN, + g_param_spec_double ("gain", "gain", + "gain for the frequency band ranging from -12.0 dB to +12.0 dB", + -12.0, 12.0, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_FREQ, + g_param_spec_double ("freq", "freq", + "center frequency of the band", + 0.0, 100000.0, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_BANDWIDTH, + g_param_spec_double ("bandwidth", "bandwidth", + "difference between bandedges in Hz", + 0.0, 100000.0, 1.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_TYPE, + g_param_spec_enum ("type", "Type", + "Filter type", GST_TYPE_IIR_EQUALIZER_BAND_TYPE, + BAND_TYPE_PEAK, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); +} + +static void +gst_iir_equalizer_band_init (GstIirEqualizerBand * band, + GstIirEqualizerBandClass * klass) +{ + GST_DEBUG ("gst_iir_equalizer_band_init"); + band->freq = 0.0; + band->gain = 0.0; + band->width = 1.0; + band->type = BAND_TYPE_PEAK; +} + +static GType +gst_iir_equalizer_band_get_type (void) +{ + GST_DEBUG ("gst_iir_equalizer_band_get_type"); + static GType type = 0; + + if (G_UNLIKELY (!type)) { + const GTypeInfo type_info = { + sizeof (GstIirEqualizerBandClass), + NULL, + NULL, + (GClassInitFunc) gst_iir_equalizer_band_class_init, + NULL, + NULL, + sizeof (GstIirEqualizerBand), + 0, + (GInstanceInitFunc) gst_iir_equalizer_band_init, + }; + type = + g_type_register_static (GST_TYPE_OBJECT, "GstIirEqualizerBand", + &type_info, 0); + } + return (type); +} + + +/* child proxy iface */ +static GstObject * +gst_iir_equalizer_child_proxy_get_child_by_index (GstChildProxy * child_proxy, + guint index) +{ + GST_DEBUG ("gst_iir_equalizer_child_proxy_get_child_by_index"); + Gstaudioeq *audioeq = GST_AUDIOEQ(child_proxy); + GstIirEqualizer *equ = &audioeq->equ; + GstObject *ret; + + BANDS_LOCK (equ); + if (G_UNLIKELY (index >= equ->freq_band_count)) { + BANDS_UNLOCK (equ); + g_return_val_if_fail (index < equ->freq_band_count, NULL); + } + + ret = gst_object_ref (equ->bands[index]); + BANDS_UNLOCK (equ); + + GST_LOG_OBJECT (equ, "return child[%d] %" GST_PTR_FORMAT, index, ret); + return ret; +} + +static guint +gst_iir_equalizer_child_proxy_get_children_count (GstChildProxy * child_proxy) +{ + GST_DEBUG ("gst_iir_equalizer_child_proxy_get_children_count"); + Gstaudioeq *audioeq = GST_AUDIOEQ(child_proxy); + GstIirEqualizer *equ = &audioeq->equ; + + GST_LOG ("we have %d children", equ->freq_band_count); + return equ->freq_band_count; +} + +static void +gst_iir_equalizer_child_proxy_interface_init (gpointer g_iface, + gpointer iface_data) +{ + GstChildProxyInterface *iface = g_iface; + + GST_DEBUG ("initializing iface"); + + iface->get_child_by_index = gst_iir_equalizer_child_proxy_get_child_by_index; + iface->get_children_count = gst_iir_equalizer_child_proxy_get_children_count; +} +static void +gst_iir_equalizer_class_init (GstIirEqualizerClass * klass) +{ + GST_DEBUG ("gst_iir_equalizer_class_init"); +} + +static void +gst_iir_equalizer_init (GstIirEqualizer * eq, GstIirEqualizerClass * g_class) +{ + GST_DEBUG ("gst_iir_equalizer_init"); + eq->bands_lock = g_mutex_new (); + eq->need_new_coefficients = TRUE; +} + +GType +gst_iir_equalizer_get_type (void) +{ + GST_DEBUG ("gst_iir_equalizer_get_type"); + static GType type = 0; + + if (G_UNLIKELY (!type)) { + const GTypeInfo type_info = { + sizeof (GstIirEqualizerClass), + NULL, + NULL, + (GClassInitFunc) gst_iir_equalizer_class_init, + NULL, + NULL, + sizeof (GstIirEqualizer), + 0, + (GInstanceInitFunc) gst_iir_equalizer_init, + }; + type = + g_type_register_static (GST_TYPE_OBJECT, "GstIirEqualizer", + &type_info, 0); + } + return (type); +} +/* Filter taken from + * + * The Equivalence of Various Methods of Computing + * Biquad Coefficients for Audio Parametric Equalizers + * + * by Robert Bristow-Johnson + * + * http://www.aes.org/e-lib/browse.cfm?elib=6326 + * http://www.musicdsp.org/files/EQ-Coefficients.pdf + * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + * + * The bandwidth method that we use here is the preferred + * one from this article transformed from octaves to frequency + * in Hz. + */ +static inline gdouble +arg_to_scale (gdouble arg) +{ + GST_DEBUG ("arg_to_scale"); + return (pow (10.0, arg / 40.0)); +} + +static gdouble +calculate_omega (gdouble freq, gint rate) +{ + GST_DEBUG ("calculate_omega"); + gdouble omega; + + if (freq / rate >= 0.5) + omega = G_PI; + else if (freq <= 0.0) + omega = 0.0; + else + omega = 2.0 * G_PI * (freq / rate); + + return omega; +} + +static gdouble +calculate_bw (GstIirEqualizerBand * band, gint rate) +{ + GST_DEBUG ("calculate_bw"); + gdouble bw = 0.0; + + if (band->width / rate >= 0.5) { + /* If bandwidth == 0.5 the calculation below fails as tan(G_PI/2) + * is undefined. So set the bandwidth to a slightly smaller value. + */ + bw = G_PI - 0.00000001; + } else if (band->width <= 0.0) { + /* If bandwidth == 0 this band won't change anything so set + * the coefficients accordingly. The coefficient calculation + * below would create coefficients that for some reason amplify + * the band. + */ + band->a0 = 1.0; + band->a1 = 0.0; + band->a2 = 0.0; + band->b1 = 0.0; + band->b2 = 0.0; + } else { + bw = 2.0 * G_PI * (band->width / rate); + } + return bw; +} + +static void +setup_peak_filter (Gstaudioeq* audioeq, GstIirEqualizerBand * band) +{ + GST_DEBUG ("setup_peak_filter"); + //g_return_if_fail (GST_AUDIO_FILTER (equ)->format.rate); + + { + gdouble gain, omega, bw; + gdouble alpha, alpha1, alpha2, b0; + + gain = arg_to_scale (band->gain); + omega = calculate_omega (band->freq, audioeq->samplerate); + bw = calculate_bw (band, audioeq->samplerate); + if (bw == 0.0) + goto out; + + alpha = tan (bw / 2.0); + + alpha1 = alpha * gain; + alpha2 = alpha / gain; + + b0 = (1.0 + alpha2); + + band->a0 = (1.0 + alpha1) / b0; + band->a1 = (-2.0 * cos (omega)) / b0; + band->a2 = (1.0 - alpha1) / b0; + band->b1 = (2.0 * cos (omega)) / b0; + band->b2 = -(1.0 - alpha2) / b0; + + out: + GST_INFO + ("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g", + band->gain, band->width, band->freq, band->a0, band->a1, band->a2, + band->b1, band->b2); + } +} + +static void +setup_low_shelf_filter (Gstaudioeq* audioeq, GstIirEqualizerBand * band) +{ + GST_DEBUG ("setup_low_shelf_filter"); + //g_return_if_fail (GST_AUDIO_FILTER (equ)->format.rate); + + { + gdouble gain, omega, bw; + gdouble alpha, delta, b0; + gdouble egp, egm; + + gain = arg_to_scale (band->gain); + omega = calculate_omega (band->freq, audioeq->samplerate); + bw = calculate_bw (band, audioeq->samplerate); + if (bw == 0.0) + goto out; + + egm = gain - 1.0; + egp = gain + 1.0; + alpha = tan (bw / 2.0); + + delta = 2.0 * sqrt (gain) * alpha; + b0 = egp + egm * cos (omega) + delta; + + band->a0 = ((egp - egm * cos (omega) + delta) * gain) / b0; + band->a1 = ((egm - egp * cos (omega)) * 2.0 * gain) / b0; + band->a2 = ((egp - egm * cos (omega) - delta) * gain) / b0; + band->b1 = ((egm + egp * cos (omega)) * 2.0) / b0; + band->b2 = -((egp + egm * cos (omega) - delta)) / b0; + + + out: + GST_INFO + ("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g", + band->gain, band->width, band->freq, band->a0, band->a1, band->a2, + band->b1, band->b2); + } +} + +static void +setup_high_shelf_filter (Gstaudioeq* audioeq, GstIirEqualizerBand * band) +{ + GST_DEBUG ("setup_high_shelf_filter"); + { + gdouble gain, omega, bw; + gdouble alpha, delta, b0; + gdouble egp, egm; + + gain = arg_to_scale (band->gain); + omega = calculate_omega (band->freq, audioeq->samplerate); + bw = calculate_bw (band, audioeq->samplerate); + if (bw == 0.0) + goto out; + + egm = gain - 1.0; + egp = gain + 1.0; + alpha = tan (bw / 2.0); + + delta = 2.0 * sqrt (gain) * alpha; + b0 = egp - egm * cos (omega) + delta; + + band->a0 = ((egp + egm * cos (omega) + delta) * gain) / b0; + band->a1 = ((egm + egp * cos (omega)) * -2.0 * gain) / b0; + band->a2 = ((egp + egm * cos (omega) - delta) * gain) / b0; + band->b1 = ((egm - egp * cos (omega)) * -2.0) / b0; + band->b2 = -((egp - egm * cos (omega) - delta)) / b0; + + + out: + GST_INFO + ("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g", + band->gain, band->width, band->freq, band->a0, band->a1, band->a2, + band->b1, band->b2); + } +} + +/* Must be called with bands_lock and transform lock! */ +static void +set_passthrough (Gstaudioeq* audioeq) +{ + GST_DEBUG ("set_passthrough"); + GstIirEqualizer* equ=&audioeq->equ; + gint i; + gboolean passthrough = TRUE; + + for (i = 0; i < equ->freq_band_count; i++) { + passthrough = passthrough && (equ->bands[i]->gain == 0.0); + } + + gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (audioeq), passthrough); + GST_DEBUG ("Passthrough mode: %d\n", passthrough); +} + +/* Must be called with bands_lock and transform lock! */ +static void +update_coefficients (Gstaudioeq* audioeq) +{ + GST_DEBUG ("update_coefficients"); + GstIirEqualizer* equ=&audioeq->equ; + gint i, n = equ->freq_band_count; + + for (i = 0; i < n; i++) { + if (equ->bands[i]->type == BAND_TYPE_PEAK) + setup_peak_filter (audioeq, equ->bands[i]); + else if (equ->bands[i]->type == BAND_TYPE_LOW_SHELF) + setup_low_shelf_filter (audioeq, equ->bands[i]); + else + setup_high_shelf_filter (audioeq, equ->bands[i]); + } + + equ->need_new_coefficients = FALSE; +} + +/* Must be called with transform lock! */ +static void +alloc_history (GstIirEqualizer * equ) +{ + GST_DEBUG ("alloc_history"); + /* free + alloc = no memcpy */ + g_free (equ->history); + equ->history = + g_malloc0 (equ->history_size * equ->audiofilter.format.channels * + equ->freq_band_count); +} +void +gst_audioeq_band_set_property(Gstaudioeq * audioeq) +{ + GST_DEBUG ("gst_audioeq_band_set_property"); + GstIirEqualizer *equ = &audioeq->equ; + gshort i ; + for ( i = 0; i < DEFAULT_CUSTOM_EQ_NUM; i++){ + GST_DEBUG ("gain = %lf -> %d", equ->bands[i]->gain, audioeq->custom_eq[i] ); + if (audioeq->custom_eq[i] != equ->bands[i]->gain) { + equ->bands[i]->gain = audioeq->custom_eq[i]; + GST_DEBUG("changed gain = %lf ", equ->bands[i]->gain); + g_object_notify (G_OBJECT (equ->bands[i]), "gain"); + } + } +} + +void +gst_iir_equalizer_compute_frequencies (Gstaudioeq * audioeq, guint new_count) +{ + GST_DEBUG ("gst_iir_equalizer_compute_frequencies"); + + GstIirEqualizer *equ = &audioeq->equ; + guint old_count, i; + gdouble freq0, freq1, step; + gchar name[20]; + GST_DEBUG ("gst_iir_equalizer_compute_frequencies before calling equalizer object"); + if (equ->freq_band_count == new_count) + return; + + GST_DEBUG ("gst_iir_equalizer_compute_frequencies equalizer object"); + + BANDS_LOCK (equ); + GST_DEBUG ("gst_iir_equalizer_compute_frequencies 1"); + if (G_UNLIKELY (equ->freq_band_count == new_count)) { + BANDS_UNLOCK (equ); + return; + } + GST_DEBUG ("gst_iir_equalizer_compute_frequencies 2"); + old_count = equ->freq_band_count; + equ->freq_band_count = new_count; + GST_DEBUG ("bands %u -> %u", old_count, new_count); + + if (old_count < new_count) { + /* add new bands */ + equ->bands = g_realloc (equ->bands, sizeof (GstObject *) * new_count); + for (i = old_count; i < new_count; i++) { + equ->bands[i] = g_object_new (GST_TYPE_IIR_EQUALIZER_BAND, NULL); + /* otherwise they get names like 'iirequalizerband5' */ + snprintf (name, sizeof(name), "band%u", i); + gst_object_set_name (GST_OBJECT (equ->bands[i]), name); + GST_DEBUG ("adding band[%d]=%p", i, equ->bands[i]); + + gst_object_set_parent (GST_OBJECT (equ->bands[i]), GST_OBJECT (audioeq)); + gst_child_proxy_child_added (GST_OBJECT (audioeq), + GST_OBJECT (equ->bands[i])); + } + } else { + /* free unused bands */ + for (i = new_count; i < old_count; i++) { + GST_DEBUG ("removing band[%d]=%p", i, equ->bands[i]); + gst_child_proxy_child_removed (GST_OBJECT (audioeq), + GST_OBJECT (equ->bands[i])); + gst_object_unparent (GST_OBJECT (equ->bands[i])); + equ->bands[i] = NULL; + } + } + + alloc_history (equ); + + /* set center frequencies and name band objects + * FIXME: arg! we can't change the name of parented objects :( + * application should read band->freq to get the name + */ + + step = pow (HIGHEST_FREQ / LOWEST_FREQ, 1.0 / new_count); + freq0 = LOWEST_FREQ; + for (i = 0; i < new_count; i++) { + freq1 = freq0 * step; + + if (i == 0) + equ->bands[i]->type = BAND_TYPE_LOW_SHELF; + else if (i == new_count - 1) + equ->bands[i]->type = BAND_TYPE_HIGH_SHELF; + else + equ->bands[i]->type = BAND_TYPE_PEAK; + + equ->bands[i]->freq = freq0 + ((freq1 - freq0) / 2.0); + equ->bands[i]->width = freq1 - freq0; + GST_DEBUG ("band[%2d] = '%lf'", i, equ->bands[i]->freq); + + g_object_notify (G_OBJECT (equ->bands[i]), "bandwidth"); + g_object_notify (G_OBJECT (equ->bands[i]), "freq"); + g_object_notify (G_OBJECT (equ->bands[i]), "type"); + + freq0 = freq1; + } + + equ->need_new_coefficients = TRUE; + + BANDS_UNLOCK (equ); +} +/* start of code that is type specific */ + +#define CREATE_OPTIMIZED_FUNCTIONS_INT(TYPE,BIG_TYPE,MIN_VAL,MAX_VAL) \ +typedef struct { \ + BIG_TYPE x1, x2; /* history of input values for a filter */ \ + BIG_TYPE y1, y2; /* history of output values for a filter */ \ +} SecondOrderHistory ## TYPE; \ + \ +static inline BIG_TYPE \ +one_step_ ## TYPE (GstIirEqualizerBand *filter, \ + SecondOrderHistory ## TYPE *history, BIG_TYPE input) \ +{ \ + /* calculate output */ \ + BIG_TYPE output = filter->a0 * input + \ + filter->a1 * history->x1 + filter->a2 * history->x2 + \ + filter->b1 * history->y1 + filter->b2 * history->y2; \ + /* update history */ \ + history->y2 = history->y1; \ + history->y1 = output; \ + history->x2 = history->x1; \ + history->x1 = input; \ + \ + return output; \ +} \ + \ +static const guint \ +history_size_ ## TYPE = sizeof (SecondOrderHistory ## TYPE); \ + \ +static void \ +gst_iir_equ_process_ ## TYPE (GstIirEqualizer *equ, guint8 *data, \ +guint size, guint channels) \ +{ \ + guint frames = size / channels / sizeof (TYPE); \ + guint i, c, f, nf = equ->freq_band_count; \ + BIG_TYPE cur; \ + GstIirEqualizerBand **filters = equ->bands; \ + \ + for (i = 0; i < frames; i++) { \ + SecondOrderHistory ## TYPE *history = equ->history; \ + for (c = 0; c < channels; c++) { \ + cur = *((TYPE *) data); \ + for (f = 0; f < nf; f++) { \ + cur = one_step_ ## TYPE (filters[f], history, cur); \ + history++; \ + } \ + cur = CLAMP (cur, MIN_VAL, MAX_VAL); \ + *((TYPE *) data) = (TYPE) floor (cur); \ + data += sizeof (TYPE); \ + } \ + } \ +} + +#define CREATE_OPTIMIZED_FUNCTIONS(TYPE) \ +typedef struct { \ + TYPE x1, x2; /* history of input values for a filter */ \ + TYPE y1, y2; /* history of output values for a filter */ \ +} SecondOrderHistory ## TYPE; \ + \ +static inline TYPE \ +one_step_ ## TYPE (GstIirEqualizerBand *filter, \ + SecondOrderHistory ## TYPE *history, TYPE input) \ +{ \ + /* calculate output */ \ + TYPE output = filter->a0 * input + filter->a1 * history->x1 + \ + filter->a2 * history->x2 + filter->b1 * history->y1 + \ + filter->b2 * history->y2; \ + /* update history */ \ + history->y2 = history->y1; \ + history->y1 = output; \ + history->x2 = history->x1; \ + history->x1 = input; \ + \ + return output; \ +} \ + \ +static const guint \ +history_size_ ## TYPE = sizeof (SecondOrderHistory ## TYPE); \ + \ +static void \ +gst_iir_equ_process_ ## TYPE (GstIirEqualizer *equ, guint8 *data, \ +guint size, guint channels) \ +{ \ + guint frames = size / channels / sizeof (TYPE); \ + guint i, c, f, nf = equ->freq_band_count; \ + TYPE cur; \ + GstIirEqualizerBand **filters = equ->bands; \ + \ + for (i = 0; i < frames; i++) { \ + SecondOrderHistory ## TYPE *history = equ->history; \ + for (c = 0; c < channels; c++) { \ + cur = *((TYPE *) data); \ + for (f = 0; f < nf; f++) { \ + cur = one_step_ ## TYPE (filters[f], history, cur); \ + history++; \ + } \ + *((TYPE *) data) = (TYPE) cur; \ + data += sizeof (TYPE); \ + } \ + } \ +} + +CREATE_OPTIMIZED_FUNCTIONS_INT (gint16, gfloat, -32768.0, 32767.0); +CREATE_OPTIMIZED_FUNCTIONS (gfloat); +CREATE_OPTIMIZED_FUNCTIONS (gdouble); + +#ifdef AUDIOEQ_REDUCE_MEMCPY +static GstFlowReturn +gst_audioeq_transform_ip (GstBaseTransform * base, GstBuffer * buf) +{ + GST_DEBUG ("gst_audioeq_transform_ip"); + Gstaudioeq *audioeq = GST_AUDIOEQ(base); + GstIirEqualizer *equ = &audioeq->equ; + + equ->history_size = history_size_gint16; + equ->process = gst_iir_equ_process_gint16; + g_free (equ->history); + equ->history = g_malloc0 (equ->history_size * audioeq->channels * equ->freq_band_count); + GstClockTime timestamp; + + if (G_UNLIKELY (audioeq->channels < 1 || equ->process == NULL)) { + GST_DEBUG ("gst_audioeq_transform_ip return GST_FLOW_NOT_NEGOTIATED;"); + if (G_UNLIKELY (equ->process == NULL)) + GST_DEBUG ("gst_audioeq_transform_ip equ->process "); + if (G_UNLIKELY (audioeq->channels < 1)) + GST_DEBUG ("gst_audioeq_transform_ip audioeq->channels"); + return GST_FLOW_NOT_NEGOTIATED; + } + GST_DEBUG ("gst_audioeq_transform_ip BANDS_LOCK (equ);"); + BANDS_LOCK (equ); + if (equ->need_new_coefficients) { + GST_DEBUG ("gst_audioeq_transform_ip update_coefficients"); + update_coefficients (audioeq); + set_passthrough (audioeq); + } + BANDS_UNLOCK (equ); + + if (gst_base_transform_is_passthrough (base)) { + GST_DEBUG ("gst_audioeq_transform_ip gst_base_transform_is_passthrough return GST_FLOW_OK;"); + return GST_FLOW_OK; + } + timestamp = GST_BUFFER_TIMESTAMP (buf); + timestamp = + gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp); + + if (GST_CLOCK_TIME_IS_VALID (timestamp)) + gst_object_sync_values (G_OBJECT (audioeq), timestamp); + GST_DEBUG (" equ->process ++"); + equ->process (equ, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), + audioeq->channels); + GST_DEBUG (" equ->process --"); + GST_DEBUG ("gst_audioeq_transform_ip return GST_FLOW_OK;"); + return GST_FLOW_OK; +} +#endif + +static gboolean +gst_audioeq_set_caps (GstBaseTransform * base, GstCaps * incaps, + GstCaps * outcaps) +{ + GST_DEBUG ("gst_audioeq_set_caps"); + Gstaudioeq *audioeq = GST_AUDIOEQ(base); + GstStructure *ins; + GstPad *pad; + gint samplerate; + gint channels; + gshort old_samplerate; + gshort old_channels; + + pad = gst_element_get_static_pad(GST_ELEMENT(audioeq), "src"); + + /* forward-negotiate */ + if(!gst_pad_set_caps(pad, incaps)) { + gst_object_unref(pad); + return FALSE; + } + + /* negotiation succeeded, so now configure ourselves */ + ins = gst_caps_get_structure(incaps, 0); + + /* get samplerate from caps & convert */ + old_samplerate = audioeq->samplerate; + old_channels = audioeq->channels; + gst_structure_get_int(ins, "rate", &samplerate); + switch (samplerate) { + case 48000: + audioeq->samplerate = SAMPLERATE_48000Hz; + break; + case 44100: + audioeq->samplerate = SAMPLERATE_44100Hz; + break; + case 32000: + audioeq->samplerate = SAMPLERATE_32000Hz; + break; + case 24000: + audioeq->samplerate = SAMPLERATE_24000Hz; + break; + case 22050: + audioeq->samplerate = SAMPLERATE_22050Hz; + break; + case 16000: + audioeq->samplerate = SAMPLERATE_16000Hz; + break; + case 12000: + audioeq->samplerate = SAMPLERATE_12000Hz; + break; + case 11025: + audioeq->samplerate = SAMPLERATE_11025Hz; + break; + case 8000: + audioeq->samplerate = SAMPLERATE_8000Hz; + break; + default: + if (samplerate < 8000) { + audioeq->samplerate = SAMPLERATE_8000Hz; + } else if (samplerate > 48000) { + audioeq->samplerate = SAMPLERATE_48000Hz; + } + break; + } + /* get number of channels from caps */ + gst_structure_get_int(ins, "channels", &channels); + audioeq->channels = (gshort)channels; + + if ((old_samplerate != audioeq->samplerate) + || (old_channels != audioeq->channels)) { + audioeq->need_update_filter = TRUE; + } + + gst_object_unref (pad); + + return TRUE; +} + +static void +gst_audioeq_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GST_DEBUG ("gst_audioeq_set_property"); + Gstaudioeq *audioeq = GST_AUDIOEQ (object); + GstIirEqualizer *equ = &audioeq->equ; + gshort *pointer; + + switch (prop_id) { + + case PROP_FILTER_ACTION: + audioeq->filter_action = g_value_get_uint(value); + BANDS_LOCK(equ); + equ->need_new_coefficients = TRUE; + BANDS_UNLOCK(equ); + break; + + case PROP_CUSTOM_EQ: + pointer = g_value_get_pointer(value); + if (pointer) { + memcpy(audioeq->custom_eq, pointer, sizeof(gint) * CUSTOM_EQ_BAND_MAX); + if (audioeq->filter_action == FILTER_ADVANCED_SETTING) { + BANDS_LOCK(equ); + equ->need_new_coefficients = TRUE; + gst_audioeq_band_set_property(audioeq); + BANDS_UNLOCK(equ); + } + } + break; + + default: + break; + } + GST_DEBUG ("gst_audioeq_set_property need_update_filter %d", audioeq->need_update_filter); +} + +static void +gst_audioeq_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) +{ +GST_DEBUG ("gst_audioeq_get_property"); + + Gstaudioeq *audioeq = GST_AUDIOEQ (object); + GstIirEqualizer *equ = &audioeq->equ; + gshort i; + gdouble widtharr[DEFAULT_CUSTOM_EQ_NUM],freqarr[DEFAULT_CUSTOM_EQ_NUM]; + + switch (prop_id) { + case PROP_FILTER_ACTION: + g_value_set_uint(value, audioeq->filter_action); + break; + + case PROP_CUSTOM_EQ: + g_value_set_pointer(value, audioeq->custom_eq); + break; + + case PROP_CUSTOM_EQ_NUM: + g_value_set_uint(value, DEFAULT_CUSTOM_EQ_NUM); + break; + + case PROP_CUSTOM_EQ_FREQ: + for(i=0;ibands[i]->freq; + } + g_value_set_pointer(value, &freqarr); + break; + + case PROP_CUSTOM_EQ_WIDTH: + for(i=0;ibands[i]->width; + } + g_value_set_pointer(value, &widtharr); + break; + + default: + break; + } +} + +static gboolean +gst_iir_equalizer_setup (GstAudioFilter * audio, GstRingBufferSpec * fmt) +{ +GST_DEBUG ("gst_iir_equalizer_setup"); + GstIirEqualizer *equ = GST_IIR_EQUALIZER (audio); + + switch (fmt->type) { + case GST_BUFTYPE_LINEAR: + switch (fmt->width) { + case 16: + equ->history_size = history_size_gint16; + equ->process = gst_iir_equ_process_gint16; + break; + default: + return FALSE; + } + break; + case GST_BUFTYPE_FLOAT: + switch (fmt->width) { + case 32: + equ->history_size = history_size_gfloat; + equ->process = gst_iir_equ_process_gfloat; + break; + case 64: + equ->history_size = history_size_gdouble; + equ->process = gst_iir_equ_process_gdouble; + break; + default: + return FALSE; + } + break; + default: + return FALSE; + } + + alloc_history (equ); + return TRUE; +} + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG ("audioeq plugin_init "); + GST_DEBUG_CATEGORY_INIT(gst_audioeq_debug, "audioeq", 0, "Audio Equalizer Plugin"); + return gst_element_register(plugin, "audioeq", GST_RANK_NONE, GST_TYPE_AUDIOEQ); +} + + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "audioeq", + "Audio Equalizer Plugin", + plugin_init, + VERSION, + "LGPL", + "gst-plugins-ext", + "https://www.tizen.org/") diff --git a/audioeq/src/gstaudioeq.h b/audioeq/src/gstaudioeq.h new file mode 100644 index 0000000..a2c3815 --- /dev/null +++ b/audioeq/src/gstaudioeq.h @@ -0,0 +1,127 @@ +/* + * audioeq + * + * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Aditi Narula + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef __GST_AUDIOEQ_H__ +#define __GST_AUDIOEQ_H__ + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AUDIOEQ \ + (gst_audioeq_get_type()) +#define GST_AUDIOEQ(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIOEQ,Gstaudioeq)) +#define GST_audioeq_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIOEQ,GstaudioeqClass)) +#define GST_IS_AUDIOEQ(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIOEQ)) +#define GST_IS_AUDIOEQ_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIOEQ)) +#define GST_TYPE_IIR_EQUALIZER \ + (gst_iir_equalizer_get_type()) +#define GST_IIR_EQUALIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IIR_EQUALIZER,GstIirEqualizer)) +#define GST_IIR_EQUALIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IIR_EQUALIZER,GstIirEqualizerClass)) +#define GST_IS_IIR_EQUALIZER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IIR_EQUALIZER)) +#define GST_IS_IIR_EQUALIZER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IIR_EQUALIZER)) + +typedef struct _Gstaudioeq Gstaudioeq; +typedef struct _GstaudioeqClass GstaudioeqClass; +typedef struct _GstIirEqualizer GstIirEqualizer; +typedef struct _GstIirEqualizerClass GstIirEqualizerClass; +typedef struct _GstIirEqualizerBand GstIirEqualizerBand; + +#define CUSTOM_EQ_BAND_MAX 9 + +#define LOWEST_FREQ (20.0) +#define HIGHEST_FREQ (20000.0) + +typedef void (*ProcessFunc) (GstIirEqualizer * eq, guint8 * data, guint size, + guint channels); + +struct _GstIirEqualizer +{ + GstAudioFilter audiofilter; + + /*< private >*/ + + GMutex *bands_lock; + GstIirEqualizerBand **bands; + + /* properties */ + guint freq_band_count; + /* for each band and channel */ + gpointer history; + guint history_size; + + gboolean need_new_coefficients; + + ProcessFunc process; +}; + +struct _GstIirEqualizerClass +{ + GstAudioFilterClass audiofilter_class; +}; + + + +struct _Gstaudioeq +{ + GstBaseTransform element; + + guint samplerate; + guint channels; + + /* properties */ + guint filter_action; + gint custom_eq[CUSTOM_EQ_BAND_MAX]; + gboolean need_update_filter; + GstIirEqualizer equ; +}; + +struct _GstaudioeqClass +{ + GstAudioFilterClass parent_class; +}; + +void gst_iir_equalizer_compute_frequencies (Gstaudioeq * audioeq, guint new_count); + +GType gst_iir_equalizer_get_type(void); + +GType gst_audioeq_get_type (void); + +G_END_DECLS + +#endif /* __GST_AUDIOEQ_H__ */ + diff --git a/audiotp/src/gstaudiotp.c b/audiotp/src/gstaudiotp.c index 5b1419b..f0e73e4 100755 --- a/audiotp/src/gstaudiotp.c +++ b/audiotp/src/gstaudiotp.c @@ -517,6 +517,7 @@ send_reverse: return GST_FLOW_OK; } +#if 0 send_dummy: { @@ -532,6 +533,7 @@ send_dummy: } return GST_FLOW_OK; } +#endif error_exit: diff --git a/avsystem/src/Makefile.am b/avsystem/src/Makefile.am deleted file mode 100644 index dd1576e..0000000 --- a/avsystem/src/Makefile.am +++ /dev/null @@ -1,60 +0,0 @@ - -# plugindir is set in configure - -plugin_LTLIBRARIES = libgstavsysaudiosrc.la - -# sources used to compile this plug-in -libgstavsysaudiosrc_la_SOURCES = gstavsyssrc.c \ - gstavsysaudiosrc.c - -libgstavsysaudiosrc_la_CFLAGS = $(GST_CFLAGS) \ - $(GST_BASE_CFLAGS) \ - $(AVSYSTEM_CFLAGS) \ - $(GST_VIDEO_FLAGS) \ - -I$(includedir)/mmf \ - $(MMTA_CFLAGS) \ - $(GST_AUDIO_CFLAGS) \ - $(VCONF_CFLAGS) \ - $(AVSYSAUDIO_CFLAGS) - -libgstavsysaudiosrc_la_LIBADD = $(GST_LIBS) \ - $(GST_BASE_LIBS) \ - $(DATACOMLIB_LIBS) \ - $(HTTPLIB_LIBS) \ - $(AVSYSTEM_LIBS) \ - $(GST_VIDEO_LIBS) \ - -lgstaudio-0.10 \ - $(MMTA_LIBS) \ - $(GST_AUDIO_LIBS) \ - -ldl \ - $(VCONF_LIBS) \ - $(AVSYSAUDIO_LIBS) - -libgstavsysaudiosrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) - -if IS_I386 -libgstavsysaudiosrc_la_CFLAGS += -DI386_SIMULATOR -else -endif - -plugin_LTLIBRARIES += libgstavsyssink.la - -## sources used to compile this plug-in -libgstavsyssink_la_SOURCES = gstavsyssink.c \ - gstavsysmemsink.c - -libgstavsyssink_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(AVSYSVIDEO_CFLAGS) $(AVSYSTEM_CFLAGS) -I$(includedir)/mmf -libgstavsyssink_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(DATACOMLIB_LIBS) $(HTTPLIB_LIBS) $(AVSYSVIDEO_LIBS) $(AVSYSTEM_LIBS) $(GST_VIDEO_LIBS) -lgstaudio-0.10 -ldl -libgstavsyssink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) - - -libgstavsyssink_la_SOURCES += gstavsysaudiosink.c -libgstavsyssink_la_CFLAGS += $(AVSYSAUDIO_CFLAGS) -libgstavsyssink_la_LIBADD += $(AVSYSAUDIO_LIBS) - -if IS_I386 -libgstavsyssink_la_CFLAGS += -DI386_SIMULATOR -else -endif - - diff --git a/avsystem/src/gstavsysaudiosink.c b/avsystem/src/gstavsysaudiosink.c deleted file mode 100644 index 3a6f224..0000000 --- a/avsystem/src/gstavsysaudiosink.c +++ /dev/null @@ -1,943 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - - - -#include -#include - -#include - -#include "gstavsysaudiosink.h" - -#define _ALSA_DAPM_ -#define __REPLACE_RESET_WITH_CLOSE_AND_REOPEN__ - -#define CONVERT_MUTE_VALUE(_mute) ((_mute) ? AVSYS_AUDIO_MUTE : AVSYS_AUDIO_UNMUTE) - -GST_DEBUG_CATEGORY_EXTERN (avsystem_sink_debug); -#define GST_CAT_DEFAULT avsystem_sink_debug - -#define DEFAULT_USER_ROUTE AVSYSAUDIOSINK_USERROUTE_AUTO -#define DEFAULT_AUDIO_ROUTE AVSYSAUDIOSINK_AUDIOROUTE_USE_EXTERNAL_SETTING -#define DEFAULT_VOLUME_TYPE AVSYS_AUDIO_VOLUME_TYPE_MEDIA -#define DEFAULT_MEDIACALL_MODE AVSYS_AUDIO_ECHO_MODE_NONE -#define DEFAULT_FADEUP_VOLUME FALSE -#define DEFAULT_AUDIO_MUTE AVSYSAUDIOSINK_AUDIO_UNMUTE -#define DEFAULT_AUDIO_LATENCY AVSYSAUDIOSINK_LATENCY_MID - - -//GST_DEBUG_CATEGORY_STATIC (gst_avsystemsink_debug); - -/* element factory information */ -static const GstElementDetails gst_avsysaudiosink_details = - GST_ELEMENT_DETAILS ("AV-system Audio OUT", - "Sink/Audio", - "Output to AV System", - "Samsung Electronics co., ltd"); - -enum -{ - PROP_0, - PROP_AUDIO_MUTE, - PROP_AUDIO_VOLUME_TYPE, - PROP_AUDIO_PRIORITY, - PROP_AUDIO_FADEUPVOLUME, - PROP_AUDIO_ROUTE_POLICY, - PROP_AUDIO_USER_ROUTE, - PROP_AUDIO_LATENCY, - PROP_AUDIO_HANDLE, - PROP_AUDIO_CALLBACK -}; - -GType -gst_avsysaudiosink_audio_mute_get_type (void) -{ - static GType avaudio_mute_type = 0; - static const GEnumValue avaudio_mute[] = { - {AVSYSAUDIOSINK_AUDIO_UNMUTE, "Unmute", "unmute"}, - {AVSYSAUDIOSINK_AUDIO_MUTE, "Mute immediately", "mute"}, - {AVSYSAUDIOSINK_AUDIO_MUTE_WITH_FADEDOWN_EFFECT, "Mute with fadedown effect", "fadedown"}, - {0, NULL, NULL}, - }; - - if (!avaudio_mute_type) { - avaudio_mute_type = - g_enum_register_static ("GstAvsysAudioSinkAudioMute", avaudio_mute); - } - return avaudio_mute_type; -} - -GType -gst_avsysaudiosink_user_route_get_type (void) -{ - static GType user_route_type = 0; - static const GEnumValue user_route[] = { - {AVSYSAUDIOSINK_USERROUTE_AUTO, "Route automatically", "auto"}, - {AVSYSAUDIOSINK_USERROUTE_PHONE, "Route to phone only", "phone"}, - {0, NULL, NULL}, - }; - - if (!user_route_type) { - user_route_type = - g_enum_register_static ("GstAvsysAudioSinkUserRoutePolicy",user_route); - } - return user_route_type; -} - -GType -gst_avsysaudiosink_audio_route_get_type (void) -{ - static GType playback_audio_route_type = 0; - static const GEnumValue playback_audio_route[] = { - {AVSYSAUDIOSINK_AUDIOROUTE_USE_EXTERNAL_SETTING, "Use external sound path", "external"}, - {AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_NORMAL, "Auto change between speaker & earphone", "normal"}, - {AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_ALERT, "Play via both speaker & earphone", "alert"}, - {AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_HEADSET_ONLY, "Play via earphone only", "headset"}, - {0, NULL, NULL}, - }; - - if (!playback_audio_route_type) { - playback_audio_route_type = - g_enum_register_static ("GstAvsysAudioSinkAudioRoutePolicy", playback_audio_route); - } - return playback_audio_route_type; -} - - -GType -gst_avsysaudiosink_volume_table_get_type (void) -{ - static GType avsysaudio_volume_table_type = 0; - static const GEnumValue avsysaudio_volume_table[] = { - {AVSYS_AUDIO_VOLUME_TYPE_SYSTEM, "System Volume", "system"}, - {AVSYS_AUDIO_VOLUME_TYPE_NOTIFICATION, "Notification Volume", "notification"}, - {AVSYS_AUDIO_VOLUME_TYPE_ALARM, "Alarm Volume", "alarm"}, - {AVSYS_AUDIO_VOLUME_TYPE_RINGTONE, "Ringtone Volume", "ringtone"}, - {AVSYS_AUDIO_VOLUME_TYPE_MEDIA, "Media Volume", "media"}, - {AVSYS_AUDIO_VOLUME_TYPE_CALL, "Call Volume", "call"}, - {AVSYS_AUDIO_VOLUME_TYPE_FIXED, "Fixed Volume", "fixed"}, - {AVSYS_AUDIO_VOLUME_TYPE_EXT_SYSTEM_JAVA, "Java Volume", "java"}, - {0, NULL, NULL}, - }; - - if (!avsysaudio_volume_table_type) { - avsysaudio_volume_table_type = - g_enum_register_static ("GstAvsysAudioSinkVolumeTable", avsysaudio_volume_table); - } - return avsysaudio_volume_table_type; -} - -GType -gst_avsysaudiosink_latency_get_type (void) -{ - static GType avsysaudio_latency_type = 0; - static const GEnumValue avsysaudio_latency[] = { - {AVSYSAUDIOSINK_LATENCY_LOW, "Low latency", "low"}, - {AVSYSAUDIOSINK_LATENCY_MID, "Mid latency", "mid"}, - {AVSYSAUDIOSINK_LATENCY_HIGH, "High latency", "high"}, - {0, NULL, NULL}, - }; - - if (!avsysaudio_latency_type) { - avsysaudio_latency_type = - g_enum_register_static ("GstAvsysAudioSinkLatency", avsysaudio_latency); - } - return avsysaudio_latency_type; -} - -static void gst_avsysaudiosink_init_interfaces (GType type); - -//#define GST_BOILERPLATE_FULL(type, type_as_function, parent_type, parent_type_macro, additional_initializations) - -GST_BOILERPLATE_FULL (GstAvsysAudioSink, gst_avsysaudiosink, GstAudioSink, - GST_TYPE_AUDIO_SINK, gst_avsysaudiosink_init_interfaces); - - -static void gst_avsysaudiosink_finalise (GObject * object); -static void gst_avsysaudiosink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_avsysaudiosink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); -#if 0 /*not use*/ -static GstCaps *gst_avsysaudiosink_getcaps (GstBaseSink * bsink); -#endif - -static gboolean gst_avsysaudiosink_avsys_close(GstAvsysAudioSink *avsys_audio); -static gboolean gst_avsysaudiosink_avsys_open(GstAvsysAudioSink *avsys_audio); - -static gboolean gst_avsysaudiosink_open (GstAudioSink * asink); -static gboolean gst_avsysaudiosink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec); -static gboolean gst_avsysaudiosink_unprepare (GstAudioSink * asink); -static gboolean gst_avsysaudiosink_close (GstAudioSink * asink); -static guint gst_avsysaudiosink_write (GstAudioSink * asink, gpointer data, guint length); -static guint gst_avsysaudiosink_delay (GstAudioSink * asink); -static void gst_avsysaudiosink_reset (GstAudioSink * asink); -static gboolean avsysaudiosink_post_message(GstAvsysAudioSink* self,int errorcode); - - -#define AVSYS_AUDIO_FACTORY_ENDIANNESS "LITTLE_ENDIAN" - - -static GstStaticPadTemplate avsysaudiosink_sink_factory = - GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw-int, " - "endianness = (int) { " AVSYS_AUDIO_FACTORY_ENDIANNESS " }, " - "signed = (boolean) { TRUE }, " - "width = (int) 16, " - "depth = (int) 16, " - "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 2 ]; " - "audio/x-raw-int, " - "signed = (boolean) { FALSE }, " - "width = (int) 8, " - "depth = (int) 8, " - "rate = (int) [ 1, MAX ], " - "channels = (int) [ 1, 2 ] " - ) - ); -/* -static inline guint _time_to_sample(GstAvsysAudioSink * asink, GstClockTime diff) -{ - guint result = 0; - result =(GST_TIME_AS_USECONDS(diff) * asink->audio_param.samplerate)/1000000; - return result; -} -*/ - -static void -gst_avsysaudiosink_finalise (GObject * object) -{ - GstAvsysAudioSink *sink = NULL; - - sink = GST_AVSYS_AUDIO_SINK (object); - gst_avsysaudiosink_avsys_close(sink); - g_mutex_free (sink->avsys_audio_lock); - g_mutex_free (sink->avsys_audio_reset_lock); - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static void -gst_avsysaudiosink_init_interfaces (GType type) -{ - /* None */ -} - -static void -gst_avsysaudiosink_base_init (gpointer g_class) -{ - GstElementClass *element_class = NULL; - - element_class = GST_ELEMENT_CLASS (g_class); - gst_element_class_set_details (element_class, &gst_avsysaudiosink_details); - gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&avsysaudiosink_sink_factory)); -} - -static GstStateChangeReturn -gst_avsyssudiosink_change_state (GstElement *element, GstStateChange transition); - - -static void -gst_avsysaudiosink_class_init (GstAvsysAudioSinkClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSinkClass *gstbasesink_class; - GstBaseAudioSinkClass *gstbaseaudiosink_class; - GstAudioSinkClass *gstaudiosink_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - gstbasesink_class = (GstBaseSinkClass *) klass; - gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass; - gstaudiosink_class = (GstAudioSinkClass *) klass; - - parent_class = g_type_class_peek_parent (klass); - gstelement_class->change_state = GST_DEBUG_FUNCPTR(gst_avsyssudiosink_change_state); - - gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_finalise); - gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_get_property); - gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_set_property); - -// gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_getcaps); - - gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_open); - gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_prepare); - gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_unprepare); - gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_close); - gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_write); - gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_delay); - gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_avsysaudiosink_reset); - - g_object_class_install_property ( gobject_class, PROP_AUDIO_VOLUME_TYPE, - g_param_spec_enum ("volumetype", "Avsystem Volume Type", - "Select avsystem audio software volume type", - GST_AVSYS_AUDIO_SINK_VOLUME_TYPE, DEFAULT_VOLUME_TYPE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property ( gobject_class, PROP_AUDIO_PRIORITY, - g_param_spec_int ("priority", "Avsystem Sound Priority", "Avsystem sound priority", - AVSYS_AUDIO_PRIORITY_NORMAL, AVSYS_AUDIO_PRIORITY_SOLO_WITH_TRANSITION_EFFECT, - AVSYS_AUDIO_PRIORITY_NORMAL, G_PARAM_READWRITE)); - - g_object_class_install_property ( gobject_class, PROP_AUDIO_HANDLE, - g_param_spec_pointer("audio-handle", "Avsystem handle", - "Avsystem audio handle", - G_PARAM_READWRITE)); - - g_object_class_install_property ( gobject_class, PROP_AUDIO_CALLBACK, - g_param_spec_pointer("audio-callback", "Avsystem callback", - "Avsystem audio callback", - G_PARAM_READWRITE)); - - g_object_class_install_property (gobject_class, PROP_AUDIO_FADEUPVOLUME, - g_param_spec_boolean ("fadeup", "Avsystem fadeup volume", - "Enable avsystem audio fadeup volume when pause to play", - DEFAULT_FADEUP_VOLUME, G_PARAM_READWRITE)); - - g_object_class_install_property (gobject_class ,PROP_AUDIO_MUTE, - g_param_spec_enum("mute", "Avsystem mute", - "Avsystem audio mute", - GST_AVSYS_AUDIO_SINK_MUTE, DEFAULT_AUDIO_MUTE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS )); - - g_object_class_install_property (gobject_class ,PROP_AUDIO_ROUTE_POLICY, - g_param_spec_enum("audio-route", "Audio Route Policy", - "Audio route policy of system", - GST_AVSYS_AUDIO_SINK_AUDIO_ROUTE, DEFAULT_AUDIO_ROUTE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS )); - - g_object_class_install_property (gobject_class ,PROP_AUDIO_USER_ROUTE, - g_param_spec_enum("user-route", "User Route Policy", - "User route policy", - GST_AVSYS_AUDIO_SINK_USER_ROUTE, DEFAULT_USER_ROUTE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS )); - - g_object_class_install_property (gobject_class ,PROP_AUDIO_LATENCY, - g_param_spec_enum("latency", "Audio Backend Latency", - "Audio backend latency", - GST_AVSYS_AUDIO_SINK_LATENCY_TYPE, DEFAULT_AUDIO_LATENCY, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS )); -} - -static void -gst_avsysaudiosink_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - GstAvsysAudioSink *sink = NULL; - int nvalue = 0; - gboolean nbool = FALSE; - - sink = GST_AVSYS_AUDIO_SINK (object); - - switch (prop_id) - { - case PROP_AUDIO_HANDLE: - sink->cbHandle = g_value_get_pointer(value); - break; - case PROP_AUDIO_CALLBACK: - sink->audio_stream_cb = g_value_get_pointer(value); - break; - - case PROP_AUDIO_VOLUME_TYPE: - nvalue = g_value_get_enum(value); - sink->volume_type = nvalue; - break; - - case PROP_AUDIO_PRIORITY: - nvalue = g_value_get_int(value); - sink->sound_priority = nvalue; - break; - - case PROP_AUDIO_MUTE: - nvalue = g_value_get_enum(value); - if(sink->audio_handle != (avsys_handle_t)-1) - { - if(AVSYS_SUCCESS(avsys_audio_set_mute_fadedown(sink->audio_handle))) - sink->mute = nvalue; - } - else - { - sink->mute = nvalue; - } - break; - case PROP_AUDIO_FADEUPVOLUME: - nbool = g_value_get_boolean(value); - sink->use_fadeup_volume = nbool; - break; - - case PROP_AUDIO_ROUTE_POLICY: - nvalue = g_value_get_enum(value); - sink->audio_route_policy = nvalue; - switch(sink->audio_route_policy) - { - case AVSYSAUDIOSINK_AUDIOROUTE_USE_EXTERNAL_SETTING: - GST_INFO_OBJECT(sink, "use external audio route setting"); - break; - default: - g_print("AVSYSAUDIOSINK :: Unknown audio route option %d\n", sink->audio_route_policy); - GST_ERROR_OBJECT(sink, "Unknown audio route option %d", sink->audio_route_policy); - break; - } - break; - case PROP_AUDIO_USER_ROUTE: - nvalue = g_value_get_enum(value); - sink->user_route_policy = nvalue; - break; - case PROP_AUDIO_LATENCY: - nvalue = g_value_get_enum(value); - sink->latency = nvalue; - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_avsysaudiosink_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec) -{ - GstAvsysAudioSink *sink = NULL; - - sink = GST_AVSYS_AUDIO_SINK (object); - - switch (prop_id) { - case PROP_AUDIO_VOLUME_TYPE: - g_value_set_enum(value, sink->volume_type); - break; - case PROP_AUDIO_PRIORITY: - g_value_set_int(value, sink->sound_priority); - break; - case PROP_AUDIO_MUTE: - g_value_set_enum(value, sink->mute); - break; - case PROP_AUDIO_FADEUPVOLUME: - g_value_set_boolean(value, sink->use_fadeup_volume); - break; - - case PROP_AUDIO_ROUTE_POLICY: - g_value_set_enum(value, sink->audio_route_policy); - break; - case PROP_AUDIO_USER_ROUTE: - g_value_set_enum(value, sink->user_route_policy); - break; - case PROP_AUDIO_LATENCY: - g_value_set_enum(value, sink->latency); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_avsysaudiosink_init (GstAvsysAudioSink * avsysaudiosink, GstAvsysAudioSinkClass * g_class) -{ - GST_DEBUG_OBJECT (avsysaudiosink, "initializing avsysaudiosink"); - - avsysaudiosink->audio_handle = (avsys_handle_t)-1; - avsysaudiosink->cached_caps = NULL; - avsysaudiosink->avsys_audio_lock = g_mutex_new (); - avsysaudiosink->avsys_audio_reset_lock = g_mutex_new (); - avsysaudiosink->volume_type = DEFAULT_VOLUME_TYPE; - avsysaudiosink->sound_priority = AVSYS_AUDIO_PRIORITY_NORMAL; - avsysaudiosink->mute = DEFAULT_AUDIO_MUTE; - avsysaudiosink->use_fadeup_volume = DEFAULT_FADEUP_VOLUME; - avsysaudiosink->latency = DEFAULT_AUDIO_LATENCY; - avsysaudiosink->audio_route_policy = DEFAULT_AUDIO_ROUTE; - avsysaudiosink->bytes_per_sample = 1; -#if defined (LPCM_DUMP_SUPPORT) - avsysaudiosink->dumpFp = NULL; -#endif - -} -#if 0 -static GstCaps * -gst_avsysaudiosink_getcaps (GstBaseSink * bsink) -{ - GstElementClass *element_class = NULL; - GstPadTemplate *pad_template = NULL; - GstAvsysAudioSink *sink = GST_AVSYS_AUDIO_SINK (bsink); - GstCaps *caps; - -// debug_fenter(); - - sink = GST_AVSYS_AUDIO_SINK (bsink); - if (sink->audio_handle == -1) - { - GST_DEBUG_OBJECT (sink, "avsystem audio not open, using template caps"); - return NULL; /* base class will get template caps for us */ - } - - if (sink->cached_caps) - { - GST_LOG_OBJECT (sink, "Returning cached caps"); - return gst_caps_ref (sink->cached_caps); - } - - element_class = GST_ELEMENT_GET_CLASS (sink); - pad_template = gst_element_class_get_pad_template (element_class, "sink"); - g_return_val_if_fail (pad_template != NULL, NULL); - - // todo : get supported format. - //caps = gst_avsysaudio_probe_supported_formats (GST_OBJECT (sink), sink->, - //gst_pad_template_get_caps (pad_template)); - - //if (caps) { - //sink->cached_caps = gst_caps_ref (caps); - //} - - GST_INFO_OBJECT (sink, "returning caps %" GST_PTR_FORMAT, caps); - - return caps; -} -#endif - -static gboolean -avsysaudiosink_parse_spec (GstAvsysAudioSink * avsys_audio, GstRingBufferSpec * spec) -{ - /* Check param */ - if (spec->type != GST_BUFTYPE_LINEAR || - spec->channels > 2 || spec->channels < 1 || - !(spec->format == GST_S8 || spec->format == GST_S16_LE) ) - return FALSE; - - switch(spec->format) - { - case GST_S8: - avsys_audio->audio_param.format = AVSYS_AUDIO_FORMAT_8BIT; - avsys_audio->bytes_per_sample = 1; - break; - case GST_S16_LE: - avsys_audio->audio_param.format = AVSYS_AUDIO_FORMAT_16BIT; - avsys_audio->bytes_per_sample = 2; - break; - default: - return FALSE; - } - - /// set audio parameter for avsys audio open - switch(avsys_audio->latency) - { - case AVSYSAUDIOSINK_LATENCY_LOW: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_OUTPUT_VIDEO; - break; - case AVSYSAUDIOSINK_LATENCY_MID: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_OUTPUT; - break; - case AVSYSAUDIOSINK_LATENCY_HIGH: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_OUTPUT_CLOCK; - break; - } - - - - avsys_audio->audio_param.priority = 0; - avsys_audio->audio_param.samplerate = spec->rate; - avsys_audio->audio_param.channels = spec->channels; - - if(spec->channels == 2) - avsys_audio->bytes_per_sample *= 2; - else if(spec->channels > 2) - { - GST_ERROR_OBJECT(avsys_audio,"Unsupported channel number %d", spec->channels); - return FALSE; - } - - /* set software volume table type */ - avsys_audio->audio_param.vol_type = avsys_audio->volume_type; - avsys_audio->audio_param.priority = avsys_audio->sound_priority; - avsys_audio->audio_param.handle_route = avsys_audio->user_route_policy; - - return TRUE; -} - -static gboolean -gst_avsysaudiosink_open (GstAudioSink * asink) -{ - return TRUE; -} - -static gboolean -gst_avsysaudiosink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) -{ - GstAvsysAudioSink *avsys_audio = NULL; - guint p_time = 0, b_time = 0; - - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - - // set avsys audio param - if (!avsysaudiosink_parse_spec (avsys_audio, spec)) - goto spec_parse; - - if (gst_avsysaudiosink_avsys_open(avsys_audio) == FALSE) - { - GST_ERROR_OBJECT(avsys_audio, "gst_avsysaudiosink_avsys_open() failed"); - return FALSE; - } - - - /* Ring buffer size */ - if (AVSYS_STATE_SUCCESS == - avsys_audio_get_period_buffer_time(avsys_audio->audio_handle, &p_time, &b_time)) - { - if(p_time == 0 || b_time == 0) - return FALSE; - - spec->latency_time = (guint64)p_time; - spec->buffer_time = (guint64)b_time; - } - else - { - return FALSE; - } - spec->segsize = avsys_audio->avsys_size; /* '/16' see avsys_audio_open */ - spec->segtotal = (b_time / p_time) + (((b_time % p_time)/p_time > 0.5) ? 1: 0); - //spec->segtotal+2; - - GST_WARNING_OBJECT (avsys_audio, "latency time %u, buffer time %u, seg total %u\n", - (unsigned int)(spec->latency_time/1000), (unsigned int)(spec->buffer_time/1000), spec->segtotal); - return TRUE; - -spec_parse: - { - GST_ELEMENT_ERROR (avsys_audio, RESOURCE, SETTINGS, (NULL), - ("Setting of swparams failed: " )); - return FALSE; - } -} - -static gboolean -gst_avsysaudiosink_unprepare (GstAudioSink * asink) -{ - GstAvsysAudioSink *avsys_audio = NULL; - gboolean result = TRUE; - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - - if(!gst_avsysaudiosink_avsys_close(avsys_audio)) - { - GST_ERROR_OBJECT(avsys_audio, "gst_avsysaudiosink_avsys_close() failed"); - result = FALSE; - } - - return result; -} - -static gboolean -gst_avsysaudiosink_close (GstAudioSink * asink) -{ - GstAvsysAudioSink *avsys_audio = NULL; - - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - gst_caps_replace (&avsys_audio->cached_caps, NULL); - - return TRUE; -} - - -/* - * Underrun and suspend recovery - */ - -static guint -gst_avsysaudiosink_write (GstAudioSink * asink, gpointer data, guint length) -{ - GstAvsysAudioSink *avsys_audio = NULL; - gint write_len = 0; - - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - GST_AVSYS_AUDIO_SINK_LOCK (asink); - - if(avsys_audio->audio_stream_cb == NULL) - { - write_len = avsys_audio_write(avsys_audio->audio_handle, data, length); - -#if defined (LPCM_DUMP_SUPPORT) - fwrite(data, 1, write_len, avsys_audio->dumpFp); //This is for original data (no volume convert) -#endif - if(write_len != length) - { - goto write_error; - } - - GST_AVSYS_AUDIO_SINK_UNLOCK (asink); - - return write_len; -write_error: - { - GST_AVSYS_AUDIO_SINK_UNLOCK (asink); - - if(AVSYS_FAIL(write_len)) - { - GST_ERROR_OBJECT(avsys_audio, "avsys_audio_write() failed with %d\n", write_len); - } - return length; /* skip one period */ - - } - } - else - { - gboolean result; - result = avsys_audio->audio_stream_cb(data, length, avsys_audio->cbHandle); - if(!result) - { - GST_ERROR_OBJECT(avsys_audio,"auido stream callback failed\n"); - } - GST_AVSYS_AUDIO_SINK_UNLOCK (asink); - return length; - } -} - -static guint -gst_avsysaudiosink_delay (GstAudioSink * asink) -{ - GstAvsysAudioSink *avsys_audio = NULL; - int delay = 0; - guint retValue = 0; - - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - GST_AVSYS_AUDIO_SINK_RESET_LOCK (asink); - if((int)avsys_audio->audio_handle != -1) { - if(AVSYS_STATE_SUCCESS == avsys_audio_delay(avsys_audio->audio_handle, &delay)) { - retValue = delay; - } - } - GST_AVSYS_AUDIO_SINK_RESET_UNLOCK (asink); - return retValue; -} - -static void -gst_avsysaudiosink_reset (GstAudioSink * asink) -{ - GstAvsysAudioSink *avsys_audio = NULL; - int avsys_result = AVSYS_STATE_SUCCESS; - - GST_AVSYS_AUDIO_SINK_LOCK (asink); - avsys_audio = GST_AVSYS_AUDIO_SINK (asink); - -#if defined(__REPLACE_RESET_WITH_CLOSE_AND_REOPEN__) - GST_AVSYS_AUDIO_SINK_RESET_LOCK (asink); - avsys_result = avsys_audio_close (avsys_audio->audio_handle); - if(AVSYS_FAIL(avsys_result)) - { - GST_ERROR_OBJECT (avsys_audio, "avsys_audio_close: internal error: "); - } - else - { - avsys_audio->audio_handle =(avsys_handle_t) -1; - } - GST_AVSYS_AUDIO_SINK_RESET_UNLOCK (asink); -#else - if(AVSYS_STATE_SUCCESS != avsys_audio_reset(avsys_audio->audio_handle)) - { - GST_ERROR_OBJECT (avsys_audio, "avsys-reset: internal error: "); - } -#endif - - GST_AVSYS_AUDIO_SINK_UNLOCK (asink); - - return; -} - - -static gboolean -gst_avsysaudiosink_avsys_open(GstAvsysAudioSink *avsys_audio) -{ - int avsys_result; - - GST_AVSYS_AUDIO_SINK_LOCK(avsys_audio); - if(avsys_audio->audio_handle == (avsys_handle_t)-1) { - - GST_LOG_OBJECT (avsys_audio, "avsys_audio_open() with user policy [%d] ", avsys_audio->user_route_policy); - - avsys_result = avsys_audio_open(&avsys_audio->audio_param, &avsys_audio->audio_handle, &avsys_audio->avsys_size); - if(avsys_result != AVSYS_STATE_SUCCESS) - { - GST_AVSYS_AUDIO_SINK_UNLOCK (avsys_audio); - avsysaudiosink_post_message(avsys_audio, avsys_result); - return FALSE; - } - - GST_LOG_OBJECT (avsys_audio, "Opened av system "); - - GST_AVSYS_AUDIO_SINK_UNLOCK (avsys_audio); - - } - else { - GST_WARNING_OBJECT(avsys_audio, "audio handle has already opened"); - GST_AVSYS_AUDIO_SINK_UNLOCK (avsys_audio); - return FALSE; - } -#if defined (LPCM_DUMP_SUPPORT) - if(avsys_audio->dumpFp == NULL) - { - avsys_audio->dumpFp = fopen("/root/dump.lpcm","w"); - } -#endif - return TRUE; -} - -static gboolean -gst_avsysaudiosink_avsys_close(GstAvsysAudioSink *avsys_audio) -{ - int avsys_result = AVSYS_STATE_SUCCESS; - - GST_AVSYS_AUDIO_SINK_LOCK (avsys_audio); - - if(avsys_audio->audio_handle != (avsys_handle_t)-1 ) - { - avsys_result = avsys_audio_close(avsys_audio->audio_handle); - - if (AVSYS_FAIL(avsys_result)) - { - GST_ERROR_OBJECT(avsys_audio, - "avsys_audio_close() failed with 0x%x", avsys_result); - GST_AVSYS_AUDIO_SINK_UNLOCK (avsys_audio); - return FALSE; - } - else - { - avsys_audio->audio_handle = (avsys_handle_t) -1; - GST_INFO_OBJECT(avsys_audio, "avsys_audio_close() success"); - } - - GST_LOG_OBJECT (avsys_audio, "Closed av system "); - } - else - { - GST_WARNING_OBJECT(avsys_audio, "audio handle has already closed"); - } - - GST_AVSYS_AUDIO_SINK_UNLOCK (avsys_audio); -#if defined (LPCM_DUMP_SUPPORT) - if(avsys_audio->dumpFp != NULL) - { - fclose(avsys_audio->dumpFp); - avsys_audio->dumpFp = NULL; - } -#endif - return TRUE; -} - -static GstStateChangeReturn -gst_avsyssudiosink_change_state (GstElement *element, GstStateChange transition) -{ - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - GstAvsysAudioSink *avsys_audio = GST_AVSYS_AUDIO_SINK (element); - - int avsys_result = AVSYS_STATE_SUCCESS; - - switch (transition) - { - case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: -#if defined(_ALSA_DAPM_) - switch(avsys_audio->audio_route_policy) - { - case AVSYSAUDIOSINK_AUDIOROUTE_USE_EXTERNAL_SETTING: - GST_INFO_OBJECT(avsys_audio, "audio route uses external setting"); - break; - default: - GST_ERROR_OBJECT(avsys_audio, "Unknown audio route option %d\n", avsys_audio->audio_route_policy); - break; - } -#endif -#if defined(__REPLACE_RESET_WITH_CLOSE_AND_REOPEN__) - if(avsys_audio->audio_handle == (avsys_handle_t)-1) - { - avsys_result = avsys_audio_open(&avsys_audio->audio_param, &avsys_audio->audio_handle, &avsys_audio->avsys_size); - if(AVSYS_FAIL(avsys_result)) - { - GST_ERROR_OBJECT (avsys_audio, "avsys_audio_open: internal error: "); - return GST_STATE_CHANGE_FAILURE; - } - } -#endif - - if(avsys_audio->use_fadeup_volume) { - GST_INFO_OBJECT(avsys_audio, "Set fadeup volume"); - avsys_audio_set_volume_fadeup(avsys_audio->audio_handle); - } - - if(AVSYS_STATE_SUCCESS != avsys_audio_set_mute(avsys_audio->audio_handle, CONVERT_MUTE_VALUE(avsys_audio->mute))) - { - GST_ERROR_OBJECT(avsys_audio, "Set mute failed %d", CONVERT_MUTE_VALUE(avsys_audio->mute)); - } - break; - default: - break; - } - ret = GST_ELEMENT_CLASS(parent_class)->change_state (element, transition); - if (ret == GST_STATE_CHANGE_FAILURE) - { - return ret; - } - switch (transition) - { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - return ret; -} - - -static gboolean -avsysaudiosink_post_message(GstAvsysAudioSink* self,int errorcode) -{ - GST_DEBUG("avsysaudiosink_post_message\n"); - gboolean ret = TRUE; - GstMessage *Msg = NULL; - GQuark domain; - gboolean status = FALSE; - GError *error = NULL; - gint error_code; - /* - if(errorcode>0) - error_code = errorcode; - else - error_code = GST_STREAM_ERROR_TYPE_NOT_FOUND; */ - error_code = GST_RESOURCE_ERROR_FAILED; - domain = gst_resource_error_quark(); - error = g_error_new (domain, error_code, "AVSYSAUDIOSINK_RESOURCE_ERROR"); - Msg = gst_message_new_error(GST_ELEMENT(self), error, "AVSYSAUDIOSINK_ERROR"); - status = gst_element_post_message (GST_ELEMENT(self), Msg); - if (status == FALSE) - { - GST_ERROR("Error in posting message on the bus ...\n"); - ret = FALSE; - } - - return ret; -} diff --git a/avsystem/src/gstavsysaudiosink.h b/avsystem/src/gstavsysaudiosink.h deleted file mode 100644 index 04d5dce..0000000 --- a/avsystem/src/gstavsysaudiosink.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - - -#ifndef __GST_AVSYSAUDIOSINK_H__ -#define __GST_AVSYSAUDIOSINK_H__ - -#include -#include - -#include - -G_BEGIN_DECLS - -#define GST_TYPE_AVSYS_AUDIO_SINK (gst_avsysaudiosink_get_type()) -#define GST_AVSYS_AUDIO_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVSYS_AUDIO_SINK,GstAvsysAudioSink)) -#define GST_AVSYS_AUDIO_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVSYS_AUDIO_SINK,GstAvsysAudioSinkClass)) -#define GST_IS_AVSYS_AUDIO_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVSYS_AUDIO_SINK)) -#define GST_IS_AVSYS_AUDIO_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVSYS_AUDIO_SINK)) -//#define GST_AVSYS_AUDIO_SINK_CAST(obj) ((GstAvsysAudioSink *) (obj)) - -typedef struct _GstAvsysAudioSink GstAvsysAudioSink; -typedef struct _GstAvsysAudioSinkClass GstAvsysAudioSinkClass; - -#define GST_AVSYS_AUDIO_SINK_GET_LOCK(obj) (GST_AVSYS_AUDIO_SINK (obj)->avsys_audio_lock) -#define GST_AVSYS_AUDIO_SINK_LOCK(obj) (g_mutex_lock (GST_AVSYS_AUDIO_SINK_GET_LOCK(obj))) -#define GST_AVSYS_AUDIO_SINK_UNLOCK(obj) (g_mutex_unlock (GST_AVSYS_AUDIO_SINK_GET_LOCK(obj))) - -#define GST_AVSYS_AUDIO_SINK_GET_RESET_LOCK(obj) (GST_AVSYS_AUDIO_SINK (obj)->avsys_audio_reset_lock) -#define GST_AVSYS_AUDIO_SINK_RESET_LOCK(obj) (g_mutex_lock (GST_AVSYS_AUDIO_SINK_GET_RESET_LOCK(obj))) -#define GST_AVSYS_AUDIO_SINK_RESET_UNLOCK(obj) (g_mutex_unlock (GST_AVSYS_AUDIO_SINK_GET_RESET_LOCK(obj))) - - -typedef enum { - AVSYSAUDIOSINK_AUDIO_UNMUTE = 0, - AVSYSAUDIOSINK_AUDIO_MUTE, - AVSYSAUDIOSINK_AUDIO_MUTE_WITH_FADEDOWN_EFFECT, -}GstAvsysAudioSinkAudioMute; - - -typedef enum { - AVSYSAUDIOSINK_AUDIOROUTE_USE_EXTERNAL_SETTING = -1, - AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_NORMAL, - AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_ALERT, - AVSYSAUDIOSINK_AUDIOROUTE_PLAYBACK_HEADSET_ONLY -}GstAvsysAudioSinkAudioRoutePolicy; - - -typedef enum { - AVSYSAUDIOSINK_USERROUTE_AUTO = 0, - AVSYSAUDIOSINK_USERROUTE_PHONE -}GstAvsysAudioSinkUserRoutePolicy; - -typedef enum { - AVSYSAUDIOSINK_LATENCY_LOW = 0, - AVSYSAUDIOSINK_LATENCY_MID, - AVSYSAUDIOSINK_LATENCY_HIGH, -}GstAvsysAudioSinkLatency; - -#define GST_AVSYS_AUDIO_SINK_USER_ROUTE (gst_avsysaudiosink_user_route_get_type ()) -#define GST_AVSYS_AUDIO_SINK_AUDIO_ROUTE (gst_avsysaudiosink_audio_route_get_type ()) -#define GST_AVSYS_AUDIO_SINK_VOLUME_TABLE (gst_avsysaudiosink_volume_table_get_type ()) -#define GST_AVSYS_AUDIO_SINK_VOLUME_TYPE (gst_avsysaudiosink_volume_table_get_type ()) -#define GST_AVSYS_AUDIO_SINK_MUTE (gst_avsysaudiosink_audio_mute_get_type()) -#define GST_AVSYS_AUDIO_SINK_LATENCY_TYPE (gst_avsysaudiosink_latency_get_type ()) - -//this define if for debugging -//#define LPCM_DUMP_SUPPORT -struct _GstAvsysAudioSink { - GstAudioSink sink; - - avsys_handle_t audio_handle; - avsys_audio_param_t audio_param; - gint avsys_size; - gint mute; - gint use_fadeup_volume; - gint audio_route_policy; - gint user_route_policy; - gint latency; - - GstCaps *cached_caps; - - GMutex *avsys_audio_lock; - GMutex *avsys_audio_reset_lock; - gint volume_type; - gint sound_priority; - gint bytes_per_sample; - - gpointer cbHandle; - gboolean (*audio_stream_cb) (void *stream, int stream_size, void *user_param); -}; - - -struct _GstAvsysAudioSinkClass { - GstAudioSinkClass parent_class; -}; - -GType gst_avsysaudiosink_get_type (void); -GType gst_avsysaudiosink_audio_route_get_type (void); -GType gst_avsysaudiosink_volume_table_get_type (void); -GType gst_avsysaudiosink_audio_mute_get_type(void); -GType gst_avsysaudiosink_latency_get_type (void); - -G_END_DECLS - -#endif /* __GST_AVSYSAUDIOSINK_H__ */ diff --git a/avsystem/src/gstavsysaudiosrc.c b/avsystem/src/gstavsysaudiosrc.c deleted file mode 100644 index 3a4fdc0..0000000 --- a/avsystem/src/gstavsysaudiosrc.c +++ /dev/null @@ -1,1005 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include -#include -#include -#include -#include - -#include -#include -//#include - -#include /*gettimeofday*/ -#include - -#include "gstavsysaudiosrc.h" - -/** - * Define for Release - * - * _ENABLE_FAKE_READ: enable fake read instead of avsys_audio_read(). (default: 0) - */ -#define _ENABLE_FAKE_READ 0 -//#define USE_GPT8 - - -#define _MIN_RATE 8000 -#define _MAX_RATE 48000 -#define _MIN_CHANNEL 1 -#define _MAX_CHANNEL 2 - - -#define DEFAULT_GPT8_FREQ 26 - -#if _ENABLE_FAKE_READ -static long g_delay_time; -#endif - -#define DEFAULT_MEDIACALL_MODE AVSYS_AUDIO_ECHO_MODE_NONE -#define DEFAULT_AUDIO_LATENCY AVSYSAUDIOSRC_LATENCY_MID - -GST_DEBUG_CATEGORY_EXTERN (avsystem_src_debug); -#define GST_CAT_DEFAULT avsystem_src_debug - -/* elementfactory information */ -static const GstElementDetails gst_avsysaudiosrc_details = - GST_ELEMENT_DETAILS ("AV system source", - "Source/Audio", - "Read from a AV system audio in", - "Samsung Electronics co., ltd."); - - -enum { - PROP_0, -#if 0 -#if !defined(I386_SIMULATOR) - PROP_AUDIO_MEDIA_CALL, -#endif -#endif - PROP_AUDIO_LATENCY, -}; - -enum { - CAPTURE_UNCORK = 0, - CAPTURE_CORK, -}; - -GType -gst_avsysaudiosrc_audio_latency_get_type (void) -{ - static GType capture_audio_latency_type = 0; - static const GEnumValue capture_audio_latency[] = { - {AVSYSAUDIOSRC_LATENCY_LOW, "Set capture latency as low", "low"}, - {AVSYSAUDIOSRC_LATENCY_MID, "Set capture latency as mid", "mid"}, - {AVSYSAUDIOSRC_LATENCY_HIGH, "Set capture latency as high", "high"}, - {0, NULL, NULL}, - }; - - if (!capture_audio_latency_type) { - capture_audio_latency_type = - g_enum_register_static ("GstAvsysAudioSrcAudioLatency", capture_audio_latency); - } - return capture_audio_latency_type; -} - - -GST_BOILERPLATE (GstAvsysAudioSrc, gst_avsysaudiosrc, GstAudioSrc, GST_TYPE_AUDIO_SRC); - -#if _ENABLE_FAKE_READ -static unsigned long fake_delay (unsigned long usec); -#endif - -static guint gst_avsysaudiosrc_delay (GstAudioSrc *asrc); - -static void gst_avsysaudiosrc_finalize (GObject * object); -static void gst_avsysaudiosrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_avsysaudiosrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); - -static GstCaps* gst_avsysaudiosrc_getcaps (GstBaseSrc * bsrc); - -static gboolean gst_avsysaudiosrc_open (GstAudioSrc * asrc); -static gboolean gst_avsysaudiosrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec); -static gboolean gst_avsysaudiosrc_unprepare (GstAudioSrc * asrc); -static gboolean gst_avsysaudiosrc_close (GstAudioSrc * asrc); -static guint gst_avsysaudiosrc_read (GstAudioSrc * asrc, gpointer data, guint length); -static void gst_avsysaudiosrc_reset (GstAudioSrc * asrc); -static gboolean gst_avsysaudiosrc_avsys_close (GstAvsysAudioSrc *src); -static gboolean gst_avsysaudiosrc_avsys_open (GstAvsysAudioSrc *src); -static gboolean gst_avsysaudiosrc_avsys_cork (GstAvsysAudioSrc *avsysaudiosrc, int cork); -static gboolean gst_avsysaudiosrc_avsys_start (GstAvsysAudioSrc *src); -static gboolean gst_avsysaudiosrc_avsys_stop (GstAvsysAudioSrc *src); -#if defined(_USE_CAPS_) -static GstCaps *gst_avsysaudiosrc_detect_rates (GstObject * obj, avsys_pcm_hw_params_t * hw_params, GstCaps * in_caps); -static GstCaps *gst_avsysaudiosrc_detect_channels (GstObject * obj,avsys_pcm_hw_params_t * hw_params, GstCaps * in_caps); -static GstCaps *gst_avsysaudiosrc_detect_formats (GstObject * obj,avsys_pcm_hw_params_t * hw_params, GstCaps * in_caps); -static GstCaps *gst_avsysaudiosrc_probe_supported_formats (GstObject * obj, avsys_handle_t handle, const GstCaps * template_caps); -#endif -static GstStateChangeReturn gst_avsys_src_change_state (GstElement * element, GstStateChange transition) ; - - - -/* AvsysAudioSrc signals and args */ -enum { - LAST_SIGNAL -}; - -# define AVSYS_AUDIO_SRC_FACTORY_ENDIANNESS "LITTLE_ENDIAN" - -static GstStaticPadTemplate avsysaudiosrc_src_factory = - GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ( - "audio/x-raw-int, " - "endianness = (int) { " AVSYS_AUDIO_SRC_FACTORY_ENDIANNESS " }, " - "signed = (boolean) { TRUE }, " - "width = (int) 16, " - "depth = (int) 16, " - "rate = (int) [ 8000, 48000 ], " - "channels = (int) [ 1, 2 ]; " - "audio/x-raw-int, " - "signed = (boolean) { FALSE }, " - "width = (int) 8, " - "depth = (int) 8, " - "rate = (int) [ 8000, 48000 ], " - "channels = (int) [ 1, 2 ]") - ); - -static inline guint _time_to_sample(GstAvsysAudioSrc *avsyssrc, GstClockTime diff) -{ - guint result = 0; - // sample rate : asrc->audio_param.samplerate - result =(GST_TIME_AS_USECONDS(diff) * avsyssrc->audio_param.samplerate)/1000000; - return result; -} - - -static void -gst_avsysaudiosrc_finalize (GObject * object) -{ - GstAvsysAudioSrc *src = NULL; - - - src = GST_AVSYS_AUDIO_SRC (object); - g_mutex_free (src->avsysaudio_lock); - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static void -gst_avsysaudiosrc_base_init (gpointer g_class) -{ - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - - gst_element_class_set_details (element_class, &gst_avsysaudiosrc_details); - - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&avsysaudiosrc_src_factory)); -} - -static void -gst_avsysaudiosrc_class_init (GstAvsysAudioSrcClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSrcClass *gstbasesrc_class; - GstBaseAudioSrcClass *gstbaseaudiosrc_class; - GstAudioSrcClass *gstaudiosrc_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - gstbasesrc_class = (GstBaseSrcClass *) klass; - gstbaseaudiosrc_class = (GstBaseAudioSrcClass *) klass; - gstaudiosrc_class = (GstAudioSrcClass *) klass; - - gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_avsys_src_change_state); - - parent_class = g_type_class_peek_parent (klass); - - gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_finalize); - gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_get_property); - gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_set_property); -#if defined(_USE_CAPS_) - gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_getcaps); -#endif - gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_open); - gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_prepare); - gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_unprepare); - gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_close); - gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_read); - gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_delay); - gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_avsysaudiosrc_reset); - - g_object_class_install_property (gobject_class ,PROP_AUDIO_LATENCY, - g_param_spec_enum("latency", "Audio Latency", - "Audio latency", - GST_AVSYS_AUDIO_SRC_LATENCY, DEFAULT_AUDIO_LATENCY, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS )); -} - - - -/** - *@note useful range 0 - 1000000 microsecond - */ -#if _ENABLE_FAKE_READ -static unsigned long -fake_delay (unsigned long usec) -{ - struct timeval s_time; - struct timeval c_time; - long s_sec = 0L; - long s_usec = 0L; - long c_sec = 0L; - long c_usec = 0L; - long t_sec = 0L; - long t_usec = 0L; - unsigned long total_usec = 0UL; - unsigned long t = 0UL; - - if (usec == 0UL) { - usleep (0); - return 0; - } - - /*get start time*/ - if (gettimeofday ((struct timeval *)&s_time, NULL) == 0) { - s_sec = s_time.tv_sec; - s_usec = s_time.tv_usec; - } else { - return 0; - } - - for (;;) { - /*get current time*/ - if (gettimeofday ((struct timeval *)&c_time, NULL) == 0) { - c_sec = c_time.tv_sec; - c_usec = c_time.tv_usec; - } else { - return 0; - } - - /*get elasped sec*/ - t_sec = c_sec - s_sec; - - /*get elapsed usec*/ - if ((s_usec) > (c_usec)) { - t_usec = 1000000L - (s_usec) + (c_usec); - t_sec--; - } else { - t_usec = (c_usec) - (s_usec); - } - - /*get total elapsed time*/ - total_usec = (t_sec * 1000000UL) + t_usec; - - t = usec - total_usec; - - if (total_usec >= usec) { - break; - } else { - if (t > 10000UL) { - /*this function does not work in precision*/ - usleep (1); - } - } - } - - return total_usec; -} -#endif - -static guint -gst_avsysaudiosrc_delay (GstAudioSrc *asrc) -{ - GstAvsysAudioSrc *avsys_audio = NULL; - int delay; - guint retValue = 0; - - avsys_audio = GST_AVSYS_AUDIO_SRC (asrc); - if(AVSYS_STATE_SUCCESS == avsys_audio_delay(avsys_audio->audio_handle, &delay)) - retValue = delay; - - return retValue; -} - - - -static void -gst_avsysaudiosrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) -{ - GstAvsysAudioSrc *src = NULL; -// gint getvalue = 0; - src = GST_AVSYS_AUDIO_SRC (object); - - if (src->cached_caps == NULL) - { - switch (prop_id) - { - case PROP_AUDIO_LATENCY: - src->latency = g_value_get_enum(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } - } -} - -static void -gst_avsysaudiosrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) -{ - GstAvsysAudioSrc *src; - - src = GST_AVSYS_AUDIO_SRC (object); - - switch (prop_id) - { - case PROP_AUDIO_LATENCY: - g_value_set_enum(value, src->latency); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_avsysaudiosrc_init (GstAvsysAudioSrc * avsysaudiosrc, GstAvsysAudioSrcClass * g_class) -{ - GST_DEBUG_OBJECT (avsysaudiosrc, "initializing"); - - avsysaudiosrc->cached_caps = NULL; - avsysaudiosrc->audio_handle = (avsys_handle_t)-1; - avsysaudiosrc->buffer_size = 0; - avsysaudiosrc->avsysaudio_lock = g_mutex_new (); - avsysaudiosrc->latency = DEFAULT_AUDIO_LATENCY; -} - -#if defined(_USE_CAPS_) -static GstCaps * -gst_avsysaudiosrc_getcaps (GstBaseSrc * bsrc) -{ - GstElementClass *element_class; - GstPadTemplate *pad_template; - GstAvsysAudioSrc *src; - GstCaps *caps = NULL; - - src = GST_AVSYS_AUDIO_SRC (bsrc); - - if(src->audio_handle==(avsys_handle_t)-1){ - GST_DEBUG_OBJECT(src,"device not open, using template caps"); - return NULL; - } - - if (src->cached_caps) - { - GST_LOG_OBJECT (src, "Returning cached caps"); - return gst_caps_ref(src->cached_caps); - } - element_class = GST_ELEMENT_GET_CLASS (src); - pad_template = gst_element_class_get_pad_template (element_class, "src"); - g_return_val_if_fail (pad_template != NULL, NULL); - - caps = gst_avsysaudiosrc_probe_supported_formats (GST_OBJECT (src), src->audio_handle, - gst_pad_template_get_caps (pad_template)); - - if (caps) { - src->cached_caps = gst_caps_ref (caps); - } - - GST_INFO_OBJECT (src, "returning caps %" GST_PTR_FORMAT, caps); - - return caps; -} -#endif -static gboolean -gst_avsysaudiosrc_open (GstAudioSrc * asrc) -{ - return TRUE; -} - -static gboolean -avsysaudiosrc_parse_spec (GstAvsysAudioSrc *avsys_audio, GstRingBufferSpec * spec) -{ - /* Check param */ - if (spec->type != GST_BUFTYPE_LINEAR || - spec->channels > 2 || spec->channels < 1 || - !(spec->format == GST_S8 || spec->format == GST_S16_LE) ) - return FALSE; - - switch(spec->format) - { - case GST_S8: - avsys_audio->audio_param.format = AVSYS_AUDIO_FORMAT_8BIT; - avsys_audio->bytes_per_sample = 1; - break; - case GST_S16_LE: - avsys_audio->audio_param.format = AVSYS_AUDIO_FORMAT_16BIT; - avsys_audio->bytes_per_sample = 2; - break; - default: - GST_DEBUG_OBJECT(avsys_audio, "Only support S8, S16LE format"); - return FALSE; - } - - // set audio parameter for avsys audio open - - switch(avsys_audio->latency) - { - case AVSYSAUDIOSRC_LATENCY_LOW: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_INPUT_LOW_LATENCY; - break; - case AVSYSAUDIOSRC_LATENCY_MID: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_INPUT; - break; - case AVSYSAUDIOSRC_LATENCY_HIGH: - avsys_audio->audio_param.mode = AVSYS_AUDIO_MODE_INPUT_HIGH_LATENCY; - break; - default: - break; - } - - avsys_audio->audio_param.priority = 0; - avsys_audio->audio_param.samplerate = spec->rate; - avsys_audio->audio_param.channels = spec->channels; - - if(spec->channels == 2) - avsys_audio->bytes_per_sample *= 2; - else if(spec->channels > 2) - { - GST_ERROR_OBJECT(avsys_audio,"Unsupported channel number %d", spec->channels); - return FALSE; - } - - return TRUE; -} - -static gboolean -gst_avsysaudiosrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) -{ - GstAvsysAudioSrc *avsysaudiosrc = NULL; - guint p_time = 0, b_time = 0; - - avsysaudiosrc = GST_AVSYS_AUDIO_SRC (asrc); - - if (!avsysaudiosrc_parse_spec(avsysaudiosrc, spec)) - { - GST_ERROR("avsysaudiosrc_parse_spec failed"); - return FALSE; - } - - /*open avsys audio*/ - if (!gst_avsysaudiosrc_avsys_open (avsysaudiosrc)) - { - GST_ERROR("gst_avsysaudiosrc_avsys_open failed"); - return FALSE; - } - - /* Ring buffer size */ - if (AVSYS_STATE_SUCCESS == - avsys_audio_get_period_buffer_time(avsysaudiosrc->audio_handle, &p_time, &b_time)) - { - if(p_time == 0 || b_time == 0) - return FALSE; - - spec->latency_time = (guint64)p_time; - spec->buffer_time = (guint64)b_time; - } - else - { - return FALSE; - } - spec->segsize = avsysaudiosrc->buffer_size; - spec->segtotal = (b_time / p_time) + (((b_time % p_time)/p_time > 0.5) ? 1: 0); - spec->segtotal += 2; - - GST_INFO_OBJECT(avsysaudiosrc, "audio buffer spec : latency_time(%llu), buffer_time(%llu), segtotal(%d), segsize(%d)\n", - spec->latency_time, spec->buffer_time, spec->segtotal, spec->segsize); - - - return TRUE; -} - -static gboolean -gst_avsysaudiosrc_unprepare (GstAudioSrc * asrc) -{ - GstAvsysAudioSrc *avsysaudiosrc = NULL; - gboolean ret = TRUE; - - avsysaudiosrc = GST_AVSYS_AUDIO_SRC (asrc); - - /*close*/ - GST_AVSYS_AUDIO_SRC_LOCK (avsysaudiosrc); - - if(!gst_avsysaudiosrc_avsys_close(avsysaudiosrc)) - { - GST_ERROR_OBJECT(avsysaudiosrc, "gst_avsysaudiosrc_avsys_close failed"); - ret = FALSE; - } - GST_AVSYS_AUDIO_SRC_UNLOCK (asrc); - - - - return ret; -} - -static gboolean -gst_avsysaudiosrc_close (GstAudioSrc * asrc) -{ - GstAvsysAudioSrc *avsysaudiosrc = NULL; - - avsysaudiosrc = GST_AVSYS_AUDIO_SRC (asrc); - gst_caps_replace (&avsysaudiosrc->cached_caps, NULL); - - return TRUE; -} - -static void -gst_avsysaudiosrc_reset (GstAudioSrc * asrc) -{ - GstAvsysAudioSrc *avsys_audio = NULL; - - avsys_audio = GST_AVSYS_AUDIO_SRC (asrc); - GST_AVSYS_AUDIO_SRC_LOCK (asrc); - - if(AVSYS_STATE_SUCCESS != avsys_audio_reset(avsys_audio->audio_handle)) - { - GST_ERROR_OBJECT (avsys_audio, "avsys-reset: internal error: "); - } - - GST_AVSYS_AUDIO_SRC_UNLOCK (asrc); - - return; -} - - -static GstStateChangeReturn -gst_avsys_src_change_state (GstElement * element, GstStateChange transition) -{ - GstAvsysAudioSrc *avsys = NULL; - GstStateChangeReturn ret ; - - avsys = GST_AVSYS_AUDIO_SRC (element); - GST_DEBUG("gst_avsys_src_change_state"); - - switch (transition) - { - case GST_STATE_CHANGE_NULL_TO_READY: - { - GST_DEBUG ("GST_STATE_CHANGE_NULL_TO_READY\n") ; - break ; - } - case GST_STATE_CHANGE_READY_TO_PAUSED: - { - GST_DEBUG ("GST_STATE_CHANGE_READY_TO_PAUSED\n") ; - break ; - } - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - { - GST_DEBUG ("GST_STATE_CHANGE_PAUSED_TO_PLAYING\n") ; - /* Capture Start */ - if (!gst_avsysaudiosrc_avsys_start (avsys)) { - GST_ERROR("gst_avsysaudiosrc_avsys_start failed"); - } - break ; - } - default: - break ; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - GST_DEBUG ("After parent_class->change_state...\n") ; - switch (transition) - { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - { - GST_DEBUG ("GST_STATE_CHANGE_PLAYING_TO_PAUSED\n") ; - /* Capture Stop */ - if (!gst_avsysaudiosrc_avsys_stop (avsys)) { - GST_ERROR("gst_avsysaudiosrc_avsys_stop failed"); - } - break ; - } - case GST_STATE_CHANGE_PAUSED_TO_READY: - { - GST_DEBUG ("GST_STATE_CHANGE_PAUSED_TO_READY\n") ; - break ; - } - case GST_STATE_CHANGE_READY_TO_NULL: - { - GST_DEBUG ("GST_STATE_CHANGE_READY_TO_NULL\n") ; - break ; - } - default: - break ; - } - - return ret; -} - - - -static guint -gst_avsysaudiosrc_read (GstAudioSrc * asrc, gpointer data, guint length) -{ - GstAvsysAudioSrc *avsysaudiosrc = NULL; - gint readed = 0; - gpointer ptr = NULL; - guint used_length = 0; - - avsysaudiosrc = GST_AVSYS_AUDIO_SRC (asrc); - - ptr = data; - - GST_AVSYS_AUDIO_SRC_LOCK (avsysaudiosrc); -#if _ENABLE_FAKE_READ - readed = avsysaudiosrc->buffer_size; - memset (ptr, 10, avsysaudiosrc->buffer_size); /*maybe can't hear*/ - fake_delay (1000000UL / g_delay_time); -#else - ptr = data; - readed = avsys_audio_read (avsysaudiosrc->audio_handle, ptr, length); - if (readed < 0) - goto _READ_ERROR; -#endif //_ENABLE_FAKE_READ - GST_AVSYS_AUDIO_SRC_UNLOCK (avsysaudiosrc); - - return readed; - -_READ_ERROR: - { - GST_AVSYS_AUDIO_SRC_UNLOCK (asrc); - return length; /* skip one period */ - } -} - -static gboolean -gst_avsysaudiosrc_avsys_cork (GstAvsysAudioSrc *avsysaudiosrc, int cork) -{ - int avsys_result = avsys_audio_cork (avsysaudiosrc->audio_handle, cork); - if (avsys_result != AVSYS_STATE_SUCCESS) { - GST_ERROR_OBJECT(avsysaudiosrc, "avsys_audio_cork() error. [0x%x]\n", avsys_result); - return FALSE; - } - return TRUE; -} - -static gboolean -gst_avsysaudiosrc_avsys_start (GstAvsysAudioSrc *avsysaudiosrc) -{ - gboolean result; - - GST_AVSYS_AUDIO_SRC_LOCK (avsysaudiosrc); - result = gst_avsysaudiosrc_avsys_cork(avsysaudiosrc, CAPTURE_UNCORK); - GST_AVSYS_AUDIO_SRC_UNLOCK (avsysaudiosrc); - - return result; -} - -static gboolean -gst_avsysaudiosrc_avsys_stop (GstAvsysAudioSrc *avsysaudiosrc) -{ - gboolean result; - - GST_AVSYS_AUDIO_SRC_LOCK (avsysaudiosrc); - result = gst_avsysaudiosrc_avsys_cork(avsysaudiosrc, CAPTURE_CORK); - GST_AVSYS_AUDIO_SRC_UNLOCK (avsysaudiosrc); - - return result; -} - -static gboolean -gst_avsysaudiosrc_avsys_open (GstAvsysAudioSrc *avsysaudiosrc) -{ - int avsys_result = 0; - - GST_AVSYS_AUDIO_SRC_LOCK (avsysaudiosrc); - - avsys_result = avsys_audio_open(&avsysaudiosrc->audio_param, &avsysaudiosrc->audio_handle, &avsysaudiosrc->buffer_size); - - if (avsys_result != AVSYS_STATE_SUCCESS) { - GST_ERROR_OBJECT(avsysaudiosrc, "avsys_audio_open() error. [0x%x]\n", avsys_result); - GST_AVSYS_AUDIO_SRC_UNLOCK (avsysaudiosrc); - return FALSE; - } - -#if _ENABLE_FAKE_READ - g_delay_time = (unsigned long)((avsysaudiosrc->samplerate * (avsysaudiosrc->format / 8) * avsysaudiosrc->channels) / avsysaudiosrc->buffer_size); -#endif - - GST_AVSYS_AUDIO_SRC_UNLOCK (avsysaudiosrc); - return TRUE; -} - -static gboolean -gst_avsysaudiosrc_avsys_close(GstAvsysAudioSrc *src) -{ - int ret; - - if (src->audio_handle != (avsys_handle_t)-1) - { - /*close avsys audio*/ - ret = avsys_audio_close (src->audio_handle); - if (AVSYS_FAIL(ret)) - { - GST_ERROR_OBJECT(src, "avsys_audio_close() error 0x%x", ret); - return FALSE; - } - src->audio_handle = (avsys_handle_t)-1; - GST_DEBUG_OBJECT(src, "AVsys audio handle closed"); - } - else - { - GST_WARNING_OBJECT(src,"avsys audio handle has already closed"); - } - - return TRUE; -} -#if defined(_USE_CAPS_) -static GstCaps * -gst_avsysaudiosrc_detect_rates(GstObject *obj, - avsys_pcm_hw_params_t *hw_params, GstCaps *in_caps) -{ - GstCaps *caps; - guint min, max; - gint err, dir, min_rate, max_rate, i; - - GST_LOG_OBJECT(obj, "probing sample rates ..."); - - if ((err = avsys_pcm_hw_params_get_rate_min(hw_params, &min, &dir)) < 0) - goto min_rate_err; - - if ((err = avsys_pcm_hw_params_get_rate_max(hw_params, &max, &dir)) < 0) - goto max_rate_err; - - min_rate = min; - max_rate = max; - - if (min_rate < _MIN_RATE) - min_rate = _MIN_RATE; /* random 'sensible minimum' */ - - if (max_rate <= 0) - max_rate = _MAX_RATE; /* or maybe just use 192400 or so? */ - else if (max_rate > 0 && max_rate < _MIN_RATE) - max_rate = MAX (_MIN_RATE, min_rate); - - GST_DEBUG_OBJECT(obj, "Min. rate = %u (%d)", min_rate, min); - GST_DEBUG_OBJECT(obj, "Max. rate = %u (%d)", max_rate, max); - - caps = gst_caps_make_writable(in_caps); - - for (i = 0; i < gst_caps_get_size(caps); ++i) - { - GstStructure *s; - - s = gst_caps_get_structure(caps, i); - if (min_rate == max_rate) - { - gst_structure_set(s, "rate", G_TYPE_INT, min_rate, NULL); - } - else - { - gst_structure_set(s, "rate", GST_TYPE_INT_RANGE, min_rate, - max_rate, NULL); - } - } - - return caps; - /* ERRORS */ - min_rate_err: - { - GST_ERROR_OBJECT(obj, "failed to query minimum sample rate"); - gst_caps_unref(in_caps); - return NULL; - } - max_rate_err: - { - GST_ERROR_OBJECT(obj, "failed to query maximum sample rate"); - gst_caps_unref(in_caps); - return NULL; - } -} - - -static GstCaps * -gst_avsysaudiosrc_detect_formats(GstObject * obj, - avsys_pcm_hw_params_t * hw_params, GstCaps * in_caps) -{ - avsys_pcm_format_mask_t *mask; - GstStructure *s; - GstCaps *caps; - gint i; - - avsys_pcm_format_mask_malloc(&mask); - avsys_pcm_hw_params_get_format_mask(hw_params, mask); - - caps = gst_caps_new_empty(); - - for (i = 0; i < gst_caps_get_size(in_caps); ++i) - { - GstStructure *scopy; - //gint w; - gint width = 0, depth = 0; - gint sndformat; - - s = gst_caps_get_structure(in_caps, i); - if (!gst_structure_has_name(s, "audio/x-raw-int")) - { - GST_WARNING_OBJECT(obj, "skipping non-int format"); - continue; - } - if (!gst_structure_get_int(s, "width", &width) - || !gst_structure_get_int(s, "depth", &depth)) - continue; - - GST_DEBUG_OBJECT(obj, "width = %d height = %d", width, depth); - if (width == 8) - sndformat = 0;//SND_PCM_FORMAT_S8 - else - //width==16 - sndformat = 2; //SND_PCM_FORMAT_S16_LE - if (avsys_pcm_format_mask_test(mask, sndformat)) - { //must be implemented - /* template contains { true, false } or just one, leave it as it is */ - scopy = gst_structure_copy(s); - - } - else - { - scopy = NULL; - } - if (scopy) - { - /* TODO: proper endianness detection, for now it's CPU endianness only */ - gst_structure_set(scopy, "signed", G_TYPE_BOOLEAN, TRUE, NULL); - gst_structure_set(scopy, "endianness", G_TYPE_INT, G_BYTE_ORDER, - NULL); - gst_caps_append_structure(caps, scopy); - } - - } - - avsys_pcm_format_mask_free(mask); - gst_caps_unref(in_caps); - return caps; -} - - -static GstCaps * -gst_avsysaudiosrc_detect_channels(GstObject * obj, - avsys_pcm_hw_params_t * hw_params, GstCaps * in_caps) -{ - GstCaps *caps; - guint min, max; - gint err, min_channel, max_channel, i; -// gint dir; - - GST_LOG_OBJECT(obj, "probing sample rates ..."); - - if ((err = avsys_pcm_hw_params_get_channels_min(hw_params, &min)) < 0) - goto min_chan_err; - - if ((err = avsys_pcm_hw_params_get_channels_max(hw_params, &max)) < 0) - goto max_chan_err; - - min_channel = min; - max_channel = max; - - if (min_channel < _MIN_CHANNEL) - min_channel = _MIN_CHANNEL; /* random 'sensible minimum' */ - - if (max_channel <= 0) - max_channel = _MAX_CHANNEL; /* or maybe just use 192400 or so? */ - else if (max_channel > 0 && max_channel < _MIN_CHANNEL) - max_channel = MAX (_MAX_CHANNEL, min_channel); - - GST_DEBUG_OBJECT(obj, "Min. channel = %u (%d)", min_channel, min); - GST_DEBUG_OBJECT(obj, "Max. channel = %u (%d)", max_channel, max); - - caps = gst_caps_make_writable(in_caps); - - for (i = 0; i < gst_caps_get_size(caps); ++i) - { - GstStructure *s; - - s = gst_caps_get_structure(caps, i); - if (min_channel == max_channel) - { - gst_structure_set(s, "channels", G_TYPE_INT, _MIN_CHANNEL, NULL); - } - else - { - gst_structure_set(s, "channels", GST_TYPE_INT_RANGE, min_channel, - max_channel, NULL); - } - } - - return caps; - - /* ERRORS */ - min_chan_err: - { - GST_ERROR_OBJECT(obj, "failed to query minimum sample rate"); - gst_caps_unref(in_caps); - return NULL; - } - max_chan_err: - { - GST_ERROR_OBJECT(obj, "failed to query maximum sample rate:"); - gst_caps_unref(in_caps); - return NULL; - } -} - -/* - * gst_avsys_probe_supported_formats: - * - * Takes the template caps and returns the subset which is actually - * supported by this device. - * - */ - -static GstCaps * -gst_avsysaudiosrc_probe_supported_formats(GstObject * obj, - avsys_handle_t handle, const GstCaps * template_caps) -{ - - avsys_pcm_hw_params_t *hw_params; - GstCaps *caps; - gint err; - - avsys_pcm_hw_params_malloc(&hw_params); - if ((err = avsys_pcm_hw_params_any(handle, hw_params)) < 0) - goto error; - - caps = gst_caps_copy(template_caps); - - if (!(caps = gst_avsysaudiosrc_detect_formats(obj, hw_params, caps))) - goto subroutine_error; - - if (!(caps = gst_avsysaudiosrc_detect_rates(obj, hw_params, caps))) - goto subroutine_error; - - if (!(caps = gst_avsysaudiosrc_detect_channels(obj, hw_params, caps))) - goto subroutine_error; - - avsys_pcm_hw_params_free(hw_params); - return caps; - - /* ERRORS */ - error: - { - GST_ERROR_OBJECT(obj, "failed to query formats"); - return NULL; - } - subroutine_error: - { - GST_ERROR_OBJECT(obj, "failed to query formats"); - return NULL; - } -} -#endif diff --git a/avsystem/src/gstavsysaudiosrc.h b/avsystem/src/gstavsysaudiosrc.h deleted file mode 100644 index 4031e04..0000000 --- a/avsystem/src/gstavsysaudiosrc.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __GST_AVSYS_AUDIO_SRC_H__ -#define __GST_AVSYS_AUDIO_SRC_H__ - -//#undef _MMCAMCORDER_MERGE_TEMP - -#include -#include - -#include - -G_BEGIN_DECLS - -#define GST_TYPE_AVSYS_AUDIO_SRC (gst_avsysaudiosrc_get_type()) -#define GST_AVSYS_AUDIO_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVSYS_AUDIO_SRC,GstAvsysAudioSrc)) -#define GST_AVSYS_AUDIO_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVSYS_AUDIO_SRC,GstAvsysAudioSrcClass)) -#define GST_IS_AVSYS_AUDIO_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVSYS_AUDIO_SRC)) -#define GST_IS_AVSYS_AUDIO_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVSYS_AUDIO_SRC)) -#define GST_AVSYS_AUDIO_SRC_CAST(obj) ((GstAvsysAudioSrc *)(obj)) - -#define GST_AVSYS_AUDIO_SRC_GET_LOCK(obj) (GST_AVSYS_AUDIO_SRC_CAST (obj)->avsysaudio_lock) -#define GST_AVSYS_AUDIO_SRC_LOCK(obj) (g_mutex_lock (GST_AVSYS_AUDIO_SRC_GET_LOCK (obj))) -#define GST_AVSYS_AUDIO_SRC_UNLOCK(obj) (g_mutex_unlock (GST_AVSYS_AUDIO_SRC_GET_LOCK (obj))) - -typedef struct _GstAvsysAudioSrc GstAvsysAudioSrc; -typedef struct _GstAvsysAudioSrcClass GstAvsysAudioSrcClass; - -typedef enum { - AVSYSAUDIOSRC_LATENCY_LOW = 0, - AVSYSAUDIOSRC_LATENCY_MID, - AVSYSAUDIOSRC_LATENCY_HIGH, -}GstAvsysAudioSrcAudioLatency; - -#define GST_AVSYS_AUDIO_SRC_LATENCY (gst_avsysaudiosrc_audio_latency_get_type()) - - - -/** - * GstAvsysAudioSrc: - * - * Opaque data structure - */ -struct _GstAvsysAudioSrc { - GstAudioSrc src; - - avsys_handle_t audio_handle; - avsys_audio_param_t audio_param; - - gint buffer_size; - GstCaps *cached_caps; - GMutex *avsysaudio_lock; - - gint media_call; - gint bytes_per_sample; - gint latency; - -}; - -struct _GstAvsysAudioSrcClass { - GstAudioSrcClass parent_class; -}; - - -GType gst_avsysaudiosrc_get_type (void); -GType gst_avsysaudiosrc_audio_latency_get_type(void); - -G_END_DECLS - -#endif /* __GST_AVSYS_AUDIO_SRC_H__ */ diff --git a/avsystem/src/gstavsysmemsink.c b/avsystem/src/gstavsysmemsink.c deleted file mode 100644 index 9d56985..0000000 --- a/avsystem/src/gstavsysmemsink.c +++ /dev/null @@ -1,751 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - - -#if defined(_MM_PROJECT_FLOATER) || defined(_MM_PROJECT_PROTECTOR) || defined(_MM_PROJECT_VOLANS) -#define VERSION "0.10.19" // sec -#define PACKAGE "gstreamer" -#elif defined(_MM_PROJECT_ADORA) -#define VERSION "0.10.9" // mavell -#define PACKAGE "gstreamer" -#else -#include -#endif - -#include -#include - -#include "gstavsysmemsink.h" - -#define debug_enter g_print -#define debug_leave g_print -#define debug_fenter() g_print("enter: %s\n",__FUNCTION__) -#define debug_fleave() g_print("leave: %s\n",__FUNCTION__) -#define debug_msg g_print -#define debug_verbose g_print -#define debug_warning g_print -#define debug_error g_print - -#define GST_CAT_DEFAULT avsysmemsink_debug - -#define DISABLE_YUV_FORMAT_ON_SINK_CAPS - -GST_DEBUG_CATEGORY_STATIC (avsysmemsink_debug); - -enum -{ - SIGNAL_VIDEO_STREAM, - LAST_SIGNAL -}; - -enum -{ - PROP_0, - PROP_WIDTH, - PROP_HEIGHT, - PROP_ROTATE, -}; - -static GstStaticPadTemplate sink_factory = - GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ( -#ifndef DISABLE_YUV_FORMAT_ON_SINK_CAPS - "video/x-raw-yuv, " - "format = (fourcc){YV12}, " - "framerate = (fraction) [ 0, MAX ], " - "width = (int) [ 1, MAX ], " - "height = (int) [ 1, MAX ]; " - "video/x-raw-yuv, " - "format = (fourcc){I420}, " - "framerate = (fraction) [ 0, MAX ], " - "width = (int) [ 1, MAX ], " - "height = (int) [ 1, MAX ]; " - "video/x-raw-rgb, " - "bpp = (int)32, " - "depth = (int)24; " -#else /* BGRA */ - "video/x-raw-rgb, " - "bpp = (int)32, " - "depth = (int)32, " - "endianness = (int)4321, " - "red_mask = (int)65280, " - "green_mask = (int)16711680, " - "blue_mask = (int)-16777216, " - "alpha_mask = (int)255, " - "width = (int) [ 1, MAX ], " - "height = (int) [ 1, MAX ], " - "framerate = (fraction) [ 0, MAX ]; " -#endif - ) - ); - -static GstElementDetails AvsysMemSink_details = { - "AV-system Stream callback", - "Sink/Video", - "Stream sink for AV-System GStreamer Plug-in", - "" -}; - -static guint gst_avsysmemsink_signals[LAST_SIGNAL] = { 0 }; - - -#ifdef G_ENABLE_DEBUG -#define g_marshal_value_peek_int(v) g_value_get_int (v) -#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) -#else /* !G_ENABLE_DEBUG */ -#define g_marshal_value_peek_int(v) (v)->data[0].v_int -#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer -#endif /* !G_ENABLE_DEBUG */ - - -#define GET_PIXEL(buf, x, y, stride) (*((unsigned char*)(buf) + (x) + (y)*(stride))) - -#define GET_RED(Y,U,V) ((9535 * (Y - 16) + 13074 * (V - 128)) >> 13) -#define GET_GREEN(Y,U,V) ((9535 * (Y - 16) - 6660 * (V - 128) - 3203 * (U - 128)) >> 13 ) -#define GET_BLUE(Y,U,V) ((9535 * (Y - 16) + 16531 * (U - 128)) >> 13 ) - -#define UCLIP(a) (((a)<0)?0:((a)>255)?255:(a)) - - -static void -yuv420toargb(unsigned char *src, unsigned char *dst, int width, int height) -{ - int h,w; - int y=0,u=0,v=0; - int a=0,r=0,g=0,b=0; - - unsigned char* pixel; - - int index=0; - - unsigned char *pY; - unsigned char *pU; - unsigned char *pV; - - GST_DEBUG ("converting yuv420 to argb"); - - pY = src ; - pU = src + (width * height) ; - pV = src + (width * height) + (width * height /4) ; - - a = 255; - - for(h = 0 ; h < height; h++) - { - for(w = 0 ; w < width; w++) - { - y = GET_PIXEL(pY,w,h,width); - u = GET_PIXEL(pU,w/2,h/2,width/2); - v = GET_PIXEL(pV,w/2,h/2,width/2); - - r = GET_RED(y,u,v); - g = GET_GREEN(y,u,v); - b = GET_BLUE(y,u,v); - - r = UCLIP(r); - g = UCLIP(g); - b = UCLIP(b); - - index = (w + (h* width)) * 4; - dst[index] = r; - dst[index+1] = g; - dst[index+2] = b; - dst[index+3] = a; - } - } -} - -static void -rotate_pure(unsigned char *src, unsigned char *dst, int width,int height,int angle,int bpp) -{ - - int size; - int new_x,new_y; - int org_x,org_y; - int dst_width; - int src_idx, dst_idx; - - size = width * height * bpp; - - if(angle == 0) - { - memcpy(dst,src,size); - return; - } - - for(org_y =0; org_y < height; org_y++) - { - for(org_x = 0; org_x < width ; org_x++) - { - if(angle == 90) - { - new_x = height - org_y; - new_y = org_x; - - dst_width = height; - } - else if(angle == 180) - { - new_x = width - org_x; - new_y = height - org_y; - dst_width = width; - } - else if(angle == 270) - { - new_x = org_y; - new_y = width - org_x; - dst_width = height; - } - else - { - g_print("Not support Rotate : %d\n",angle); - return; - } - - src_idx = org_x + (org_y * width); - dst_idx = new_x + (new_y * dst_width); - - memcpy(dst + (dst_idx*bpp), src+(src_idx *bpp),bpp); - } - } - -} - -static void -resize_pure(unsigned char *src, unsigned char *dst, int src_width, int src_height, int dst_width,int dst_height, int bpp) -{ - float xFactor,yFactor; - - float org_fx,org_fy; - int org_x,org_y; - - int x,y; - - int src_index,dst_index; - - unsigned short *pshortSrc; - unsigned short *pshortDst; - - if(bpp == 2) - { - pshortSrc = (unsigned short*)src; - pshortDst = (unsigned short*)dst; - } - - xFactor = (float)((dst_width<<16) / src_width); - yFactor = (float)((dst_height<<16) / src_height); - - for(y = 0; y < dst_height; y++) - { - for(x = 0; x < dst_width; x++) - { - org_fx = (float)((x<<16)/xFactor); - org_fy = (float)((y<<16)/yFactor); - - org_x = (int)(org_fx); - org_y = (int)(org_fy); - - src_index = org_x + (org_y * src_width); - dst_index = x + (y*dst_width); - - memcpy(dst+(dst_index *bpp ),src+(src_index *bpp),bpp); - } - } -} - -/* BOOLEAN:POINTER,INT,INT (avsysvideosink.c:1) */ -void -gst_avsysmemsink_BOOLEAN__POINTER_INT_INT (GClosure *closure, - GValue *return_value G_GNUC_UNUSED, - guint n_param_values, - const GValue *param_values, - gpointer invocation_hint G_GNUC_UNUSED, - gpointer marshal_data) -{ - typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_INT_INT) (gpointer data1, - gpointer arg_1, - gint arg_2, - gint arg_3, - gpointer data2); - register GMarshalFunc_BOOLEAN__POINTER_INT_INT callback; - register GCClosure *cc = (GCClosure*) closure; - register gpointer data1, data2; - - gboolean v_return; - - g_return_if_fail (return_value != NULL); - g_return_if_fail (n_param_values == 4); - - if (G_CCLOSURE_SWAP_DATA (closure)) - { - data1 = closure->data; - data2 = g_value_peek_pointer (param_values + 0); - } - else - { - data1 = g_value_peek_pointer (param_values + 0); - data2 = closure->data; - } - callback = (GMarshalFunc_BOOLEAN__POINTER_INT_INT) (marshal_data ? marshal_data : cc->callback); - - v_return = callback (data1, - g_marshal_value_peek_pointer (param_values + 1), - g_marshal_value_peek_int (param_values + 2), - g_marshal_value_peek_int (param_values + 3), - data2); - - g_value_set_boolean (return_value, v_return); -} - -static void gst_avsysmemsink_init_interfaces (GType type); - - -GST_BOILERPLATE_FULL (GstAvsysMemSink, gst_avsysmemsink, GstVideoSink, GST_TYPE_VIDEO_SINK, gst_avsysmemsink_init_interfaces); - - -static void -gst_avsysmemsink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) -{ - GstAvsysMemSink *AvsysMemSink = GST_AVSYS_MEM_SINK (object); - switch (prop_id) { - case PROP_0: - break; - case PROP_WIDTH: - if(AvsysMemSink->dst_width != g_value_get_int (value)) - { - AvsysMemSink->dst_width = g_value_get_int (value); - AvsysMemSink->dst_changed = 1; - } - break; - case PROP_HEIGHT: - if(AvsysMemSink->dst_height != g_value_get_int (value)) - { - AvsysMemSink->dst_height = g_value_get_int (value); - AvsysMemSink->dst_changed = 1; - } - break; - case PROP_ROTATE: - if(AvsysMemSink->rotate != g_value_get_int(value)) - { - AvsysMemSink->rotate = g_value_get_int(value); - AvsysMemSink->dst_changed = 1; - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - g_print ("invalid property id\n"); - break; - }; -} - -static void -gst_avsysmemsink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) -{ - GstAvsysMemSink *AvsysMemSink = GST_AVSYS_MEM_SINK (object); - - switch (prop_id) { - case PROP_0: - break; - case PROP_WIDTH: - g_value_set_int (value, AvsysMemSink->dst_width); - break; - case PROP_HEIGHT: - g_value_set_int (value, AvsysMemSink->dst_height); - break; - case PROP_ROTATE: - g_value_set_int (value, AvsysMemSink->rotate); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - debug_warning ("invalid property id\n"); - break; - } -} - -static void -free_buffer(GstAvsysMemSink *AvsysMemSink) -{ - if(AvsysMemSink->con_buf) - free(AvsysMemSink->con_buf); - - if(AvsysMemSink->rot_buf) - free(AvsysMemSink->rot_buf); - - if(AvsysMemSink->rsz_buf) - free(AvsysMemSink->rsz_buf); - - AvsysMemSink->con_buf = NULL; - AvsysMemSink->rot_buf = NULL; - AvsysMemSink->rsz_buf = NULL; - -} -static GstStateChangeReturn -gst_avsysmemsink_change_state (GstElement *element, GstStateChange transition) -{ - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - GstAvsysMemSink *AvsysMemSink = GST_AVSYS_MEM_SINK (element); - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - debug_msg ("GST AVSYS DISPLAY SINK: NULL -> READY\n"); - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - debug_msg ("GST AVSYS DISPLAY SINK: READY -> PAUSED\n"); - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - debug_msg ("GST AVSYS DISPLAY SINK: PAUSED -> PLAYING\n"); - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS(parent_class)->change_state (element, transition); - if (ret == GST_STATE_CHANGE_FAILURE) { - return ret; - } - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - debug_msg ("GST AVSYS MEM SINK: PLAYING -> PAUSED\n"); - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - debug_msg ("GST AVSYS MEM SINK: PAUSED -> READY\n"); - free_buffer(AvsysMemSink); - break; - case GST_STATE_CHANGE_READY_TO_NULL: - debug_msg ("GST AVSYS MEM SINK: READY -> NULL\n"); - break; - default: - break; - } - - return ret; - - -} - - -static gboolean -gst_avsysmemsink_set_caps (GstBaseSink * bs, GstCaps * caps) -{ - GstAvsysMemSink *s = GST_AVSYS_MEM_SINK (bs); - GstStructure *structure; - gint width = 0; - gint height = 0; - - if (caps != NULL) - { - guint32 fourcc; - char *name = NULL; - int bpp = 0, depth = 0; - - structure = gst_caps_get_structure (caps, 0); - - /**/ - name = (char *) gst_structure_get_name (structure); - GST_DEBUG_OBJECT (s, "CAPS NAME: %s", name); - - if (gst_structure_has_name (structure, "video/x-raw-rgb")) - { - s->is_rgb = TRUE; - } - else if (gst_structure_has_name (structure, "video/x-raw-yuv")) - { - s->is_rgb = FALSE; - } - - /* get source size */ - gst_structure_get_int (structure, "height", &height); - gst_structure_get_int (structure, "width", &width); - - if (gst_structure_get_fourcc (structure, "format", &fourcc)) - { - switch (fourcc) - { - case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): - debug_warning("set format AVSYS_VIDEO_FORMAT_UYVY\n"); - break; - case GST_MAKE_FOURCC ('Y', 'V', '1', '6'): - case GST_MAKE_FOURCC ('Y', '4', '2', 'B'): - debug_warning("set format AVSYS_VIDEO_FORMAT_YUV422\n"); - break; - case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): - debug_warning("set format AVSYS_VIDEO_FORMAT_YUV420\n"); - break; - case GST_MAKE_FOURCC ('R', 'G', 'B', ' '): - debug_warning("set format AVSYS_VIDEO_FORMAT_RGB565\n"); - break; - default: - debug_warning("set default format AVSYS_VIDEO_FORMAT_RGB565\n"); - break; - } - } - - if( s->src_width != width || - s->src_height != height) - { - debug_warning ("DISPLAY: Video Source Changed! [%d x %d] -> [%d x %d]\n", - s->src_width, s->src_height, width, height); - s->src_changed = TRUE; - } - - s->src_width = width; - s->src_height = height; - - if(s->dst_width ==0) - { - s->dst_width = width; - s->dst_changed = 1; - } - - if(s->dst_height == 0) - { - s->dst_height = height; - s->dst_changed = 1; - } - debug_msg ("SRC CAPS: width:%d, height:%d \n", width, height); - } - else - { - debug_warning ("caps is NULL.\n"); - } - - debug_fleave (); - - return TRUE; -} - -static GstFlowReturn -gst_avsysmemsink_preroll (GstBaseSink * bsink, GstBuffer * buf) -{ - GstAvsysMemSink *AvsysMemSink = GST_AVSYS_MEM_SINK (bsink); - - AvsysMemSink->src_length = GST_BUFFER_SIZE (buf); - debug_msg ("SRC LENGTH: %d\n", AvsysMemSink->src_length); - - return GST_FLOW_OK; -} - -static GstFlowReturn -gst_avsysmemsink_show_frame (GstBaseSink * bsink, GstBuffer * buf) -{ - GstAvsysMemSink *s = GST_AVSYS_MEM_SINK (bsink); - gboolean res = FALSE; - int f_size; - f_size = GST_BUFFER_SIZE (buf); - unsigned char *dst_buf; - - if ( ! s->is_rgb ) - { - GST_DEBUG_OBJECT (s, "src format is not rgb"); - if (s->dst_changed == TRUE) - { - if(s->con_buf) - { - free(s->con_buf); - s->con_buf = NULL; - } - - if(s->rot_buf) - { - free(s->rot_buf); - s->rot_buf = NULL; - } - - if(s->rsz_buf) - { - free(s->rsz_buf); - s->rsz_buf = NULL; - } - - s->con_buf = malloc(s->src_width * s->src_height * 4); - if(s->rotate != 0) - { - s->rot_buf = malloc(s->src_width * s->src_height * 4); - } - - s->rsz_buf = malloc(s->dst_width * s->dst_height *4); - - s->dst_changed = FALSE; - } - - yuv420toargb(GST_BUFFER_DATA (buf),s->con_buf,s->src_width, s->src_height); - if(s->rotate != 0) - { - rotate_pure(s->con_buf,s->rot_buf,s->src_width, s->src_height,s->rotate,4); - if(s->rotate == 90 || s->rotate == 270) - { - resize_pure(s->rot_buf,s->rsz_buf,s->src_height,s->src_width, - s->dst_width, s->dst_height,4); - } - else - { - resize_pure(s->rot_buf,s->rsz_buf,s->src_width,s->src_height, - s->dst_width, s->dst_height,4); - } - } - else - { - resize_pure(s->con_buf,s->rsz_buf,s->src_width,s->src_height, - s->dst_width, s->dst_height,4); - } - - /* emit signal for video-stream */ - g_signal_emit (s,gst_avsysmemsink_signals[SIGNAL_VIDEO_STREAM], - 0,s->rsz_buf, - s->dst_width,s->dst_height, - &res); - } - else - { - GST_DEBUG_OBJECT (s, "src format is rgb"); - - /* NOTE : video can be resized by convert plugin's set caps on running time. - * So, it should notice it to application through callback func. - */ - g_signal_emit (s, gst_avsysmemsink_signals[SIGNAL_VIDEO_STREAM], - 0, GST_BUFFER_DATA (buf), - s->src_width, s->src_height, - &res); - } - GST_DEBUG_OBJECT (s, "g_signal_emit : src_width=%d, src_height=%d, GST_BUFFER_SIZE=%d", s->src_width,s->src_height,GST_BUFFER_SIZE(buf)); - - /*check video stream callback result.*/ - if (res) - { - //debug_verbose("Video stream is called.\n"); - return GST_FLOW_OK; - } - - return GST_FLOW_OK; -} - -static void -gst_avsysmemsink_init_interfaces (GType type) -{ - /*void*/ -} - - -static void -gst_avsysmemsink_base_init (gpointer klass) -{ - GstElementClass *element_class = GST_ELEMENT_CLASS (klass); - - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&sink_factory)); - gst_element_class_set_details (element_class, &AvsysMemSink_details); -} - -static void -gst_avsysmemsink_class_init (GstAvsysMemSinkClass *klass) -{ - GObjectClass *gobject_class = (GObjectClass*) klass; - GstElementClass *gstelement_class = (GstElementClass*) klass; - GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; - - - parent_class = g_type_class_peek_parent (klass); - - gobject_class->set_property = gst_avsysmemsink_set_property; - gobject_class->get_property = gst_avsysmemsink_get_property; - - - g_object_class_install_property (gobject_class, PROP_WIDTH, - g_param_spec_int ("width", - "Width", - "Width of display", - 0, G_MAXINT, 176, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - - g_object_class_install_property (gobject_class, PROP_HEIGHT, - g_param_spec_int ("height", - "Height", - "Height of display", - 0, G_MAXINT, 144, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (gobject_class, PROP_ROTATE, - g_param_spec_int ("rotate", - "Rotate", - "Rotate of display", - 0, G_MAXINT, 0, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - - /** - * GstAvsysVideoSink::video-stream: - */ - gst_avsysmemsink_signals[SIGNAL_VIDEO_STREAM] = g_signal_new ( - "video-stream", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - gst_avsysmemsink_BOOLEAN__POINTER_INT_INT, - G_TYPE_BOOLEAN, - 3, - G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT); - - gstelement_class->change_state = gst_avsysmemsink_change_state; - - gstbasesink_class->set_caps = gst_avsysmemsink_set_caps; - gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_avsysmemsink_preroll); - gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_avsysmemsink_show_frame); - - - GST_DEBUG_CATEGORY_INIT (avsysmemsink_debug, - "avsysmemsink", - 0, - "AV system based GStreamer Plug-in"); -} - - -static void -gst_avsysmemsink_init (GstAvsysMemSink *AvsysMemSink, GstAvsysMemSinkClass *klass) -{ - /*private*/ - AvsysMemSink->src_width = 0; - AvsysMemSink->src_height = 0; - - AvsysMemSink->src_changed = 0; - - /*property*/ - AvsysMemSink->dst_width = 0; - AvsysMemSink->dst_height = 0; - - AvsysMemSink->dst_changed = 0; - - AvsysMemSink->rotate = 0; - - AvsysMemSink->con_buf = NULL; - AvsysMemSink->rot_buf = NULL; - AvsysMemSink->rsz_buf = NULL; - - AvsysMemSink->is_rgb = FALSE; -} - - -/* EOF */ diff --git a/avsystem/src/gstavsysmemsink.h b/avsystem/src/gstavsysmemsink.h deleted file mode 100644 index bc28029..0000000 --- a/avsystem/src/gstavsysmemsink.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - - -#ifndef __GST_AVSYSMEMSINK_H__ -#define __GST_AVSYSMEMSINK_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define GST_TYPE_AVSYS_MEM_SINK (gst_avsysmemsink_get_type()) -#define GST_AVSYS_MEM_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVSYS_MEM_SINK, GstAvsysMemSink)) -#define GST_AVSYS_MEM_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVSYS_MEM_SINK, GstAvsysMemSinkClass)) -#define GST_IS_AVSYS_MEM_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVSYS_MEM_SINK)) -#define GST_IS_AVSYS_MEM_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVSYS_MEM_SINK)) - - -typedef struct _GstAvsysMemSink GstAvsysMemSink; -typedef struct _GstAvsysMemSinkClass GstAvsysMemSinkClass; - -struct _GstAvsysMemSink -{ - GstVideoSink videosink; - - int src_width; - int src_height; - - int src_changed; - int src_length; - - - int dst_width; - int dst_height; - - int dst_length; - int dst_changed; - - unsigned char *con_buf; - unsigned char *rot_buf; - unsigned char *rsz_buf; - - int rotate; - - int is_rgb; -}; - -struct _GstAvsysMemSinkClass -{ - GstVideoSinkClass parent_class; -}; - -GType gst_avsysmemsink_get_type (void); - -G_END_DECLS - - -#endif /* __GST_AVSYSMEMSINK_H__ */ - -/* EOF */ diff --git a/avsystem/src/gstavsyssink.c b/avsystem/src/gstavsyssink.c deleted file mode 100644 index a1ab29e..0000000 --- a/avsystem/src/gstavsyssink.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include "gstavsysaudiosink.h" -#include "gstavsysmemsink.h" - -GST_DEBUG_CATEGORY (avsystem_sink_debug); - -static gboolean -plugin_init (GstPlugin *plugin) -{ - gboolean error; - /*register the exact name you can find in the framework*/ - error = gst_element_register (plugin, "avsysaudiosink", - GST_RANK_PRIMARY + 100, - GST_TYPE_AVSYS_AUDIO_SINK); - - error = gst_element_register (plugin, "avsysmemsink", - GST_RANK_NONE, - GST_TYPE_AVSYS_MEM_SINK); - - if (!error) - goto failed; - - GST_DEBUG_CATEGORY_INIT (avsystem_sink_debug, "avsystemsink", 0, "avsystem sink plugins"); - return TRUE; - -failed: - - return FALSE; -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "avsyssink", - "AV system video/audio sink plug-in", - plugin_init, - PACKAGE_VERSION, - "LGPL", - "AV System Sink", - "http://www.samsung.com") diff --git a/avsystem/src/gstavsyssrc.c b/avsystem/src/gstavsyssrc.c deleted file mode 100644 index adde8df..0000000 --- a/avsystem/src/gstavsyssrc.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * avsystem - * - * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. - * - * Contact: JongHyuk Choi - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation; either version 2.1 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., 51 - * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include - -#include "gstavsysaudiosrc.h" - -GST_DEBUG_CATEGORY (avsystem_src_debug); - -static gboolean -plugin_init (GstPlugin *plugin) -{ - gboolean error; - /*register the exact name you can find in the framework*/ - error = gst_element_register (plugin, "avsysaudiosrc", - GST_RANK_NONE, - GST_TYPE_AVSYS_AUDIO_SRC); - if (!error) - goto failed; - - GST_DEBUG_CATEGORY_INIT (avsystem_src_debug, "avsystemsrc", 0, "avsystem src plugins"); - return TRUE; - -failed: - - return FALSE; -} - - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "avsysaudiosrc", - "AV system audio source plug-in", - plugin_init, - PACKAGE_VERSION, - "LGPL", - "AV System Source", - "http://www.samsung.com") - diff --git a/common/m4/gst-x11.m4 b/common/m4/gst-x11.m4 new file mode 100644 index 0000000..d3baf2d --- /dev/null +++ b/common/m4/gst-x11.m4 @@ -0,0 +1,70 @@ +dnl macros for X-related detections +dnl AC_SUBST's HAVE_X, X_CFLAGS, X_LIBS +AC_DEFUN([AG_GST_CHECK_X], +[ + AC_PATH_XTRA + ac_cflags_save="$CFLAGS" + ac_cppflags_save="$CPPFLAGS" + CFLAGS="$CFLAGS $X_CFLAGS" + CPPFLAGS="$CPPFLAGS $X_CFLAGS" + + dnl now try to find the HEADER + AC_CHECK_HEADER(X11/Xlib.h, HAVE_X="yes", HAVE_X="no") + + if test "x$HAVE_X" = "xno" + then + AC_MSG_NOTICE([cannot find X11 development files]) + else + dnl this is much more than we want + X_LIBS="$X_LIBS $X_PRE_LIBS $X_EXTRA_LIBS" + dnl AC_PATH_XTRA only defines the path needed to find the X libs, + dnl it does not add the libs; therefore we add them here + X_LIBS="$X_LIBS -lX11" + AC_SUBST(X_CFLAGS) + AC_SUBST(X_LIBS) + fi + AC_SUBST(HAVE_X) + + CFLAGS="$ac_cflags_save" + CPPFLAGS="$ac_cppflags_save" +]) + +dnl *** XVideo *** +dnl Look for the PIC library first, Debian requires it. +dnl Check debian-devel archives for gory details. +dnl 20020110: +dnl At the moment XFree86 doesn't distribute shared libXv due +dnl to unstable API. On many platforms you CAN NOT link a shared +dnl lib to a static non-PIC lib. This is what the xvideo GStreamer +dnl plug-in wants to do. So Debian distributes a PIC compiled +dnl version of the static lib for plug-ins to link to when it is +dnl inappropriate to link the main application to libXv directly. +dnl FIXME: add check if this platform can support linking to a +dnl non-PIC libXv, if not then don not use Xv. +dnl FIXME: perhaps warn user if they have a shared libXv since +dnl this is an error until XFree86 starts shipping one +AC_DEFUN([AG_GST_CHECK_XV], +[ + if test x$HAVE_X = xyes; then + AC_CHECK_LIB(Xv_pic, XvQueryExtension, + HAVE_XVIDEO="yes", HAVE_XVIDEO="no", + $X_LIBS -lXext) + + if test x$HAVE_XVIDEO = xyes; then + XVIDEO_LIBS="-lXv_pic -lXext" + AC_SUBST(XVIDEO_LIBS) + else + dnl try again using something else if we didn't find it first + if test x$HAVE_XVIDEO = xno; then + AC_CHECK_LIB(Xv, XvQueryExtension, + HAVE_XVIDEO="yes", HAVE_XVIDEO="no", + $X_LIBS -lXext) + + if test x$HAVE_XVIDEO = xyes; then + XVIDEO_LIBS="-lXv -lXext" + AC_SUBST(XVIDEO_LIBS) + fi + fi + fi + fi +]) diff --git a/compile b/compile new file mode 100755 index 0000000..1b1d232 --- /dev/null +++ b/compile @@ -0,0 +1,142 @@ +#! /bin/sh +# Wrapper for compilers which do not understand `-c -o'. + +scriptversion=2005-05-14.22 + +# Copyright (C) 1999, 2000, 2003, 2004, 2005 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +case $1 in + '') + echo "$0: No command. Try \`$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand `-c -o'. +Remove `-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file `INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; +esac + +ofile= +cfile= +eat= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as `compile cc -o foo foo.c'. + # So we strip `-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no `-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # `.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'` + +# Create the lock directory. +# Note: use `[/.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/config.guess b/config.guess new file mode 100755 index 0000000..40eaed4 --- /dev/null +++ b/config.guess @@ -0,0 +1,1517 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011 Free Software Foundation, Inc. + +timestamp='2011-05-11' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Originally written by Per Bothner. Please send patches (context +# diff format) to and include a ChangeLog +# entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, +2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free +Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm:riscos:*:*|arm:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux${UNAME_RELEASE} + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval $set_cc_for_build + SUN_ARCH="i386" + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH="x86_64" + fi + fi + echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + case ${UNAME_MACHINE} in + pc98) + echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + 8664:Windows_NT:*) + echo x86_64-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-gnu + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-gnueabi + else + echo ${UNAME_MACHINE}-unknown-linux-gnueabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit ;; + crisv32:Linux:*:*) + echo crisv32-axis-linux-gnu + exit ;; + frv:Linux:*:*) + echo frv-unknown-linux-gnu + exit ;; + i*86:Linux:*:*) + LIBC=gnu + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'` + echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + or32:Linux:*:*) + echo or32-unknown-linux-gnu + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-gnu + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + tile*:Linux:*:*) + echo ${UNAME_MACHINE}-tilera-linux-gnu + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-gnu + exit ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configury will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + case $UNAME_PROCESSOR in + i386) + eval $set_cc_for_build + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + UNAME_PROCESSOR="x86_64" + fi + fi ;; + unknown) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-?:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk${UNAME_RELEASE} + exit ;; + NSE-?:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; + i*86:AROS:*:*) + echo ${UNAME_MACHINE}-pc-aros + exit ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..be0c437 --- /dev/null +++ b/config.h.in @@ -0,0 +1,68 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define the version */ +#undef GST_PLUGIN_VERSION + +/* Define the release version */ +#undef GST_PLUGIN_VERSION_RELEASE + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION diff --git a/configure.ac b/configure.ac index 316d441..ee6ab18 100755 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ AC_INIT(extension, 1.0) - + dnl versions of gstreamer and plugins-base GST_MAJORMINOR=0.10 GST_REQUIRED=0.10.0 @@ -39,10 +39,10 @@ AC_PROG_LIBTOOL dnl decide on error flags AS_COMPILER_FLAG(-Wall, GST_WALL="yes", GST_WALL="no") - + if test "x$GST_WALL" = "xyes"; then GST_ERROR="$GST_ERROR -Wall" - + # if test "x$GST_PLUGIN_CVS" = "xyes"; then # AS_COMPILER_FLAG(-Werror,GST_ERROR="$GST_ERROR -Werror",GST_ERROR="$GST_ERROR") # fi @@ -160,12 +160,47 @@ PKG_CHECK_MODULES(GST_INTERFACES, gstreamer-interfaces-$GST_MAJORMINOR >= $GST_R AC_SUBST(GST_INTERFACES_CFLAGS) AC_SUBST(GST_INTERFACES_LIBS) +dnl xvimagesrc _CFLAGS and _LIB available + +PKG_CHECK_MODULES(DRI2, libdri2) +AC_SUBST(DRI2_CFLAGS) +AC_SUBST(DRI2_LIBS) + +PKG_CHECK_MODULES(X11, x11) +AC_SUBST(X11_CFLAGS) +AC_SUBST(X11_LIBS) + +PKG_CHECK_MODULES(XEXT, xext) +AC_SUBST(XEXT_CFLAGS) +AC_SUBST(XEXT_LIBS) + +PKG_CHECK_MODULES(XV, xv) +AC_SUBST(XV_CFLAGS) +AC_SUBST(XV_LIBS) + +PKG_CHECK_MODULES(XDAMAGE, xdamage) +AC_SUBST(XDAMAGE_CFLAGS) +AC_SUBST(XDAMAGE_LIBS) + +PKG_CHECK_MODULES(DRM, libdrm) +AC_SUBST(DRM_CFLAGS) +AC_SUBST(DRM_LIBS) + +PKG_CHECK_MODULES(DRM, libdrm-devel) +AC_SUBST(DRM_DEVEL_CFLAGS) +AC_SUBST(DRM_DEVEL_LIBS) + +PKG_CHECK_MODULES(TBM, libtbm) +AC_SUBST(TBM_CFLAGS) +AC_SUBST(TBM_LIBS) + dnl use time analysis module -PKG_CHECK_MODULES(MMTA, mm-ta) -AC_SUBST(MMTA_CFLAGS) -AC_SUBST(MMTA_LIBS) -dnl required package for evasimagesink +PKG_CHECK_MODULES(XML2, libxml-2.0) +AC_SUBST(XML2_CFLAGS) +AC_SUBST(XML2_LIBS) + +dnl required package for evasimagesink/evaspixmapsink PKG_CHECK_MODULES(EFL, [ evas >= $EFL_REQUIRED ecore >= $EFL_REQUIRED @@ -182,6 +217,88 @@ PKG_CHECK_MODULES(EFL, [ ]) ]) +dnl *** belows are related to evaspixmapsink plug-ins *** +AG_GST_ARG_WITH_PACKAGE_NAME +AG_GST_ARG_WITH_PACKAGE_ORIGIN + +dnl set license and copyright notice +GST_LICENSE="LGPL" +AC_DEFINE_UNQUOTED(GST_LICENSE, "$GST_LICENSE", [GStreamer license]) +AC_SUBST(GST_LICENSE) + +echo +AC_MSG_NOTICE([Checking libraries for evaspixmapsink plugin]) +echo +dnl *** X11 *** +translit(dnm, m, l) AM_CONDITIONAL(USE_X, true) +AG_GST_CHECK_FEATURE(X, [X libraries and plugins], + [evaspixmapsink], [ + AC_PATH_XTRA + ac_cflags_save="$CFLAGS" + ac_cppflags_save="$CPPFLAGS" + CFLAGS="$CFLAGS $X_CFLAGS" + CPPFLAGS="$CPPFLAGS $X_CFLAGS" + + dnl now try to find the HEADER + AC_CHECK_HEADER(X11/Xlib.h, HAVE_X="yes", HAVE_X="no") + + if test "x$HAVE_X" = "xno" + then + AC_MSG_NOTICE([cannot find X11 development files]) + else + dnl this is much more than we want + X_LIBS="$X_LIBS $X_PRE_LIBS $X_EXTRA_LIBS" + dnl AC_PATH_XTRA only defines the path needed to find the X libs, + dnl it does not add the libs; therefore we add them here + X_LIBS="$X_LIBS -lX11" + AC_SUBST(X_CFLAGS) + AC_SUBST(X_LIBS) + fi + AC_SUBST(HAVE_X) + CFLAGS="$ac_cflags_save" + CPPFLAGS="$ac_cppflags_save" +]) + +dnl Check for Xv extension +translit(dnm, m, l) AM_CONDITIONAL(USE_XVIDEO, true) +AG_GST_CHECK_FEATURE(XVIDEO, [X11 XVideo extensions], + [evaspixmapsink], [ +AG_GST_CHECK_XV +]) + +dnl check for X Shm +translit(dnm, m, l) AM_CONDITIONAL(USE_XSHM, true) +AG_GST_CHECK_FEATURE(XSHM, [X Shared Memory extension], , [ + if test x$HAVE_X = xyes; then + AC_CHECK_LIB(Xext, XShmAttach, + HAVE_XSHM="yes", HAVE_XSHM="no", + $X_LIBS) + if test "x$HAVE_XSHM" = "xyes"; then + XSHM_LIBS="-lXext" + else + dnl On AIX, it is in XextSam instead, but we still need -lXext + AC_CHECK_LIB(XextSam, XShmAttach, + HAVE_XSHM="yes", HAVE_XSHM="no", + $X_LIBS) + if test "x$HAVE_XSHM" = "xyes"; then + XSHM_LIBS="-lXext -lXextSam" + fi + fi + fi +], , [ + AC_SUBST(HAVE_XSHM) + AC_SUBST(XSHM_LIBS) +]) + +dnl use tbm +PKG_CHECK_MODULES(TBM, libtbm) +AC_SUBST(TBM_CFLAGS) +AC_SUBST(TBM_LIBS) + +PKG_CHECK_MODULES(VCONF, vconf) +AC_SUBST(VCONF_CFLAGS) +AC_SUBST(VCONF_LIBS) + dnl PKG_CHECK_MODULES(UDEVMGR, unified-dev-mgr) dnl AC_SUBST(UDEVMGR_CFLAGS) dnl AC_SUBST(UDEVMGR_LIBS) @@ -198,18 +315,6 @@ AC_ARG_ENABLE(ext-encodebin, AC_HELP_STRING([--enable-ext-encodebin], [using enc [GST_EXT_USE_EXT_ENCODEBIN=yes]) AM_CONDITIONAL(GST_EXT_USE_EXT_ENCODEBIN, test "x$GST_EXT_USE_EXT_ENCODEBIN" = "xyes") -dnl use ext-avsystem -------------------------------------------------------------------------- -AC_ARG_ENABLE(ext-avsystem, AC_HELP_STRING([--enable-ext-avsystem], [using avsystem]), - [ - case "${enableval}" in - yes) GST_EXT_USE_EXT_AVSYSTEM=yes ;; - no) GST_EXT_USE_EXT_AVSYSTEM=no ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-avsystem) ;; - esac - ], - [GST_EXT_USE_EXT_AVSYSTEM=yes]) -AM_CONDITIONAL(GST_EXT_USE_EXT_AVSYSTEM, test "x$GST_EXT_USE_EXT_AVSYSTEM" = "xyes") - dnl use ext-evasimagesink -------------------------------------------------------------------------- AC_ARG_ENABLE(ext-evasimagesink, AC_HELP_STRING([--enable-ext-evasimagesink], [using evasimagesink]), [ @@ -222,30 +327,40 @@ AC_ARG_ENABLE(ext-evasimagesink, AC_HELP_STRING([--enable-ext-evasimagesink], [u [GST_EXT_USE_EXT_EVASIMAGESINK=yes]) AM_CONDITIONAL(GST_EXT_USE_EXT_EVASIMAGESINK, test "x$GST_EXT_USE_EXT_EVASIMAGESINK" = "xyes") -dnl use ext-gstreamer-audio ------------------------------------------------------------------- -AC_ARG_ENABLE(ext-gstreamer-audio, AC_HELP_STRING([--enable-ext-gstreamer-audio], [using gstreamer-audio]), +dnl use evaspixmapsink --------------------------------------------------------------------------- +AC_ARG_ENABLE(ext-evaspixmapsink, AC_HELP_STRING([--enable-ext-evaspixmapsink], [using evaspixmapsink]), + [ + case "${enableval}" in + yes) GST_EXT_USE_EXT_EVASPIXMAPSINK=yes ;; + no) GST_EXT_USE_EXT_EVASPIXMAPSINK=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-evaspixmapsink) ;; + esac + ], + [GST_EXT_USE_EXT_EVASPIXMAPSINK=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_EVASPIXMAPSINK, test "x$GST_EXT_USE_EXT_EVASPIXMAPSINK" = "xyes") + +dnl use ext-xvimagesrc-------------------------------------------------------------------------- +AC_ARG_ENABLE(ext-xvimagesrc, AC_HELP_STRING([--enable-ext-xvimagesrc], [using xvimagesrc]), [ case "${enableval}" in - yes) GST_EXT_USE_EXT_AVSYSAUDIO=yes ;; - no) GST_EXT_USE_EXT_AVSYSAUDIO=no ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-gstreamer-audio) ;; + yes) GST_EXT_USE_EXT_XVIMAGESRC=yes ;; + no) GST_EXT_USE_EXT_XVIMAGESRC=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-xvimagesrc) ;; esac ], - [GST_EXT_USE_EXT_AVSYSAUDIO=yes]) -AM_CONDITIONAL(GST_EXT_USE_EXT_AVSYSAUDIO, test "x$GST_EXT_USE_EXT_AVSYSAUDIO" = "xyes") - -if test "x$GST_EXT_USE_EXT_AVSYSAUDIO" = "xyes"; then - HAVE_AVSYSAUDIO=NO - PKG_CHECK_MODULES(AVSYSAUDIO, avsysaudio, HAVE_AVSYSAUDIO="yes", [ - HAVE_AVSYSAUDIO="no" - AC_MSG_RESULT(no) - ]) - if test "x$HAVE_AVSYSAUDIO" = "xno"; then - AC_MSG_ERROR(no avsysaudio package found) - fi - AC_SUBST(AVSYSAUDIO_CFLAGS) - AC_SUBST(AVSYSAUDIO_LIBS) -fi + [GST_EXT_USE_EXT_XVIMAGESRC=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_XVIMAGESRC, test "x$GST_EXT_USE_EXT_XVIMAGESRC" = "xyes") + +AC_ARG_ENABLE(pcmdump, AC_HELP_STRING([--enable-pcmdump], [pcm dump]), + [ + case "${enableval}" in + yes) PCM_DUMP_ENABLE=yes ;; + no) PCM_DUMP_ENABLE=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pcmdump) ;; + esac + ], + [PCM_DUMP_ENABLE=no]) +AM_CONDITIONAL([PCM_DUMP_ENABLE], [test "x$PCM_DUMP_ENABLE" = "xyes"]) dnl use ext-drmsrc -------------------------------------------------------------------------- AC_ARG_ENABLE(ext-drmsrc, AC_HELP_STRING([--enable-ext-drmsrc], [using drmsrc]), @@ -258,6 +373,28 @@ AC_ARG_ENABLE(ext-drmsrc, AC_HELP_STRING([--enable-ext-drmsrc], [using drmsrc]), ], [GST_EXT_USE_EXT_DRMSRC=yes]) AM_CONDITIONAL(GST_EXT_USE_EXT_DRMSRC, test "x$GST_EXT_USE_EXT_DRMSRC" = "xyes") + +if test "x$GST_EXT_USE_EXT_DRMSRC" = "xyes"; then + PKG_CHECK_MODULES(DRM_CLIENT, drm-client) + AC_SUBST(DRM_CLIENT_CFLAGS) + AC_SUBST(DRM_CLIENT_LIBS) + PKG_CHECK_MODULES(DRM_TRUSTED, drm-trusted) + AC_SUBST(DRM_TRUSTED_CFLAGS) + AC_SUBST(DRM_TRUSTED_LIBS) +fi + +dnl use ext-submux -------------------------------------------------------------------------- +AC_ARG_ENABLE(ext-submux, AC_HELP_STRING([--enable-ext-submux], [using submux]), + [ + case "${enableval}" in + yes) GST_EXT_USE_EXT_SUBMUX=yes ;; + no) GST_EXT_USE_EXT_SUBMUX=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-submux) ;; + esac + ], + [GST_EXT_USE_EXT_SUBMUX=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_SUBMUX, test "x$GST_EXT_USE_EXT_SUBMUX" = "xyes") + dnl use ext-toggle -------------------------------------------------------------------------- AC_ARG_ENABLE(ext-toggle, AC_HELP_STRING([--enable-ext-toggle], [using toggle]), [ @@ -305,6 +442,39 @@ AC_ARG_ENABLE(ext-audiotp, AC_HELP_STRING([--enable-ext-audiotp], [using audiotp [GST_EXT_USE_EXT_AUDIOTP=yes]) AM_CONDITIONAL(GST_EXT_USE_EXT_AUDIOTP, test "x$GST_EXT_USE_EXT_AUDIOTP" = "xyes") +dnl use ext-audioeq -------------------------------------------------------------------------- +AC_ARG_ENABLE(ext-audioeq, AC_HELP_STRING([--enable-ext-audioeq], [using audioeq]), +[ + case "${enableval}" in + yes) GST_EXT_USE_EXT_AUDIOEQ=yes ;; + no) GST_EXT_USE_EXT_AUDIOEQ=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-audioeq) ;; + esac + ], + [GST_EXT_USE_EXT_AUDIOEQ=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_AUDIOEQ, test "x$GST_EXT_USE_EXT_AUDIOEQ" = "xyes") + +dnl use ext-piffdemux -------------------------------------------------------------------------- +AC_ARG_ENABLE(ext-piffdemux, AC_HELP_STRING([--enable-ext-piffdemux], [using piffdemux]), +[ + case "${enableval}" in + yes) GST_EXT_USE_EXT_PIFFDEMUX=yes ;; + no) GST_EXT_USE_EXT_PIFFDEMUX=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-piffdemux) ;; + esac + ], + [GST_EXT_USE_EXT_PIFFDEMUX=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_PIFFDEMUX, test "x$GST_EXT_USE_EXT_PIFFDEMUX" = "xyes") + +#if test "x$GST_EXT_USE_EXT_PIFFDEMUX" = "xyes"; then +# PKG_CHECK_MODULES(DRM_CLIENT, drm-client) +# AC_SUBST(DRM_CLIENT_CFLAGS) +# AC_SUBST(DRM_CLIENT_LIBS) +# PKG_CHECK_MODULES(DRM_TRUSTED, drm-trusted) +# AC_SUBST(DRM_TRUSTED_CFLAGS) +# AC_SUBST(DRM_TRUSTED_LIBS) +#fi + dnl use ext-ssdemux -------------------------------------------------------------------------- AC_ARG_ENABLE(ext-ssdemux, AC_HELP_STRING([--enable-ext-ssdemux], [using ssdemux]), [ @@ -317,25 +487,66 @@ AC_ARG_ENABLE(ext-ssdemux, AC_HELP_STRING([--enable-ext-ssdemux], [using ssdemux [GST_EXT_USE_EXT_SSDEMUX=yes]) AM_CONDITIONAL(GST_EXT_USE_EXT_SSDEMUX, test "x$GST_EXT_USE_EXT_SSDEMUX" = "xyes") +dnl use ext-dashdemux ----------------------------------------------------------------------- +AC_ARG_ENABLE(ext-dashdemux, AC_HELP_STRING([--enable-ext-dashdemux], [using dashdemux]), + [ + case "${enableval}" in + yes) GST_EXT_USE_EXT_DASHDEMUX=yes ;; + no) GST_EXT_USE_EXT_DASHDEMUX=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-dashdemux) ;; + esac + ], + [GST_EXT_USE_EXT_DASHDEMUX=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_DASHDEMUX, test "x$GST_EXT_USE_EXT_DASHDEMUX" = "xyes") + +dnl use ext-hlsdemux2 ----------------------------------------------------------------------- +AC_ARG_ENABLE(ext-hlsdemux2, AC_HELP_STRING([--enable-ext-hlsdemux2], [using hlsdemux2]), + [ + case "${enableval}" in + yes) GST_EXT_USE_EXT_HLSDEMUX2=yes ;; + no) GST_EXT_USE_EXT_HLSDEMUX2=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ext-hlsdemux2) ;; + esac + ] + [GST_EXT_USE_EXT_HLSDEMUX2=yes]) +AM_CONDITIONAL(GST_EXT_USE_EXT_HLSDEMUX2, test "x$GST_EXT_USE_EXT_HLSDEMUX2" = "xyes") + +if test "x$GST_EXT_USE_EXT_HLSDEMUX2" = "xyes"; then + dnl crypto for hlsdemux + PKG_CHECK_MODULES(CRYPTO, libcrypto) + AC_SUBST(CRYPTO_CFLAGS) + AC_SUBST(CRYPTO_LIBS) +fi AC_OUTPUT( Makefile common/Makefile common/m4/Makefile -avsystem/Makefile pdpushsrc/Makefile pdpushsrc/src/Makefile -avsystem/src/Makefile encodebin/Makefile encodebin/src/Makefile evasimagesink/Makefile evasimagesink/src/Makefile +evaspixmapsink/Makefile +xvimagesrc/Makefile +xvimagesrc/src/Makefile toggle/Makefile toggle/src/Makefile drmsrc/Makefile drmsrc/src/Makefile +submux/Makefile +submux/src/Makefile audiotp/Makefile audiotp/src/Makefile +audioeq/Makefile +audioeq/src/Makefile +piffdemux/Makefile +piffdemux/src/Makefile ssdemux/Makefile ssdemux/src/Makefile +dashdemux/Makefile +dashdemux/src/Makefile +hlsdemux2/Makefile +hlsdemux2/src/Makefile ) diff --git a/dashdemux/Makefile.am b/dashdemux/Makefile.am new file mode 100755 index 0000000..308a09c --- /dev/null +++ b/dashdemux/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/dashdemux/src/Makefile.am b/dashdemux/src/Makefile.am new file mode 100644 index 0000000..643c7b2 --- /dev/null +++ b/dashdemux/src/Makefile.am @@ -0,0 +1,25 @@ + +plugin_LTLIBRARIES = libgstdashdemux.la + +libgstdashdemux_la_SOURCES = \ + gstmpdparser.c \ + gstdashdemux.c \ + gstfragment.c \ + gsturidownloader.c \ + gstdownloadrate.c \ + gstplugin.c + +# headers we need but don't want installed +noinst_HEADERS = \ + gstmpdparser.h \ + gstfragmented.h \ + gstfragment.h \ + gstdashdemux.h \ + gsturidownloader.h \ + gstdownloadrate.h + +# compiler and linker flags used to compile this plugin, set in configure.ac +libgstdashdemux_la_CFLAGS = $(GST_CFLAGS) +libgstdashdemux_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) +libgstdashdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdashdemux_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/dashdemux/src/Makefile.in b/dashdemux/src/Makefile.in new file mode 100644 index 0000000..7d81648 --- /dev/null +++ b/dashdemux/src/Makefile.in @@ -0,0 +1,605 @@ +# Makefile.in generated by automake 1.11.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(plugindir)" +LTLIBRARIES = $(plugin_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgstdashdemux_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_libgstdashdemux_la_OBJECTS = libgstdashdemux_la-gstmpdparser.lo \ + libgstdashdemux_la-gstdashdemux.lo \ + libgstdashdemux_la-gstfragment.lo \ + libgstdashdemux_la-gsturidownloader.lo \ + libgstdashdemux_la-gstplugin.lo +libgstdashdemux_la_OBJECTS = $(am_libgstdashdemux_la_OBJECTS) +libgstdashdemux_la_LINK = $(LIBTOOL) --tag=CC \ + $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) \ + $(libgstdashdemux_la_LDFLAGS) $(LDFLAGS) -o $@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(libgstdashdemux_la_SOURCES) +DIST_SOURCES = $(libgstdashdemux_la_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +GST_CFLAGS = @GST_CFLAGS@ +GST_LIBS = @GST_LIBS@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +HAVE_PKGCONFIG = @HAVE_PKGCONFIG@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +plugin_LTLIBRARIES = libgstdashdemux.la +libgstdashdemux_la_SOURCES = \ + gstmpdparser.c \ + gstdashdemux.c \ + gstfragment.c \ + gsturidownloader.c \ + gstplugin.c + + +# headers we need but don't want installed +noinst_HEADERS = \ + gstmpdparser.h \ + gstfragmented.h \ + gstfragment.h \ + gstdashdemux.h \ + gsturidownloader.h + + +# compiler and linker flags used to compile this plugin, set in configure.ac +libgstdashdemux_la_CFLAGS = $(GST_CFLAGS) +libgstdashdemux_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) +libgstdashdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdashdemux_la_LIBTOOLFLAGS = --tag=disable-static +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(plugindir)" || $(MKDIR_P) "$(DESTDIR)$(plugindir)" + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(plugindir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ + } + +uninstall-pluginLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ + done + +clean-pluginLTLIBRARIES: + -test -z "$(plugin_LTLIBRARIES)" || rm -f $(plugin_LTLIBRARIES) + @list='$(plugin_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libgstdashdemux.la: $(libgstdashdemux_la_OBJECTS) $(libgstdashdemux_la_DEPENDENCIES) $(EXTRA_libgstdashdemux_la_DEPENDENCIES) + $(libgstdashdemux_la_LINK) -rpath $(plugindir) $(libgstdashdemux_la_OBJECTS) $(libgstdashdemux_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstdashdemux_la-gstdashdemux.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstdashdemux_la-gstfragment.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstdashdemux_la-gstmpdparser.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstdashdemux_la-gstplugin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstdashdemux_la-gsturidownloader.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libgstdashdemux_la-gstmpdparser.lo: gstmpdparser.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -MT libgstdashdemux_la-gstmpdparser.lo -MD -MP -MF $(DEPDIR)/libgstdashdemux_la-gstmpdparser.Tpo -c -o libgstdashdemux_la-gstmpdparser.lo `test -f 'gstmpdparser.c' || echo '$(srcdir)/'`gstmpdparser.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libgstdashdemux_la-gstmpdparser.Tpo $(DEPDIR)/libgstdashdemux_la-gstmpdparser.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstmpdparser.c' object='libgstdashdemux_la-gstmpdparser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -c -o libgstdashdemux_la-gstmpdparser.lo `test -f 'gstmpdparser.c' || echo '$(srcdir)/'`gstmpdparser.c + +libgstdashdemux_la-gstdashdemux.lo: gstdashdemux.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -MT libgstdashdemux_la-gstdashdemux.lo -MD -MP -MF $(DEPDIR)/libgstdashdemux_la-gstdashdemux.Tpo -c -o libgstdashdemux_la-gstdashdemux.lo `test -f 'gstdashdemux.c' || echo '$(srcdir)/'`gstdashdemux.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libgstdashdemux_la-gstdashdemux.Tpo $(DEPDIR)/libgstdashdemux_la-gstdashdemux.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstdashdemux.c' object='libgstdashdemux_la-gstdashdemux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -c -o libgstdashdemux_la-gstdashdemux.lo `test -f 'gstdashdemux.c' || echo '$(srcdir)/'`gstdashdemux.c + +libgstdashdemux_la-gstfragment.lo: gstfragment.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -MT libgstdashdemux_la-gstfragment.lo -MD -MP -MF $(DEPDIR)/libgstdashdemux_la-gstfragment.Tpo -c -o libgstdashdemux_la-gstfragment.lo `test -f 'gstfragment.c' || echo '$(srcdir)/'`gstfragment.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libgstdashdemux_la-gstfragment.Tpo $(DEPDIR)/libgstdashdemux_la-gstfragment.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstfragment.c' object='libgstdashdemux_la-gstfragment.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -c -o libgstdashdemux_la-gstfragment.lo `test -f 'gstfragment.c' || echo '$(srcdir)/'`gstfragment.c + +libgstdashdemux_la-gsturidownloader.lo: gsturidownloader.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -MT libgstdashdemux_la-gsturidownloader.lo -MD -MP -MF $(DEPDIR)/libgstdashdemux_la-gsturidownloader.Tpo -c -o libgstdashdemux_la-gsturidownloader.lo `test -f 'gsturidownloader.c' || echo '$(srcdir)/'`gsturidownloader.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libgstdashdemux_la-gsturidownloader.Tpo $(DEPDIR)/libgstdashdemux_la-gsturidownloader.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gsturidownloader.c' object='libgstdashdemux_la-gsturidownloader.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -c -o libgstdashdemux_la-gsturidownloader.lo `test -f 'gsturidownloader.c' || echo '$(srcdir)/'`gsturidownloader.c + +libgstdashdemux_la-gstplugin.lo: gstplugin.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -MT libgstdashdemux_la-gstplugin.lo -MD -MP -MF $(DEPDIR)/libgstdashdemux_la-gstplugin.Tpo -c -o libgstdashdemux_la-gstplugin.lo `test -f 'gstplugin.c' || echo '$(srcdir)/'`gstplugin.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libgstdashdemux_la-gstplugin.Tpo $(DEPDIR)/libgstdashdemux_la-gstplugin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='gstplugin.c' object='libgstdashdemux_la-gstplugin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(libgstdashdemux_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstdashdemux_la_CFLAGS) $(CFLAGS) -c -o libgstdashdemux_la-gstplugin.lo `test -f 'gstplugin.c' || echo '$(srcdir)/'`gstplugin.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(plugindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pluginLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pluginLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pluginLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-pluginLTLIBRARIES ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pluginLTLIBRARIES \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-pluginLTLIBRARIES + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/dashdemux/src/glibcompat.h b/dashdemux/src/glibcompat.h new file mode 100755 index 0000000..3f8951c --- /dev/null +++ b/dashdemux/src/glibcompat.h @@ -0,0 +1,48 @@ +/* + * glibcompat.h + * + * Copyright (C) 2013 Douglas Gore + * + * Authors: + * Douglas Gore + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GLIBCOMPAT_H_ +#define GLIBCOMPAT_H_ + +/* The GLib threading API changed in 2.32, these macros are used to + * provide backwards compatibility with older versions without + * complicating the code. */ + +#include + +#if (GLIB_CHECK_VERSION (2, 32, 0)) + #define G_MUTEX GMutex + #define G_MUTEX_INIT(mutex) g_mutex_init(&mutex) + #define G_MUTEX_CLEAR(mutex) g_mutex_clear (&mutex); + #define G_MUTEX_LOCK(mutex) g_mutex_lock (&mutex) + #define G_MUTEX_UNLOCK(mutex) g_mutex_unlock (&mutex) +#else + #define G_MUTEX GMutex* + #define G_MUTEX_INIT(mutex) mutex = g_mutex_new() + #define G_MUTEX_CLEAR(mutex) g_mutex_free (mutex) + #define G_MUTEX_LOCK(mutex) g_mutex_lock (mutex) + #define G_MUTEX_UNLOCK(mutex) g_mutex_unlock (mutex) +#endif + +#endif /* GLIBCOMPAT_H_ */ diff --git a/dashdemux/src/gstdashdemux.c b/dashdemux/src/gstdashdemux.c new file mode 100755 index 0000000..ba6fe2b --- /dev/null +++ b/dashdemux/src/gstdashdemux.c @@ -0,0 +1,1902 @@ +/* + * DASH demux plugin for GStreamer + * + * gstdashdemux.c + * + * Copyright (C) 2012 Orange + * + * Authors: + * David Corvoysier + * Hamid Zakari + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-dashdemux + * + * DASH demuxer element. + * Example launch line + * |[ + * gst-launch playbin2 uri="http://www-itec.uni-klu.ac.at/ftp/datasets/mmsys12/RedBullPlayStreets/redbull_4s/RedBullPlayStreets_4s_isoffmain_DIS_23009_1_v_2_1c2_2011_08_30.mpd" + * ]| + */ + +/* Implementation notes: + * + * The following section describes how dashdemux works internally. + * + * Introduction: + * + * dashdemux is a "fake" demux, as unlike traditional demux elements, it + * doesn't split data streams contained in an enveloppe to expose them + * to downstream decoding elements. + * + * Instead, it parses an XML file called a manifest to identify a set of + * individual stream fragments it needs to fetch and expose to the actual + * demux elements that will handle them (this behavior is sometimes + * referred as the "demux after a demux" scenario). + * + * For a given section of content, several representations corresponding + * to different bitrates may be available: dashdemux will select the most + * appropriate representation based on local conditions (typically the + * available bandwidth and the amount of buffering available, capped by + * a maximum allowed bitrate). + * + * The representation selection algorithm can be configured using + * specific properties: max bitrate, min/max buffering, bandwidth ratio. + * + * + * General Design: + * + * dashdemux has a single sink pad that accepts the data corresponding + * to the manifest, typically fetched from an HTTP or file source. + * + * dashdemux exposes the streams it recreates based on the fragments it + * fetches through dedicated src pads corresponding to the caps of the + * fragments container (ISOBMFF/MP4 or MPEG2TS). + * + * During playback, new representations will typically be exposed as a + * new set of pads (see 'Switching between representations' below). + * + * Fragments downloading is performed using a dedicated task that fills + * an internal queue. Another task is in charge of popping fragments + * from the queue and pushing them downstream. + * + * Switching between representations: + * + * Decodebin supports scenarios allowing to seamlessly switch from one + * stream to another inside the same "decoding chain". + * + * To achieve that, it combines the elements it autoplugged in chains + * and groups, allowing only one decoding group to be active at a given + * time for a given chain. + * + * A chain can signal decodebin that it is complete by sending a + * no-more-pads event, but even after that new pads can be added to + * create new subgroups, providing that a new no-more-pads event is sent. + * + * We take advantage of that to dynamically create a new decoding group + * in order to select a different representation during playback. + * + * Typically, assuming that each fragment contains both audio and video, + * the following tree would be created: + * + * chain "DASH Demux" + * |_ group "Representation set 1" + * | |_ chain "Qt Demux 0" + * | |_ group "Stream 0" + * | |_ chain "H264" + * | |_ chain "AAC" + * |_ group "Representation set 2" + * |_ chain "Qt Demux 1" + * |_ group "Stream 1" + * |_ chain "H264" + * |_ chain "AAC" + * + * Or, if audio and video are contained in separate fragments: + * + * chain "DASH Demux" + * |_ group "Representation set 1" + * | |_ chain "Qt Demux 0" + * | | |_ group "Stream 0" + * | | |_ chain "H264" + * | |_ chain "Qt Demux 1" + * | |_ group "Stream 1" + * | |_ chain "AAC" + * |_ group "Representation set 2" + * |_ chain "Qt Demux 3" + * | |_ group "Stream 2" + * | |_ chain "H264" + * |_ chain "Qt Demux 4" + * |_ group "Stream 3" + * |_ chain "AAC" + * + * In both cases, when switching from Set 1 to Set 2 an EOS is sent on + * each end pad corresponding to Rep 0, triggering the "drain" state to + * propagate upstream. + * Once both EOS have been processed, the "Set 1" group is completely + * drained, and decodebin2 will switch to the "Set 2" group. + * + * Note: nothing can be pushed to the new decoding group before the + * old one has been drained, which means that in order to be able to + * adapt quickly to bandwidth changes, we will not be able to rely + * on downstream buffering, and will instead manage an internal queue. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include +#include +#include +#include "gstdashdemux.h" + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src%d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/dash+xml")); + +GST_DEBUG_CATEGORY_STATIC (gst_dash_demux_debug); +#define GST_CAT_DEFAULT gst_dash_demux_debug + +enum +{ + PROP_0, + + PROP_MAX_BUFFERING_TIME, + PROP_BANDWIDTH_USAGE, + PROP_MAX_BITRATE, + PROP_LAST +}; + +/* Default values for properties */ +#define DEFAULT_MAX_BUFFERING_TIME 30 /* in seconds */ +#define DEFAULT_BANDWIDTH_USAGE 0.8 /* 0 to 1 */ +#define DEFAULT_MAX_BITRATE 24000000 /* in bit/s */ + +#define DEFAULT_FAILED_COUNT 3 +#define DOWNLOAD_RATE_HISTORY_MAX 3 +#define DOWNLOAD_RATE_TIME_MAX 3 * GST_SECOND + +/* Custom internal event to signal end of period */ +#define GST_EVENT_DASH_EOP GST_EVENT_MAKE_TYPE(81, GST_EVENT_TYPE_DOWNSTREAM | GST_EVENT_TYPE_SERIALIZED) +static GstEvent * +gst_event_new_dash_eop (void) +{ + return gst_event_new_custom (GST_EVENT_DASH_EOP, NULL); +} + + +/* GObject */ +static void gst_dash_demux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_dash_demux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_dash_demux_dispose (GObject * obj); + +/* GstElement */ +static GstStateChangeReturn +gst_dash_demux_change_state (GstElement * element, GstStateChange transition); + +/* GstDashDemux */ +static GstFlowReturn gst_dash_demux_pad (GstPad * pad, GstBuffer * buf); +static gboolean gst_dash_demux_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_dash_demux_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_dash_demux_src_query (GstPad * pad, GstQuery * query); +static void gst_dash_demux_stream_loop (GstDashDemux * demux); +static void gst_dash_demux_download_loop (GstDashDemux * demux); +static void gst_dash_demux_stop (GstDashDemux * demux); +static void gst_dash_demux_resume_stream_task (GstDashDemux * demux); +static void gst_dash_demux_resume_download_task (GstDashDemux * demux); +static gboolean gst_dash_demux_setup_all_streams (GstDashDemux * demux); +static gboolean gst_dash_demux_select_representations (GstDashDemux * demux); +static GstCaps *gst_dash_demux_get_input_caps (GstDashDemux * demux, GstActiveStream * stream); +static gboolean gst_dash_demux_get_next_fragment (GstDashDemux * demux, GstActiveStream **fragment_stream, GstClockTime *selected_ts); + +static void gst_dash_demux_clear_streams(GstDashDemux * demux); +static void gst_dash_demux_reset (GstDashDemux * demux, gboolean dispose); +static GstClockTime gst_dash_demux_get_buffering_time (GstDashDemux * demux); + +static void +_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (gst_dash_demux_debug, "dashdemux", 0, + "dashdemux element"); +} + +GST_BOILERPLATE_FULL (GstDashDemux, gst_dash_demux, GstElement, + GST_TYPE_ELEMENT, _do_init); + +static void +gst_dash_demux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + + gst_element_class_set_details_simple (element_class, + "DASH Demuxer", + "Codec/Demuxer", + "Dynamic Adaptive Streaming over HTTP demuxer", + "David Corvoysier \n\ + Hamid Zakari \n\ + Gianluca Gennari "); +} + +static void +gst_dash_demux_dispose (GObject * obj) +{ + GstDashDemux *demux = GST_DASH_DEMUX (obj); + + if (demux->stream_task) { + if (GST_TASK_STATE (demux->stream_task) != GST_TASK_STOPPED) { + GST_DEBUG_OBJECT (demux, "Leaving streaming task"); + gst_task_stop (demux->stream_task); + gst_task_join (demux->stream_task); + } + gst_object_unref (demux->stream_task); + g_static_rec_mutex_free (&demux->stream_lock); + g_mutex_free(demux->stream_timed_lock); + demux->stream_task = NULL; + } + + if (demux->download_task) { + if (GST_TASK_STATE (demux->download_task) != GST_TASK_STOPPED) { + GST_DEBUG_OBJECT (demux, "Leaving download task"); + gst_task_stop (demux->download_task); + gst_task_join (demux->download_task); + } + gst_object_unref (demux->download_task); + g_static_rec_mutex_free (&demux->download_lock); + demux->download_task = NULL; + } + + g_cond_clear (&demux->download_cond); + g_mutex_clear (&demux->download_mutex); + + if (demux->downloader != NULL) { + g_object_unref (demux->downloader); + demux->downloader = NULL; + } + + gst_dash_demux_reset (demux, TRUE); + + G_OBJECT_CLASS (parent_class)->dispose (obj); +} + +static void +gst_dash_demux_class_init (GstDashDemuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_dash_demux_set_property; + gobject_class->get_property = gst_dash_demux_get_property; + gobject_class->dispose = gst_dash_demux_dispose; + + g_object_class_install_property (gobject_class, PROP_MAX_BUFFERING_TIME, + g_param_spec_uint ("max-buffering-time", "Maximum buffering time", + "Maximum number of seconds of buffer accumulated during playback", + 2, G_MAXUINT, DEFAULT_MAX_BUFFERING_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BANDWIDTH_USAGE, + g_param_spec_float ("bandwidth-usage", + "Bandwidth usage [0..1]", + "Percentage of the available bandwidth to use when selecting representations", + 0, 1, DEFAULT_BANDWIDTH_USAGE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_MAX_BITRATE, + g_param_spec_uint ("max-bitrate", "Max bitrate", + "Max of bitrate supported by target decoder", + 1000, G_MAXUINT, DEFAULT_MAX_BITRATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_dash_demux_change_state); +} + +static gboolean +_check_queue_full (GstDataQueue * q, guint visible, guint bytes, guint64 time, + GstDashDemux *demux) +{ + return time > demux->max_buffering_time; +} + +static void +_data_queue_item_destroy (GstDataQueueItem * item) +{ + gst_mini_object_unref (item->object); + g_free (item); +} + +static void +gst_dash_demux_stream_push_event (GstDashDemuxStream * stream, + GstEvent *event) +{ + GstDataQueueItem *item = g_new0 (GstDataQueueItem, 1); + + item->object = GST_MINI_OBJECT_CAST (event); + item->destroy = (GDestroyNotify) _data_queue_item_destroy; + + gst_data_queue_push (stream->queue, item); +} + +static void +gst_dash_demux_stream_push_data (GstDashDemuxStream * stream, + GstBuffer * buffer) +{ + GstDataQueueItem *item = g_new0 (GstDataQueueItem, 1); + + item->object = GST_MINI_OBJECT_CAST (buffer); + item->duration = GST_BUFFER_DURATION (buffer); + item->visible = TRUE; + item->size = GST_BUFFER_SIZE (buffer); + + item->destroy = (GDestroyNotify) _data_queue_item_destroy; + + gst_data_queue_push (stream->queue, item); +} + +static void +gst_dash_demux_init (GstDashDemux * demux, GstDashDemuxClass * klass) +{ + /* sink pad */ + demux->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + gst_pad_set_chain_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_dash_demux_pad)); + gst_pad_set_event_function (demux->sinkpad, + GST_DEBUG_FUNCPTR (gst_dash_demux_sink_event)); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + /* Downloader */ + demux->downloader = gst_uri_downloader_new (); + + /* Properties */ + demux->max_buffering_time = DEFAULT_MAX_BUFFERING_TIME * GST_SECOND; + demux->bandwidth_usage = DEFAULT_BANDWIDTH_USAGE; + demux->max_bitrate = DEFAULT_MAX_BITRATE; + + demux->max_video_width = 0; + demux->max_video_height = 0; + + /* Updates task */ + g_static_rec_mutex_init (&demux->download_lock); + demux->download_task = + gst_task_create ((GstTaskFunction) gst_dash_demux_download_loop, demux); + gst_task_set_lock (demux->download_task, &demux->download_lock); + g_cond_init (&demux->download_cond); + g_mutex_init (&demux->download_mutex); + + /* Streaming task */ + g_static_rec_mutex_init (&demux->stream_lock); + demux->stream_task = + gst_task_create ((GstTaskFunction) gst_dash_demux_stream_loop, demux); + gst_task_set_lock (demux->stream_task, &demux->stream_lock); + demux->stream_timed_lock = g_mutex_new (); +} + +static void +gst_dash_demux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstDashDemux *demux = GST_DASH_DEMUX (object); + + switch (prop_id) { + case PROP_MAX_BUFFERING_TIME: + demux->max_buffering_time = g_value_get_uint (value) * GST_SECOND; + break; + case PROP_BANDWIDTH_USAGE: + demux->bandwidth_usage = g_value_get_float (value); + break; + case PROP_MAX_BITRATE: + demux->max_bitrate = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dash_demux_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstDashDemux *demux = GST_DASH_DEMUX (object); + + switch (prop_id) { + case PROP_MAX_BUFFERING_TIME: + g_value_set_uint (value, demux->max_buffering_time / GST_SECOND); + break; + case PROP_BANDWIDTH_USAGE: + g_value_set_float (value, demux->bandwidth_usage); + break; + case PROP_MAX_BITRATE: + g_value_set_uint (value, demux->max_bitrate); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_dash_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstDashDemux *demux = GST_DASH_DEMUX (element); + + GST_DEBUG_OBJECT (demux, "changing state %s - %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: + gst_dash_demux_reset (demux, FALSE); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + demux->cancelled = TRUE; + gst_dash_demux_stop (demux); + gst_task_join (demux->stream_task); + gst_task_join (demux->download_task); + break; + default: + break; + } + return ret; +} + +void +gst_dash_demux_flush_stream_queues (GstDashDemux * demux) +{ + GSList *it; + GstDashDemuxStream *stream; + for(it = demux->streams; it; it=it->next) + { + stream = it->data; + gst_data_queue_set_flushing(stream->queue, TRUE); + gst_data_queue_flush(stream->queue); + } +} + +static gboolean +gst_dash_demux_src_event (GstPad * pad, GstEvent * event) +{ + GstDashDemux *demux; + + demux = GST_DASH_DEMUX (gst_pad_get_element_private (pad)); + + switch (event->type) { + case GST_EVENT_SEEK: + { + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + GList *list; + GstClockTime current_pos = GST_CLOCK_TIME_NONE; + GstClockTime target_pos; + guint current_period; + GstActiveStream *stream; + GstStreamPeriod *period = NULL; + guint nb_active_stream; + guint stream_idx = 0; + guint *seek_idx = NULL; /*Seek positions on each stream*/ + gboolean end_of_mpd = FALSE; + + if (gst_mpd_client_is_live (demux->client)) { + GST_WARNING_OBJECT (demux, "Received seek event for live stream"); + return FALSE; + } + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, + &stop_type, &stop); + + if (format != GST_FORMAT_TIME) + return FALSE; + + GST_DEBUG_OBJECT (demux, + "seek event, rate: %f type: %d start: %" GST_TIME_FORMAT " stop: %" + GST_TIME_FORMAT, rate, start_type, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop)); + + //GST_MPD_CLIENT_LOCK (demux->client); + + /* select the requested Period in the Media Presentation */ + target_pos = (GstClockTime) start; + current_period = 0; + for (list = g_list_first (demux->client->periods); list; + list = g_list_next (list)) { + period = list->data; + current_pos = period->start; + current_period = period->number; + if (current_pos <= target_pos + && target_pos < current_pos + period->duration) { + break; + } + } + if(target_pos == current_pos + period->duration) { + /*Seeking to the end of MPD*/ + end_of_mpd = TRUE; + goto seeking; + } + if (list == NULL) { + GST_WARNING_OBJECT (demux, "Could not find seeked Period"); + return FALSE; + } + if (current_period != gst_mpd_client_get_period_index (demux->client)) { + GST_DEBUG_OBJECT (demux, "Seeking to Period %d", current_period); + /* setup video, audio and subtitle streams, starting from the new Period */ + if (!gst_mpd_client_set_period_index (demux->client, current_period) || + !gst_dash_demux_setup_all_streams (demux)) + return FALSE; + } + + /*select the requested segments for all streams*/ + nb_active_stream = gst_mpdparser_get_nb_active_stream (demux->client); + seek_idx = g_malloc0(sizeof(gint)*nb_active_stream); + gint video_idx = gst_mpd_client_get_video_active_stream_id(demux->client); + if(video_idx >= 0) { + /*Seeking on video stream firstly.*/ + GstClockTime segment_start; + segment_start = gst_mpd_client_stream_find_segment(demux->client, video_idx, + target_pos, &seek_idx[video_idx]); + if(!GST_CLOCK_TIME_IS_VALID(segment_start)) + goto no_segment; + target_pos = segment_start; + } + /*Seeking on non video streams*/ + for (stream_idx = 0; stream_idx < nb_active_stream; stream_idx++) { + if (video_idx != stream_idx) { + GstClockTime stream_start = gst_mpd_client_stream_find_segment(demux->client, + stream_idx, target_pos, &seek_idx[stream_idx]); + if(!GST_CLOCK_TIME_IS_VALID (stream_start)) { + goto no_segment; + } + } + } + +seeking: + /* We can actually perform the seek */ + nb_active_stream = gst_mpdparser_get_nb_active_stream (demux->client); + + if (flags & GST_SEEK_FLAG_FLUSH) { + GST_DEBUG_OBJECT (demux, "sending flush start"); + stream_idx = 0; + while (stream_idx < nb_active_stream) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, stream_idx); + dash_stream->need_header = TRUE; + gst_pad_push_event (dash_stream->srcpad, + gst_event_new_flush_start ()); + stream_idx++; + } + } + + /* Stop the demux */ + demux->cancelled = TRUE; + gst_dash_demux_stop (demux); + GST_DEBUG_OBJECT (demux, "joining tasks"); + gst_task_join (demux->stream_task); + gst_task_join (demux->download_task); + GST_DEBUG_OBJECT (demux, "tasks was joined"); + + /* Wait for streaming to finish */ + g_static_rec_mutex_lock (&demux->stream_lock); + + //GST_MPD_CLIENT_LOCK (demux->client); + demux->end_of_period = end_of_mpd; + //GST_MPD_CLIENT_UNLOCK (demux->client); + + + for (stream_idx = 0; stream_idx < nb_active_stream; stream_idx++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, stream_idx); + GstCaps *caps = gst_pad_get_negotiated_caps (dash_stream->srcpad); + if(caps) { + gst_caps_replace (&dash_stream->input_caps, NULL); + gst_caps_unref (caps); + } + if(!end_of_mpd) { + GST_DEBUG_OBJECT (demux, "Seeking to sequence %d on stream %d", seek_idx[stream_idx], stream_idx); + stream = gst_mpdparser_get_active_stream_by_index (demux->client, stream_idx); + gst_mpd_client_set_segment_index(stream, seek_idx[stream_idx]); + } + gst_data_queue_set_flushing(dash_stream->queue, FALSE); + dash_stream->start_time = target_pos; + dash_stream->download_end_of_period = end_of_mpd; + dash_stream->stream_end_of_period = end_of_mpd; + dash_stream->stream_eos = end_of_mpd; + dash_stream->need_segment = TRUE; + } + if(!end_of_mpd) + g_free(seek_idx); + + if (flags & GST_SEEK_FLAG_FLUSH) { + GST_DEBUG_OBJECT (demux, "Sending flush stop on all pad"); + + for (stream_idx = 0; stream_idx < nb_active_stream; stream_idx++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, stream_idx); + gst_pad_push_event (dash_stream->srcpad, + gst_event_new_flush_stop ()); + } + } + + /* Restart the demux */ + demux->cancelled = FALSE; + gst_dash_demux_resume_download_task (demux); + gst_dash_demux_resume_stream_task (demux); + g_static_rec_mutex_unlock (&demux->stream_lock); + + return TRUE; +no_segment: + { + GST_WARNING_OBJECT (demux, "Could not find seeked fragment on stream %d", stream_idx); + g_free(seek_idx); + return FALSE; + } + } + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +static gboolean +gst_dash_demux_setup_mpdparser_streams (GstDashDemux * demux, GstMpdClient *client) +{ + GList *listLang = NULL; + guint i, nb_audio; + gchar *lang; + + GST_MPD_CLIENT_LOCK (client); + /* clean old active stream list, if any */ + gst_active_streams_free (client); + + if (!gst_mpd_client_setup_streaming (client, GST_STREAM_VIDEO, "")) { + GST_INFO_OBJECT (demux, "No video adaptation set found"); + } else { + gst_mpd_client_get_max_video_dimensions(client, &demux->max_video_width, + &demux->max_video_height); + } + + nb_audio = + gst_mpdparser_get_list_and_nb_of_audio_language (client, + &listLang); + if (nb_audio == 0) + nb_audio = 1; + GST_INFO_OBJECT (demux, "Number of language is=%d", nb_audio); + + for (i = 0; i < nb_audio; i++) { + lang = (gchar *) g_list_nth_data (listLang, i); + if (gst_mpdparser_get_nb_adaptationSet (client) > 1) + if (!gst_mpd_client_setup_streaming (client, GST_STREAM_AUDIO, + lang)) + GST_INFO_OBJECT (demux, "No audio adaptation set found"); + + if (gst_mpdparser_get_nb_adaptationSet (client) > nb_audio) + if (!gst_mpd_client_setup_streaming (client, + GST_STREAM_APPLICATION, lang)) + GST_INFO_OBJECT (demux, "No application adaptation set found"); + } + GST_MPD_CLIENT_UNLOCK (client); + return TRUE; +} + +static gboolean +gst_dash_demux_setup_all_streams (GstDashDemux * demux) +{ + guint i; + if( !gst_dash_demux_setup_mpdparser_streams(demux, demux->client)) + return FALSE; + + GST_DEBUG_OBJECT (demux, "Creating dashdemux streams"); + gst_dash_demux_clear_streams(demux); + for ( i =0; i < gst_mpdparser_get_nb_active_stream (demux->client); i++) { + GstDashDemuxStream *dash_stream; + GstCaps *caps; + GstActiveStream *active_stream; + dash_stream = g_new0(GstDashDemuxStream, 1); + demux->streams = g_slist_append(demux->streams, dash_stream); + dash_stream->idx = i; + dash_stream->queue = gst_data_queue_new ((GstDataQueueCheckFullFunction) _check_queue_full, demux); + dash_stream->need_header = TRUE; + dash_stream->need_segment = TRUE; + dash_stream->start_time = GST_CLOCK_TIME_NONE; + gst_download_rate_init (&dash_stream->dnl_rate); + gst_download_rate_set_max_length (&dash_stream->dnl_rate, + DOWNLOAD_RATE_HISTORY_MAX); + gst_download_rate_set_aver_period (&dash_stream->dnl_rate, + DOWNLOAD_RATE_TIME_MAX); + /*Create stream pad*/ + active_stream = gst_mpdparser_get_active_stream_by_index(demux->client, i); + caps = gst_dash_demux_get_input_caps(demux, active_stream); + dash_stream->srcpad = gst_pad_new_from_static_template (&srctemplate, NULL); + gst_pad_set_event_function (dash_stream->srcpad, + GST_DEBUG_FUNCPTR (gst_dash_demux_src_event)); + gst_pad_set_query_function (dash_stream->srcpad, + GST_DEBUG_FUNCPTR (gst_dash_demux_src_query)); + gst_pad_set_element_private (dash_stream->srcpad, demux); + gst_pad_set_active (dash_stream->srcpad, TRUE); + gst_pad_set_caps (dash_stream->srcpad, caps); + gst_caps_unref(caps); + gst_element_add_pad (GST_ELEMENT (demux), gst_object_ref (dash_stream->srcpad)); + } + /* Send 'no-more-pads' to have decodebin create the new group */ + gst_element_no_more_pads (GST_ELEMENT (demux)); + + return TRUE; +} + +static gboolean +gst_dash_demux_sink_event (GstPad * pad, GstEvent * event) +{ + GstDashDemux *demux = GST_DASH_DEMUX (gst_pad_get_parent (pad)); + + switch (event->type) { + case GST_EVENT_EOS:{ + gchar *manifest; + GstQuery *query; + gboolean res; + + if (demux->manifest == NULL) { + GST_WARNING_OBJECT (demux, "Received EOS without a manifest."); + break; + } + + GST_DEBUG_OBJECT (demux, "Got EOS on the sink pad: manifest fetched"); + + if (demux->client) + gst_mpd_client_free (demux->client); + demux->client = gst_mpd_client_new (); + + query = gst_query_new_uri (); + res = gst_pad_peer_query (pad, query); + if (res) { + gst_query_parse_uri (query, &demux->client->mpd_uri); + GST_DEBUG_OBJECT (demux, "Fetched MPD file at URI: %s", + demux->client->mpd_uri); + } else { + GST_WARNING_OBJECT (demux, "MPD URI query failed."); + } + gst_query_unref (query); + + manifest = (gchar *) GST_BUFFER_DATA (demux->manifest); + if (manifest == NULL) { + GST_WARNING_OBJECT (demux, "Error validating the manifest."); + } else if (!gst_mpd_parse (demux->client, manifest, + GST_BUFFER_SIZE (demux->manifest))) { + /* In most cases, this will happen if we set a wrong url in the + * source element and we have received the 404 HTML response instead of + * the manifest */ + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid manifest."), + (NULL)); + return FALSE; + } + + gst_buffer_unref (demux->manifest); + demux->manifest = NULL; + + if (!gst_mpd_client_setup_media_presentation (demux->client)) { + GST_ELEMENT_ERROR (demux, STREAM, DECODE, + ("Incompatible manifest file."), (NULL)); + return FALSE; + } + + /* setup video, audio and subtitle streams, starting from first Period */ + if (!gst_mpd_client_set_period_index (demux->client, 0) || + !gst_dash_demux_setup_all_streams (demux)) + return FALSE; + + /* start playing from the first segment */ + gst_mpd_client_set_segment_index_for_all_streams (demux->client, 0); + + /* Send duration message */ + if (!gst_mpd_client_is_live (demux->client)) { + GstClockTime duration = + gst_mpd_client_get_media_presentation_duration (demux->client); + + if (duration != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (demux, + "Sending duration message : %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_duration (GST_OBJECT (demux), GST_FORMAT_TIME, + duration)); + } else { + GST_DEBUG_OBJECT (demux, + "mediaPresentationDuration unknown, can not send the duration message"); + } + } + gst_dash_demux_resume_download_task (demux); + gst_dash_demux_resume_stream_task (demux); + gst_event_unref (event); + return TRUE; + } + case GST_EVENT_NEWSEGMENT: + /* Swallow newsegments, we'll push our own */ + gst_event_unref (event); + return TRUE; + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +static gboolean +gst_dash_demux_src_query (GstPad * pad, GstQuery * query) +{ + GstDashDemux *dashdemux; + gboolean ret = FALSE; + + if (query == NULL) + return FALSE; + + dashdemux = GST_DASH_DEMUX (gst_pad_get_element_private (pad)); + + switch (query->type) { + case GST_QUERY_DURATION:{ + GstClockTime duration = -1; + GstFormat fmt; + + gst_query_parse_duration (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME) { + duration = + gst_mpd_client_get_media_presentation_duration (dashdemux->client); + if (GST_CLOCK_TIME_IS_VALID (duration) && duration > 0) { + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + ret = TRUE; + } + } + GST_DEBUG_OBJECT (dashdemux, + "GST_QUERY_DURATION returns %s with duration %" GST_TIME_FORMAT, + ret ? "TRUE" : "FALSE", GST_TIME_ARGS (duration)); + break; + } + case GST_QUERY_SEEKING:{ + GstFormat fmt; + gint64 stop = -1; + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + GST_DEBUG_OBJECT (dashdemux, "Received GST_QUERY_SEEKING with format %d", + fmt); + if (fmt == GST_FORMAT_TIME) { + GstClockTime duration; + + duration = + gst_mpd_client_get_media_presentation_duration (dashdemux->client); + if (GST_CLOCK_TIME_IS_VALID (duration) && duration > 0) + stop = duration; + + gst_query_set_seeking (query, fmt, + !gst_mpd_client_is_live (dashdemux->client), 0, stop); + ret = TRUE; + GST_DEBUG_OBJECT (dashdemux, "GST_QUERY_SEEKING returning with stop : %" + GST_TIME_FORMAT, GST_TIME_ARGS (stop)); + } + break; + } + case GST_QUERY_URI: { + /* forwarding uri */ + GST_DEBUG("URI query recevied in DASH demux....."); + gboolean res; + res = gst_pad_query_default (pad,query); + if(res) + GST_DEBUG("forwarding URI is done successfully!!..."); + ret = TRUE; + break; + } + default:{ + // By default, do not forward queries upstream + break; + } + } + + return ret; +} + +static GstFlowReturn +gst_dash_demux_pad (GstPad * pad, GstBuffer * buf) +{ + GstDashDemux *demux = GST_DASH_DEMUX (gst_pad_get_parent (pad)); + + if (demux->manifest == NULL) + demux->manifest = buf; + else + demux->manifest = gst_buffer_join (demux->manifest, buf); + + gst_object_unref (demux); + + return GST_FLOW_OK; +} + +static void +gst_dash_demux_stop (GstDashDemux * demux) +{ + gst_uri_downloader_cancel (demux->downloader); + gst_dash_demux_flush_stream_queues (demux); + + if (GST_TASK_STATE (demux->download_task) != GST_TASK_STOPPED) { + GST_TASK_SIGNAL (demux->download_task); + gst_task_stop (demux->download_task); + g_mutex_lock (&demux->download_mutex); + g_cond_signal (&demux->download_cond); + g_mutex_unlock (&demux->download_mutex); + } + if (GST_TASK_STATE (demux->stream_task) != GST_TASK_STOPPED) { + GST_TASK_SIGNAL (demux->stream_task); + gst_task_stop (demux->stream_task); + } +} + +/* gst_dash_demux_stream_loop: + * + * Loop for the "stream' task that pushes fragments to the src pads. + * + * Startup: + * The task is started as soon as we have received the manifest and + * waits for the first fragment to be downloaded and pushed in the + * queue. Once this fragment has been pushed, the task pauses itself + * until actual playback begins. + * + * During playback: + * The task pushes fragments downstream at regular intervals based on + * the fragment duration. If it detects a queue underrun, it sends + * a buffering event to tell the main application to pause. + * + * Teardown: + * The task is stopped when we have reached the end of the manifest + * and emptied our queue. + * + */ +static void +gst_dash_demux_stream_loop (GstDashDemux * demux) +{ + GstFlowReturn ret; + GstActiveStream *active_stream; + GstDashDemuxStream *selected_stream = NULL; + GstClockTime min_ts = GST_CLOCK_TIME_NONE; + guint i = 0; + gboolean eos = TRUE; + gboolean eop = TRUE; + + for (i = 0; i < g_slist_length (demux->streams); i++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, i); + GstBuffer *buffer; + GstDataQueueItem *item; + + if (dash_stream->stream_eos) { + GST_DEBUG_OBJECT (demux, "Stream %d is eos, skipping", dash_stream->idx); + continue; + } + + if (dash_stream->stream_end_of_period) { + GST_DEBUG_OBJECT (demux, "Stream %d is eop, skipping", dash_stream->idx); + eos = FALSE; + continue; + } + eos = FALSE; + eop = FALSE; + + if (!gst_data_queue_peek (dash_stream->queue, &item)) + goto flushing; + + if(GST_IS_BUFFER(item->object)) { + buffer = GST_BUFFER(item->object); + if(GST_BUFFER_TIMESTAMP(buffer) < min_ts || + !GST_CLOCK_TIME_IS_VALID(min_ts)) { + min_ts = GST_BUFFER_TIMESTAMP(buffer); + selected_stream = dash_stream; + } else if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (item->object))) { + selected_stream = dash_stream; + break; + } + } else { + selected_stream = dash_stream; + break; + } + } + + if(selected_stream) { + GstBuffer *buffer; + GstDataQueueItem *item; + + if (!gst_data_queue_pop (selected_stream->queue, &item)) + goto end; + if ( GST_IS_BUFFER (item->object)) { + buffer = GST_BUFFER(item->object); + active_stream = gst_mpdparser_get_active_stream_by_index (demux->client, selected_stream->idx); + + if (selected_stream->need_segment) { + if(!GST_CLOCK_TIME_IS_VALID (selected_stream->start_time)) { + if(GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))){ + selected_stream->start_time = GST_BUFFER_TIMESTAMP (buffer); + } else { + selected_stream->start_time = 0; + } + } + /* And send a newsegment */ + GST_DEBUG_OBJECT (demux, "Sending new-segment stream #%d. segment start:%" + GST_TIME_FORMAT, selected_stream->idx, GST_TIME_ARGS (selected_stream->start_time)); + gst_pad_push_event (selected_stream->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + selected_stream->start_time, GST_CLOCK_TIME_NONE, selected_stream->start_time)); + selected_stream->need_segment = FALSE; + } + + GST_DEBUG_OBJECT (demux, "Pushing fragment #%llu (stream %d) ts=%"GST_TIME_FORMAT, GST_BUFFER_OFFSET (buffer), + selected_stream->idx, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + ret = gst_pad_push (selected_stream->srcpad, gst_buffer_ref(buffer) ); + item->destroy (item); + if ((ret != GST_FLOW_OK) && (active_stream->mimeType == GST_STREAM_VIDEO)) + goto error_pushing; + } else { + GstEvent *event = GST_EVENT (item->object); + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + selected_stream->stream_eos = TRUE; + selected_stream->stream_end_of_period = TRUE; + } else if (GST_EVENT_TYPE (event) == GST_EVENT_DASH_EOP) { + selected_stream->stream_end_of_period = TRUE; + } + + if (GST_EVENT_TYPE (item->object) != GST_EVENT_DASH_EOP) { + gst_pad_push_event (selected_stream->srcpad, + gst_event_ref (GST_EVENT_CAST (item->object))); + } + + item->destroy (item); + } + } else { + if (eos) { + goto end_of_manifest; + } else if (eop) { + /*TODO Switch to next period*/ + } + } + +end: + return; + +flushing: + { + GST_INFO_OBJECT (demux, "Queue is flushing. Stopped streaming task"); + gst_task_stop (demux->stream_task); + return; + } + +end_of_manifest: + { + GST_INFO_OBJECT (demux, "Reached end of manifest, sending EOS"); + guint i = 0; + for (i = 0; i < gst_mpdparser_get_nb_active_stream (demux->client); i++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, i); + gst_pad_push_event (dash_stream->srcpad, gst_event_new_eos ()); + } + GST_INFO_OBJECT (demux, "Stopped streaming task"); + gst_task_stop (demux->stream_task); + return; + } + +error_pushing: + { + /* FIXME: handle error */ + GST_ERROR_OBJECT (demux, + "Error pushing buffer: %s... terminating the demux", + gst_flow_get_name (ret)); + gst_dash_demux_stop (demux); + return; + } +} + +static void +gst_dash_demux_clear_streams(GstDashDemux * demux) { + guint i = 0; + gst_dash_demux_flush_stream_queues (demux); + for (i = 0; i < g_slist_length(demux->streams); i++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, i); + gst_download_rate_deinit (&dash_stream->dnl_rate); + if (dash_stream->input_caps) { + gst_caps_unref (dash_stream->input_caps); + dash_stream->input_caps = NULL; + } + if (dash_stream->srcpad) { + gst_object_unref (dash_stream->srcpad); + dash_stream->srcpad = NULL; + } + /*TODO consider unref stream->output_caps*/ + g_object_unref (dash_stream->queue); + } + if(demux->streams) { + g_slist_free(demux->streams); + demux->streams = NULL; + } +} + +static void +gst_dash_demux_reset (GstDashDemux * demux, gboolean dispose) +{ + gint stream_idx; + demux->end_of_period = FALSE; + demux->cancelled = FALSE; + + gst_dash_demux_clear_streams(demux); + + if (demux->manifest) { + gst_buffer_unref (demux->manifest); + demux->manifest = NULL; + } + if (demux->client) { + gst_mpd_client_free (demux->client); + demux->client = NULL; + } + if (!dispose) { + demux->client = gst_mpd_client_new (); + } + + demux->last_manifest_update = GST_CLOCK_TIME_NONE; + for (stream_idx = 0; stream_idx < g_slist_length (demux->streams); stream_idx++) { + GstDashDemuxStream *dash_stream = g_slist_nth_data (demux->streams, stream_idx); + dash_stream->need_segment = TRUE; + } +} + +static GstClockTime +gst_dash_demux_get_buffering_time (GstDashDemux * demux) +{ + GstClockTime buffer_time = 0; + GSList *it; + GstDashDemuxStream *stream; + GstDataQueueSize queue_size; + + for(it=demux->streams; it; it=it->next) { + stream = it->data; + gst_data_queue_get_level(stream->queue, &queue_size); + + if (queue_size.time > 0) { + buffer_time = queue_size.time; + break; + } + } + + return buffer_time; +} + +static gboolean +gst_dash_demux_update_manifest(GstDashDemux *demux) { + GstFragment *download; + GstBuffer *buffer; + GstClockTime duration, now = gst_util_get_timestamp(); + gint64 update_period = demux->client->mpd_node->minimumUpdatePeriod; + + if (update_period == -1) { + GST_DEBUG_OBJECT (demux, "minimumUpdatePeriod unspecified, will not update MPD"); + return TRUE; + } + + /* init reference time for manifest file updates */ + if (!GST_CLOCK_TIME_IS_VALID (demux->last_manifest_update)) + demux->last_manifest_update = now; + + /* update the manifest file */ + if (now >= demux->last_manifest_update + update_period * GST_MSECOND) { + GST_DEBUG_OBJECT (demux, "Updating manifest file from URL %s", + demux->client->mpd_uri); + download = + gst_uri_downloader_fetch_uri (demux->downloader, + demux->client->mpd_uri); + if (download == NULL) { + GST_WARNING_OBJECT (demux, + "Failed to update the manifest file from URL %s", + demux->client->mpd_uri); + } else { + GstMpdClient *new_client = NULL; + guint period_idx; + const gchar *period_id; + GSList *iter; + + buffer = gst_fragment_get_buffer(download); + g_object_unref (download); + /* parse the manifest file */ + if (buffer == NULL) { + GST_WARNING_OBJECT (demux, "Error validating the manifest."); + return TRUE; + } + + new_client = gst_mpd_client_new (); + new_client->mpd_uri = g_strdup (demux->client->mpd_uri); + if (!gst_mpd_parse (new_client, + (gchar *) GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer))) { + /* In most cases, this will happen if we set a wrong url in the + * source element and we have received the 404 HTML response instead of + * the manifest */ + GST_WARNING_OBJECT (demux, "Error parsing the manifest."); + gst_buffer_unref (buffer); + return TRUE; + } + + gst_buffer_unref (buffer); + GST_DEBUG_OBJECT (demux, "Updating manifest"); + + period_id = gst_mpd_client_get_period_id (demux->client); + period_idx = gst_mpd_client_get_period_index (demux->client); + + /* setup video, audio and subtitle streams, starting from current Period */ + if (!gst_mpd_client_setup_media_presentation (new_client)) { + /* TODO */ + } + + if (period_idx) { + /*If more than one period exists.*/ + if (!gst_mpd_client_set_period_id (new_client, period_id)) { + GST_DEBUG_OBJECT (demux, + "Error setting up the updated manifest file"); + return FALSE; + } + } else { + if (!gst_mpd_client_set_period_index (new_client, period_idx)) { + GST_DEBUG_OBJECT (demux, + "Error setting up the updated manifest file"); + return FALSE; + } + } + + if (!gst_dash_demux_setup_mpdparser_streams (demux, new_client)) { + GST_ERROR_OBJECT (demux, "Failed to setup streams on manifest " + "update"); + return FALSE; + } + + /* update the streams to play from the next segment */ + for (iter = demux->streams; iter; iter = g_slist_next (iter)) { + GstDashDemuxStream *demux_stream = iter->data; + GstActiveStream *new_stream; + GstClockTime ts; + + new_stream = gst_mpdparser_get_active_stream_by_index (new_client, + demux_stream->idx); + + if (!new_stream) { + GST_DEBUG_OBJECT (demux, + "Stream of index %d is missing from manifest update", + demux_stream->idx); + return FALSE; + } + + if (gst_mpd_client_get_next_fragment_timestamp (demux->client, + demux_stream->idx, &ts)) { + gst_mpd_client_stream_seek (new_client, demux_stream->idx, ts); + + } else + if (gst_mpd_client_get_last_fragment_timestamp (demux->client, + demux_stream->idx, &ts)) { + /* try to set to the old timestamp + 1 */ + gst_mpd_client_stream_seek (new_client, demux_stream->idx, ts+1); + } + } + + /*Remember download failed count*/ + new_client->download_failed_count = demux->client->download_failed_count; + + gst_mpd_client_free (demux->client); + demux->client = new_client; + + /* Send an updated duration message */ + duration = + gst_mpd_client_get_media_presentation_duration (demux->client); + + if (duration != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (demux, + "Sending duration message : %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_duration(GST_OBJECT (demux), GST_FORMAT_TIME, duration)); + } else { + GST_DEBUG_OBJECT (demux, + "mediaPresentationDuration unknown, can not send the duration message"); + } + demux->last_manifest_update = gst_util_get_timestamp (); + GST_DEBUG_OBJECT (demux, "Manifest file successfully updated"); + } + } + return TRUE; +} + +static void +gst_dash_demux_download_wait (GstDashDemux * demux, GstClockTime time_diff) +{ + gint64 end_time = g_get_monotonic_time () + time_diff / GST_USECOND; + + GST_DEBUG_OBJECT (demux, "Download waiting for %" GST_TIME_FORMAT, + GST_TIME_ARGS (time_diff)); + g_cond_wait_until (&demux->download_cond, &demux->download_mutex, end_time); + GST_DEBUG_OBJECT (demux, "Download finished waiting"); +} + +static void +gst_dash_demux_check_live(GstDashDemux* demux, GstActiveStream *fragment_stream, + GstClockTime fragment_ts) +{ + gint64 time_diff; + gint pos; + + pos = + gst_mpd_client_check_time_position (demux->client, fragment_stream, + fragment_ts, &time_diff); + GST_DEBUG_OBJECT (demux, + "Checked position for fragment ts %" GST_TIME_FORMAT + ", res: %d, diff: %" G_GINT64_FORMAT, GST_TIME_ARGS (fragment_ts), + pos, time_diff); + + time_diff *= GST_USECOND; + if (pos < 0) { + /* we're behind, try moving to the 'present' */ + GDateTime *now = g_date_time_new_now_utc (); + + GST_DEBUG_OBJECT (demux, + "Falling behind live stream, moving forward"); + gst_mpd_client_seek_to_time(demux->client, now); + g_date_time_unref (now); + + } else if (pos > 0) { + /* we're ahead, wait a little */ + + GST_DEBUG_OBJECT (demux, "Waiting for next segment to be created"); + gst_dash_demux_download_wait (demux, time_diff); + } else { + demux->client->download_failed_count++; + } +} + +/* gst_dash_demux_download_loop: + * + * Loop for the "download' task that fetches fragments based on the + * selected representations. + * + * Startup: + * + * The task is started from the stream loop. + * + * During playback: + * + * It sequentially fetches fragments corresponding to the current + * representations and pushes them into a queue. + * + * It tries to maintain the number of queued items within a predefined + * range: if the queue is full, it will pause, checking every 100 ms if + * it needs to restart downloading fragments. + * + * When a new set of fragments has been downloaded, it evaluates the + * download time to check if we can or should switch to a different + * set of representations. + * + * Teardown: + * + * The task will exit when it encounters an error or when the end of the + * manifest has been reached. + * + */ +void +gst_dash_demux_download_loop (GstDashDemux * demux) +{ + GstActiveStream *fragment_stream = NULL; + GstClockTime fragment_ts; + if ( gst_mpd_client_is_live (demux->client) && demux->client->mpd_uri != NULL ) { + if (!gst_dash_demux_update_manifest(demux)) + goto end_of_manifest; + } + + /* try to switch to another set of representations if needed */ + gst_dash_demux_select_representations (demux); + + /* fetch the next fragment */ + while (!gst_dash_demux_get_next_fragment (demux, &fragment_stream, &fragment_ts)) { + if (demux->end_of_period) { + GST_INFO_OBJECT (demux, "Reached the end of the Period"); + /* setup video, audio and subtitle streams, starting from the next Period */ + if (!gst_mpd_client_set_period_index (demux->client, + gst_mpd_client_get_period_index (demux->client) + 1) + || !gst_dash_demux_setup_all_streams (demux)) { + GST_INFO_OBJECT (demux, "Reached the end of the manifest file"); + gst_task_start (demux->stream_task); + goto end_of_manifest; + } + /* start playing from the first segment of the new period */ + gst_mpd_client_set_segment_index_for_all_streams (demux->client, 0); + demux->end_of_period = FALSE; + } else if (!demux->cancelled) { + gst_uri_downloader_reset (demux->downloader); + if(gst_mpd_client_is_live (demux->client)) { + gst_dash_demux_check_live (demux, fragment_stream, fragment_ts); + } else { + demux->client->download_failed_count++; + } + + if (demux->client->download_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not fetch the next fragment"); + goto quit; + } else if (gst_mpd_client_set_next_baseURL_for_stream (demux->client)) { + GST_INFO_OBJECT (demux, "Current baseURL is %s. Trying to select another", + gst_mpdparser_get_baseURL (demux->client)); + demux->client->download_failed_count = 0; + } else { + goto error_downloading; + } + } else { + goto quit; + } + } + GST_INFO_OBJECT (demux, "Internal buffering : %" PRIu64 " s", + gst_dash_demux_get_buffering_time (demux) / GST_SECOND); + demux->client->download_failed_count = 0; + + +quit: + { + return; + } + +end_of_manifest: + { + GST_INFO_OBJECT (demux, "Stopped download task"); + gst_task_stop (demux->download_task); + return; + } + +error_downloading: + { + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, + ("Could not fetch the next fragment"), (NULL)); + gst_dash_demux_stop (demux); + return; + } +} + +static void +gst_dash_demux_resume_stream_task (GstDashDemux * demux) +{ + gst_task_start (demux->stream_task); +} + +static void +gst_dash_demux_resume_download_task (GstDashDemux * demux) +{ + gst_uri_downloader_reset(demux->downloader); + gst_task_start (demux->download_task); +} + +/* gst_dash_demux_select_representations: + * + * Select the most appropriate media representations based on a target + * bitrate. + * + * FIXME: all representations are selected against the same bitrate, but + * they will share the same bandwidth. This only works today because the + * audio representations bitrate usage is negligible as compared to the + * video representation one. + * + * Returns TRUE if a new set of representations has been selected + */ +static gboolean +gst_dash_demux_select_representations (GstDashDemux * demux) +{ + GstDashDemuxStream *stream = NULL; + GstActiveStream *active_stream = NULL; + GList *rep_list = NULL; + GSList *iter; + gint new_index; + gboolean ret = FALSE; + + GST_MPD_CLIENT_LOCK (demux->client); + for (iter = demux->streams; iter; iter = g_slist_next (iter)) { + guint64 bitrate; + + stream = iter->data; + active_stream = + gst_mpdparser_get_active_stream_by_index (demux->client, stream->idx); + if (!active_stream) + return FALSE; + + /* retrieve representation list */ + if (active_stream->cur_adapt_set) + rep_list = active_stream->cur_adapt_set->Representations; + if (!rep_list) + return FALSE; + + bitrate = gst_download_rate_get_current_rate (&stream->dnl_rate) * + demux->bandwidth_usage; + + GST_DEBUG_OBJECT (demux, "Trying to change bitrate to %" G_GUINT64_FORMAT, bitrate); + + /* get representation index with current max_bandwidth */ + new_index = + gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate); + + /* if no representation has the required bandwidth, take the lowest one */ + if (new_index == -1) + new_index = 0; + + if (new_index != active_stream->representation_idx) { + GstRepresentationNode *rep = g_list_nth_data (rep_list, new_index); + GST_INFO_OBJECT (demux, "Changing representation idx: %d %d %u", + stream->idx, new_index, rep->bandwidth); + if (gst_mpd_client_setup_representation (demux->client, active_stream, + rep)) { + ret = TRUE; + stream->need_header = TRUE; + GST_INFO_OBJECT (demux, "Switching bitrate to %d", + active_stream->cur_representation->bandwidth); + } else { + GST_WARNING_OBJECT (demux, + "Can not switch representation, aborting..."); + } + } + } + GST_MPD_CLIENT_UNLOCK (demux->client); + return ret; +} + +static GstFragment * +gst_dash_demux_get_next_header (GstDashDemux * demux, guint stream_idx) +{ + const gchar *initializationURL; + gchar *next_header_uri; + GstFragment *fragment; + + if (!gst_mpd_client_get_next_header (demux->client, &initializationURL, + stream_idx)) + return NULL; + + if (strncmp (initializationURL, "http://", 7) != 0) { + next_header_uri = + g_strconcat (gst_mpdparser_get_baseURL (demux->client), + initializationURL, NULL); + } else { + next_header_uri = g_strdup (initializationURL); + } + + GST_INFO_OBJECT (demux, "Fetching header %s", next_header_uri); + + fragment = gst_uri_downloader_fetch_uri (demux->downloader, next_header_uri); + g_free (next_header_uri); + g_free (initializationURL); + + return fragment; +} + +static GstCaps * +gst_dash_demux_get_video_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + guint width = 0, height = 0, bandwidth = 0; + const gchar *mimeType = NULL; + GstCaps *caps = NULL; + + if (stream == NULL) + return NULL; +#ifdef DASHDEMUX_MODIFICATION + /* caps need to inlcude resolution and bandwidth */ + width = gst_mpd_client_get_video_stream_width (stream); + height = gst_mpd_client_get_video_stream_height (stream); + bandwidth = gst_mpd_client_get_video_stream_bandwidth (stream); +#else + /* if bitstreamSwitching is true we dont need to swich pads on resolution change */ + if (!gst_mpd_client_get_bitstream_switching_flag (stream)) { + width = gst_mpd_client_get_video_stream_width (stream); + height = gst_mpd_client_get_video_stream_height (stream); + } +#endif + mimeType = gst_mpd_client_get_stream_mimeType (stream); + if (mimeType == NULL) + return NULL; + + caps = gst_caps_new_simple (mimeType, NULL); + if (width > 0 && height > 0) { + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, "height", + G_TYPE_INT, height, NULL); + } + + if (bandwidth > 0) + gst_caps_set_simple (caps, "bandwidth", G_TYPE_INT, bandwidth, NULL); + + gst_caps_set_simple (caps, "max-width", G_TYPE_INT, demux->max_video_width, "max-height", + G_TYPE_INT, demux->max_video_height, NULL); + +/* add ContentProtection to caps */ + if ( stream->cur_adapt_set->RepresentationBase->ContentProtection != NULL){ + GList *list; + GstDescriptorType *ContentProtectionDesc; + + list = g_list_first (stream->cur_adapt_set->RepresentationBase->ContentProtection); + ContentProtectionDesc = (GstDescriptorType *) list->data; + gchar *schemeIdUri = ContentProtectionDesc->schemeIdUri; + gchar *msprPro = ContentProtectionDesc->msprPro; + + if ( (schemeIdUri != NULL) && (msprPro != NULL) ){ + gst_caps_set_simple (caps, "contentprotection_scheme_iduri", G_TYPE_STRING, schemeIdUri, "mspr:pro", G_TYPE_STRING, msprPro, NULL); + } + } + return caps; +} + +static GstCaps * +gst_dash_demux_get_audio_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + guint rate = 0, channels = 0; + const gchar *mimeType; + GstCaps *caps = NULL; + + if (stream == NULL) + return NULL; + + /* if bitstreamSwitching is true we dont need to swich pads on rate/channels change */ + if (!gst_mpd_client_get_bitstream_switching_flag (stream)) { + channels = gst_mpd_client_get_audio_stream_num_channels (stream); + rate = gst_mpd_client_get_audio_stream_rate (stream); + } + mimeType = gst_mpd_client_get_stream_mimeType (stream); + if (mimeType == NULL) + return NULL; + + caps = gst_caps_new_simple (mimeType, NULL); + if (rate > 0) { + gst_caps_set_simple (caps, "rate", G_TYPE_INT, rate, NULL); + } + if (channels > 0) { + gst_caps_set_simple (caps, "channels", G_TYPE_INT, channels, NULL); + } + +/* add ContentProtection to caps */ + if ( stream->cur_adapt_set->RepresentationBase->ContentProtection != NULL){ + GList *list; + GstDescriptorType *ContentProtectionDesc; + + list = g_list_first (stream->cur_adapt_set->RepresentationBase->ContentProtection); + ContentProtectionDesc = (GstDescriptorType *) list->data; + gchar *schemeIdUri = ContentProtectionDesc->schemeIdUri; + gchar *msprPro = ContentProtectionDesc->msprPro; + + if ( (schemeIdUri != NULL) && (msprPro != NULL) ){ + gst_caps_set_simple (caps, "contentprotection_scheme_iduri", G_TYPE_STRING, schemeIdUri, "mspr:pro", G_TYPE_STRING, msprPro, NULL); + } + } + return caps; +} + +static GstCaps * +gst_dash_demux_get_application_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + const gchar *mimeType; + GstCaps *caps = NULL; + + if (stream == NULL) + return NULL; + + mimeType = gst_mpd_client_get_stream_mimeType (stream); + if (mimeType == NULL) + return NULL; + + caps = gst_caps_new_simple (mimeType, NULL); + + return caps; +} + +static GstCaps * +gst_dash_demux_get_input_caps (GstDashDemux * demux, GstActiveStream * stream) +{ + GstCaps *caps; + switch (stream->mimeType) { + case GST_STREAM_VIDEO: + caps = gst_dash_demux_get_video_input_caps (demux, stream); + break; + case GST_STREAM_AUDIO: + caps = gst_dash_demux_get_audio_input_caps (demux, stream); + break; + case GST_STREAM_APPLICATION: + caps = gst_dash_demux_get_application_input_caps (demux, stream); + break; + default: + return GST_CAPS_NONE; + } + /*Need to signal downstream elements about dash*/ + gst_caps_set_simple(caps, "variant", G_TYPE_STRING, "dash-fragmented", NULL); + return caps; +} + +/* gst_dash_demux_get_next_fragment_set: + * + * Get the next set of fragments for the current representations. + * + * This function uses the generic URI downloader API. + * + * Returns FALSE if an error occured while downloading fragments + * + */ +static gboolean +gst_dash_demux_get_next_fragment (GstDashDemux * demux,GstActiveStream **fragment_stream, + GstClockTime *selected_ts) +{ + GstActiveStream *stream; + GstDashDemuxStream *dash_stream; + GstDashDemuxStream *selected_stream = NULL; + GstFragment *download, *header; + gchar *next_fragment_uri; + GstClockTime duration; + GstClockTime timestamp; + GstClockTime min_timestamp = GST_CLOCK_TIME_NONE; + gboolean discont; + GTimeVal now; + GTimeVal start; + GstClockTime diff; + guint64 size_buffer = 0; + GstBuffer *buffer; + guint stream_idx; + gboolean end_of_period = TRUE; + + /*Select stream with smallest progress*/ + for (stream_idx = 0; stream_idx < g_slist_length (demux->streams); stream_idx++) { + dash_stream = g_slist_nth_data (demux->streams, stream_idx); + + if (dash_stream->download_end_of_period) + continue; + + if (gst_mpd_client_get_next_fragment_timestamp (demux->client, stream_idx, ×tamp)) { + if( timestamp < min_timestamp || !GST_CLOCK_TIME_IS_VALID(min_timestamp) ) { + selected_stream = dash_stream; + min_timestamp = timestamp; + } + } else { + GstEvent *event = NULL; + + GST_INFO_OBJECT (demux, + "This Period doesn't contain more fragments for stream %u", + dash_stream->idx); + + /* check if this is live and we should wait for more data */ + if (gst_mpd_client_is_live (demux->client) + && demux->client->mpd_node->minimumUpdatePeriod != -1) { + end_of_period = FALSE; + continue; + } + + if (gst_mpd_client_has_next_period (demux->client)) { + event = gst_event_new_dash_eop (); + } else { + GST_DEBUG_OBJECT (demux, + "No more fragments or periods for this stream, setting EOS"); + event = gst_event_new_eos (); + } + dash_stream->download_end_of_period = TRUE; + gst_dash_demux_stream_push_event (dash_stream, event); + } + } + + if (selected_ts) + *selected_ts = min_timestamp; + if (fragment_stream && selected_stream) + *fragment_stream = gst_mpdparser_get_active_stream_by_index (demux->client, selected_stream->idx); + /* Fetch next fragment from selected stream */ + if(selected_stream) { + + if (!gst_mpd_client_get_next_fragment (demux->client, + selected_stream->idx, &discont, &next_fragment_uri, &duration, ×tamp)) { + GST_WARNING_OBJECT (demux, "Failed to download fragment for stream %d", selected_stream->idx); + } else { + + g_get_current_time (&start); + GST_INFO_OBJECT (demux, "Fetching next fragment stream=%d ts=%"GST_TIME_FORMAT" url=%s", + selected_stream->idx, GST_TIME_ARGS(timestamp), next_fragment_uri); + + stream = gst_mpdparser_get_active_stream_by_index (demux->client, selected_stream->idx); + + end_of_period = FALSE; + + download = gst_uri_downloader_fetch_uri (demux->downloader, + next_fragment_uri); + g_free (next_fragment_uri); + + if (stream == NULL) + return FALSE; + + if (download == NULL) { + guint segment_idx = gst_mpd_client_get_segment_index ( stream ); + if(segment_idx > 0) { + /*Move to previous segment if download failed*/ + gst_mpd_client_set_segment_index (stream, segment_idx - 1); + } + return FALSE; + } + + download->start_time = timestamp; + download->stop_time = timestamp + duration; + + download->index = gst_mpd_client_get_segment_index (stream) - 1; + + GstCaps *caps = gst_dash_demux_get_input_caps (demux, stream); + buffer = gst_fragment_get_buffer (download); + g_return_val_if_fail (buffer != NULL, FALSE); + + if (selected_stream->need_header) { + /* Store the new input caps for that stream */ + gst_caps_replace (&dash_stream->input_caps, caps); + GST_INFO_OBJECT (demux, "Input source caps: %" GST_PTR_FORMAT, + dash_stream->input_caps); + + /* We need to fetch a new header */ + if ((header = gst_dash_demux_get_next_header (demux, selected_stream->idx)) == NULL) { + GST_INFO_OBJECT (demux, "Unable to fetch header"); + } else { + /* Replace fragment buffer with a new one including the header */ + GstBuffer *header_buffer = gst_fragment_get_buffer(header); + buffer = gst_buffer_join(header_buffer, buffer); + g_object_unref (header); + selected_stream->need_header = FALSE; + } + } else { + gst_caps_unref (caps); + } + + g_get_current_time (&now); + g_object_unref (download); + + gst_buffer_set_caps(buffer, dash_stream->input_caps); + GST_BUFFER_TIMESTAMP(buffer) = timestamp; + GST_BUFFER_DURATION(buffer) = duration; + GST_BUFFER_OFFSET(buffer) = gst_mpd_client_get_segment_index (stream) - 1; + size_buffer = GST_BUFFER_SIZE (buffer); + /* Push fragment into the queue */ + gst_dash_demux_stream_push_data (selected_stream, buffer); + diff = (GST_TIMEVAL_TO_TIME (now) - GST_TIMEVAL_TO_TIME (start)); + gst_download_rate_add_rate (&selected_stream->dnl_rate, size_buffer, diff, duration); + GST_DEBUG_OBJECT (demux, + "Stream: %d Download rate = %" G_GUINT64_FORMAT " Kbits/s (%" G_GUINT64_FORMAT + " Ko in %.2f s)\n", selected_stream->idx, + gst_download_rate_get_current_rate (&selected_stream->dnl_rate) / 1000, + size_buffer / 1024, + ((double) diff / GST_SECOND)); + } + } + + demux->end_of_period = end_of_period; + + return !end_of_period; +} diff --git a/dashdemux/src/gstdashdemux.h b/dashdemux/src/gstdashdemux.h new file mode 100755 index 0000000..9e3fe6c --- /dev/null +++ b/dashdemux/src/gstdashdemux.h @@ -0,0 +1,118 @@ +/* + * DASH demux plugin for GStreamer + * + * gstdashdemux.h + * + * Copyright (C) 2012 Orange + * + * Authors: + * David Corvoysier + * Hamid Zakari + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_DASH_DEMUX_H__ +#define __GST_DASH_DEMUX_H__ +#define DASHDEMUX_MODIFICATION +#include +#include +#include "gstmpdparser.h" +#include "gstfragmented.h" +#include "gsturidownloader.h" +#include "gstdownloadrate.h" + +G_BEGIN_DECLS +#define GST_TYPE_DASH_DEMUX \ + (gst_dash_demux_get_type()) +#define GST_DASH_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DASH_DEMUX,GstDashDemux)) +#define GST_DASH_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DASH_DEMUX,GstDashDemuxClass)) +#define GST_IS_DASH_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DASH_DEMUX)) +#define GST_IS_DASH_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DASH_DEMUX)) +// +typedef struct _GstDashDemux GstDashDemux; +typedef struct _GstDashDemuxClass GstDashDemuxClass; +typedef struct _GstDashDemuxStream GstDashDemuxStream; + +struct _GstDashDemuxStream { + guint idx; + GstPad *srcpad; + GstCaps *output_caps; + GstCaps *input_caps; + GstDataQueue *queue; + GstClockTime start_time; + gboolean download_end_of_period; + gboolean stream_end_of_period; + gboolean stream_eos; + gboolean need_header; + gboolean need_segment; + GstDownloadRate dnl_rate; +}; + +#define MAX_LANGUAGES 20 +/** + * GstDashDemux: + * + * Opaque #GstDashDemux data structure. + */ +struct _GstDashDemux +{ + GstElement parent; + GstPad *sinkpad; + GSList *streams; + gint max_video_width; + gint max_video_height; + + GstBuffer *manifest; + GstUriDownloader *downloader; + GstMpdClient *client; /* MPD client */ + gboolean end_of_period; + gboolean end_of_manifest; + + /* Properties */ + GstClockTime max_buffering_time; /* Maximum buffering time accumulated during playback */ + gfloat bandwidth_usage; /* Percentage of the available bandwidth to use */ + guint64 max_bitrate; /* max of bitrate supported by target decoder */ + + /* Streaming task */ + GstTask *stream_task; + GStaticRecMutex stream_lock; + GMutex *stream_timed_lock; + + /* Download task */ + GstTask *download_task; + GStaticRecMutex download_lock; + volatile gboolean cancelled; + GMutex download_mutex; + GCond download_cond; + + /* Manifest update */ + GstClockTime last_manifest_update; +}; + +struct _GstDashDemuxClass +{ + GstElementClass parent_class; +}; + +GType gst_dash_demux_get_type (void); + +G_END_DECLS +#endif /* __GST_DASH_DEMUX_H__ */ diff --git a/dashdemux/src/gstdownloadrate.c b/dashdemux/src/gstdownloadrate.c new file mode 100644 index 0000000..9913e0a --- /dev/null +++ b/dashdemux/src/gstdownloadrate.c @@ -0,0 +1,137 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * Copyright (C) 2012 Smart TV Alliance + * Author: Louis-Francis Ratté-Boulianne , Collabora Ltd. + * + * gstfragment.c: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "gstdownloadrate.h" + +static void +_gst_download_rate_check_remove_rates (GstDownloadRate * rate) +{ + if (rate->max_length == 0) + return; + + while (g_queue_get_length (&rate->queue) > rate->max_length) { + guint bitrate = GPOINTER_TO_UINT (g_queue_pop_head (&rate->queue)); + + rate->total -= bitrate; + } +} + +void +gst_download_rate_init (GstDownloadRate * rate) +{ + g_queue_init (&rate->queue); + g_mutex_init (&rate->mutex); + rate->total = 0; + rate->max_length = 0; + rate->aver_period = 0; +} + +void +gst_download_rate_deinit (GstDownloadRate * rate) +{ + gst_download_rate_clear (rate); + g_mutex_clear (&rate->mutex); +} + +void +gst_download_rate_set_max_length (GstDownloadRate * rate, gint max_length) +{ + g_mutex_lock (&rate->mutex); + rate->max_length = max_length; + _gst_download_rate_check_remove_rates (rate); + g_mutex_unlock (&rate->mutex); +} + +gint +gst_download_rate_get_max_length (GstDownloadRate * rate) +{ + guint ret; + g_mutex_lock (&rate->mutex); + ret = rate->max_length; + g_mutex_unlock (&rate->mutex); + + return ret; +} + +void + gst_download_rate_set_aver_period (GstDownloadRate * rate, guint64 aver_period) +{ + g_mutex_lock (&rate->mutex); + rate->aver_period = aver_period; + _gst_download_rate_check_remove_rates (rate); + g_mutex_unlock (&rate->mutex); +} + +GstClockTime +gst_download_rate_get_aver_period (GstDownloadRate * rate) +{ + guint64 ret; + g_mutex_lock (&rate->mutex); + ret = rate->aver_period; + g_mutex_unlock (&rate->mutex); + + return ret; +} + +void +gst_download_rate_clear (GstDownloadRate * rate) +{ + g_mutex_lock (&rate->mutex); + g_queue_clear (&rate->queue); + rate->total = 0; + g_mutex_unlock (&rate->mutex); +} + +void +gst_download_rate_add_rate (GstDownloadRate * rate, guint bytes, guint64 time, GstClockTime duration) +{ + guint64 bitrate; + g_mutex_lock (&rate->mutex); + + /* convert from bytes / nanoseconds to bits per second */ + bitrate = G_GUINT64_CONSTANT (8000000000) * bytes / ((double) time); + g_queue_push_tail (&rate->queue, GUINT_TO_POINTER ((guint) bitrate)); + rate->total += bitrate; + + if ( duration * rate->max_length > rate->aver_period ) + rate->max_length = + duration < rate->aver_period ? rate->aver_period / duration : 1; + + _gst_download_rate_check_remove_rates (rate); + g_mutex_unlock (&rate->mutex); +} + +guint64 +gst_download_rate_get_current_rate (GstDownloadRate * rate) +{ + guint64 ret; + g_mutex_lock (&rate->mutex); + if (g_queue_get_length (&rate->queue)) + ret = rate->total / g_queue_get_length (&rate->queue); + else + ret = 0; + g_mutex_unlock (&rate->mutex); + + return ret; +} diff --git a/dashdemux/src/gstdownloadrate.h b/dashdemux/src/gstdownloadrate.h new file mode 100644 index 0000000..e4da338 --- /dev/null +++ b/dashdemux/src/gstdownloadrate.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2012 Smart TV Alliance + * Author: Thiago Sousa Santos , Collabora Ltd. + * + * gstdownloadrate.h: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_DOWNLOAD_RATE_H__ +#define __GST_DOWNLOAD_RATE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstDownloadRate GstDownloadRate; + +struct _GstDownloadRate +{ + GQueue queue; + GMutex mutex; + + gint max_length; + guint64 aver_period; + guint64 total; +}; + +void gst_download_rate_init (GstDownloadRate * rate); +void gst_download_rate_deinit (GstDownloadRate * rate); + +void gst_download_rate_set_max_length (GstDownloadRate * rate, gint max_length); +gint gst_download_rate_get_max_length (GstDownloadRate * rate); + +void gst_download_rate_set_aver_period (GstDownloadRate * rate, GstClockTime aver_period); +GstClockTime gst_download_rate_get_aver_period (GstDownloadRate * rate); + +void gst_download_rate_clear (GstDownloadRate * rate); +void gst_download_rate_add_rate (GstDownloadRate * rate, guint bytes, guint64 time, GstClockTime duration); + +guint64 gst_download_rate_get_current_rate (GstDownloadRate * rate); + +G_END_DECLS +#endif /* __GST_DOWNLOAD_RATE_H__ */ diff --git a/dashdemux/src/gstfragment.c b/dashdemux/src/gstfragment.c new file mode 100755 index 0000000..2102726 --- /dev/null +++ b/dashdemux/src/gstfragment.c @@ -0,0 +1,211 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * + * gstfragment.c: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "glibcompat.h" +#include "gstfragmented.h" +#include "gstfragment.h" + +#define GST_CAT_DEFAULT fragmented_debug + +#define GST_FRAGMENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_FRAGMENT, GstFragmentPrivate)) + +enum +{ + PROP_0, + PROP_INDEX, + PROP_NAME, + PROP_DURATION, + PROP_DISCONTINOUS, + PROP_BUFFER_LIST, + PROP_CAPS, + PROP_LAST +}; + +struct _GstFragmentPrivate +{ + GstBuffer *buffer; + GstAdapter *adapter; +}; + +G_DEFINE_TYPE (GstFragment, gst_fragment, G_TYPE_OBJECT); + +static void gst_fragment_dispose (GObject * object); +static void gst_fragment_finalize (GObject * object); + +static void +gst_fragment_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GstFragment *fragment = GST_FRAGMENT (object); + + switch (property_id) { + case PROP_INDEX: + g_value_set_uint (value, fragment->index); + break; + + case PROP_NAME: + g_value_set_string (value, fragment->name); + break; + + case PROP_DURATION: + g_value_set_uint64 (value, fragment->stop_time - fragment->start_time); + break; + + case PROP_DISCONTINOUS: + g_value_set_boolean (value, fragment->discontinuous); + break; + + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + + +static void +gst_fragment_class_init (GstFragmentClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GstFragmentPrivate)); + + gobject_class->get_property = gst_fragment_get_property; + gobject_class->dispose = gst_fragment_dispose; + gobject_class->finalize = gst_fragment_finalize; + + g_object_class_install_property (gobject_class, PROP_INDEX, + g_param_spec_uint ("index", "Index", "Index of the fragment", 0, + G_MAXUINT, 0, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_NAME, + g_param_spec_string ("name", "Name", + "Name of the fragment (eg:fragment-12.ts)", NULL, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_DISCONTINOUS, + g_param_spec_boolean ("discontinuous", "Discontinous", + "Whether this fragment has a discontinuity or not", + FALSE, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_DURATION, + g_param_spec_uint64 ("duration", "Fragment duration", + "Duration of the fragment", 0, G_MAXUINT64, 0, G_PARAM_READABLE)); + +} + +static void +gst_fragment_init (GstFragment * fragment) +{ + GstFragmentPrivate *priv; + + fragment->priv = priv = GST_FRAGMENT_GET_PRIVATE (fragment); + + priv->adapter = gst_adapter_new (); + fragment->download_start_time = g_get_real_time (); + fragment->start_time = 0; + fragment->stop_time = 0; + fragment->index = 0; + fragment->name = g_strdup (""); + fragment->completed = FALSE; + fragment->discontinuous = FALSE; +} + +GstFragment * +gst_fragment_new (void) +{ + return GST_FRAGMENT (g_object_new (GST_TYPE_FRAGMENT, NULL)); +} + +static void +gst_fragment_finalize (GObject * gobject) +{ + GstFragment *fragment = GST_FRAGMENT (gobject); + + g_free (fragment->name); + + G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject); +} + +void +gst_fragment_dispose (GObject * object) +{ + GstFragmentPrivate *priv = GST_FRAGMENT (object)->priv; + + if (priv->buffer != NULL) { + gst_buffer_unref (priv->buffer); + priv->buffer = NULL; + } + + if (priv->adapter != NULL) { + g_object_unref (priv->adapter); + priv->adapter = NULL; + } + + G_OBJECT_CLASS (gst_fragment_parent_class)->dispose (object); +} + +GstBuffer * +gst_fragment_get_buffer (GstFragment * fragment) +{ + g_return_val_if_fail (fragment != NULL, NULL); + + if (!fragment->completed) + return NULL; + + if(!fragment->priv->buffer) { + fragment->priv->buffer = gst_adapter_take_buffer(fragment->priv->adapter, + gst_adapter_available(fragment->priv->adapter)); + } + + return gst_buffer_ref(fragment->priv->buffer); +} + +guint64 +gst_fragment_get_total_size (GstFragment * fragment) +{ + g_return_val_if_fail (fragment != NULL, 0); + + if (fragment->priv->buffer) + return GST_BUFFER_SIZE(fragment->priv->buffer); + + return gst_adapter_available(fragment->priv->adapter); +} + + + +gboolean +gst_fragment_add_buffer (GstFragment * fragment, GstBuffer * buffer) +{ + g_return_val_if_fail (fragment != NULL, FALSE); + g_return_val_if_fail (buffer != NULL, FALSE); + + if (fragment->completed) { + GST_WARNING ("Fragment is completed, could not add more buffers"); + return FALSE; + } + + gst_adapter_push(fragment->priv->adapter, buffer); + return TRUE; +} diff --git a/dashdemux/src/gstfragment.h b/dashdemux/src/gstfragment.h new file mode 100755 index 0000000..3068943 --- /dev/null +++ b/dashdemux/src/gstfragment.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * + * gstfragment.h: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSTFRAGMENT_H__ +#define __GSTFRAGMENT_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_FRAGMENT (gst_fragment_get_type()) +#define GST_FRAGMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FRAGMENT,GstFragment)) +#define GST_FRAGMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FRAGMENT,GstFragmentClass)) +#define GST_IS_FRAGMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FRAGMENT)) +#define GST_IS_FRAGMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FRAGMENT)) + +typedef struct _GstFragment GstFragment; +typedef struct _GstFragmentPrivate GstFragmentPrivate; +typedef struct _GstFragmentClass GstFragmentClass; + +struct _GstFragment +{ + GObject parent; + + gchar * name; /* Name of the fragment */ + gboolean completed; /* Whether the fragment is complete or not */ + guint64 download_start_time; /* Epoch time when the download started */ + guint64 download_stop_time; /* Epoch time when the download finished */ + guint64 start_time; /* Start time of the fragment */ + guint64 stop_time; /* Stop time of the fragment */ + gboolean index; /* Index of the fragment */ + gboolean discontinuous; /* Whether this fragment is discontinuous or not */ + + GstFragmentPrivate *priv; +}; + +struct _GstFragmentClass +{ + GObjectClass parent_class; +}; + +GType gst_fragment_get_type (void); + +guint64 gst_fragment_get_total_size (GstFragment * fragment); +GstBuffer *gst_fragment_get_buffer(GstFragment *fragment); +gboolean gst_fragment_add_buffer (GstFragment *fragment, GstBuffer *buffer); +GstFragment * gst_fragment_new (void); + +G_END_DECLS +#endif /* __GSTFRAGMENT_H__ */ diff --git a/dashdemux/src/gstfragmented.h b/dashdemux/src/gstfragmented.h new file mode 100755 index 0000000..0214a93 --- /dev/null +++ b/dashdemux/src/gstfragmented.h @@ -0,0 +1,15 @@ +#ifndef __GST_FRAGMENTED_H__ +#define __GST_FRAGMENTED_H__ + +#include + +G_BEGIN_DECLS + +GST_DEBUG_CATEGORY_EXTERN (fragmented_debug); + +#define LOG_CAPS(obj, caps) GST_DEBUG_OBJECT (obj, "%s: %" GST_PTR_FORMAT, #caps, caps) + +G_END_DECLS + +#endif /* __GST_FRAGMENTED_H__ */ + diff --git a/dashdemux/src/gstmpdparser.c b/dashdemux/src/gstmpdparser.c new file mode 100755 index 0000000..95afab9 --- /dev/null +++ b/dashdemux/src/gstmpdparser.c @@ -0,0 +1,3941 @@ +/* + * DASH MPD parsing library + * + * gstmpdparser.c + * + * Copyright (C) 2012 STMicroelectronics + * + * Authors: + * Gianluca Gennari + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "gstfragmented.h" +#include "gstmpdparser.h" + +#define GST_CAT_DEFAULT fragmented_debug + +/* Property parsing */ +static gchar *gst_mpdparser_get_xml_prop_string (xmlNode * a_node, + const gchar * property); +static gchar **gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node, + const gchar * property); +static guint gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node, + const gchar * property, guint default_val); +static guint *gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node, + const gchar * property, guint * size); +static gdouble gst_mpdparser_get_xml_prop_double (xmlNode * a_node, + const gchar * property); +static gboolean gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node, + const gchar * property); +static GstMPDFileType gst_mpdparser_get_xml_prop_type (xmlNode * a_node, + const gchar * property); +static GstSAPType gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node, + const gchar * property); +static GstRange *gst_mpdparser_get_xml_prop_range (xmlNode * a_node, + const gchar * property); +static GstRatio *gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node, + const gchar * property); +static GstFrameRate *gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node, + const gchar * property); +static GstConditionalUintType *gst_mpdparser_get_xml_prop_cond_uint (xmlNode * + a_node, const gchar * property); +static GstDateTime *gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, + const gchar * property); +static gint64 gst_mpdparser_get_xml_prop_duration (xmlNode * a_node, + const gchar * property); +static gchar *gst_mpdparser_get_xml_node_content (xmlNode * a_node); +static gchar *gst_mpdparser_get_xml_node_namespace (xmlNode * a_node, + const gchar * prefix); + +/* XML node parsing */ +static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_descriptor_type_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_content_component_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_subrepresentation_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_segment_url_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_url_type_node (GstURLType ** pointer, + xmlNode * a_node); +static void gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** + pointer, xmlNode * a_node); +static void gst_mpdparser_parse_s_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** + pointer, xmlNode * a_node); +static void gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType + ** pointer, xmlNode * a_node); +static void gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** + pointer, xmlNode * a_node); +static void +gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType ** + pointer, xmlNode * a_node); +static void gst_mpdparser_parse_representation_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_adaptation_set_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** + pointer, xmlNode * a_node); +static void gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_program_info_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_metrics_range_node (GList ** list, + xmlNode * a_node); +static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node); +static void gst_mpdparser_parse_root_node (GstMPDNode ** pointer, + xmlNode * a_node); + +/* Helper functions */ +static gint convert_to_millisecs (gint decimals, gint pos); +static int strncmp_ext (const char *s1, const char *s2); +static GstStreamPeriod *gst_mpdparser_get_stream_period (GstMpdClient * client); +static gchar *gst_mpdparser_parse_baseURL (GstMpdClient * client); +static gchar *gst_mpdparser_get_segmentURL_for_range (const gchar *url, + GstRange * range); +static gchar *gst_mpdparser_get_mediaURL (GstMpdClient * client, + GstSegmentURLNode * segmentURL); +static gchar *gst_mpdparser_get_initializationURL (GstURLType * + InitializationURL); +static gchar *gst_mpdparser_build_URL_from_template (const gchar * url_template, + const gchar * id, guint number, guint bandwidth, guint time); +static gboolean gst_mpd_client_add_media_segment (GstActiveStream * stream, + GstSegmentURLNode * url_node, guint number, guint start, + GstClockTime start_time, GstClockTime duration); +static const gchar *gst_mpdparser_mimetype_to_caps (const gchar * mimeType); +static GstClockTime gst_mpd_client_get_segment_duration (GstMpdClient * client, GstActiveStream * stream); + +/* Adaptation Set */ +static GstAdaptationSetNode + *gst_mpdparser_get_first_adapt_set_with_mimeType (GList * AdaptationSets, + const gchar * mimeType); +static GstAdaptationSetNode + *gst_mpdparser_get_adapt_set_with_mimeType_and_idx (GList * AdaptationSets, + const gchar * mimeType, gint idx); +static GstAdaptationSetNode + *gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang (GList * + AdaptationSets, const gchar * mimeType, const gchar * lang); + +/* Representation */ +static GstRepresentationNode *gst_mpdparser_get_lowest_representation (GList * + Representations); +#if 0 +static GstRepresentationNode *gst_mpdparser_get_highest_representation (GList * + Representations); +static GstRepresentationNode + *gst_mpdparser_get_representation_with_max_bandwidth (GList * + Representations, guint64 max_bandwidth); +#endif +static GstSegmentBaseType *gst_mpdparser_get_segment_base (GstPeriodNode * + Period, GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation); +static GstSegmentListNode *gst_mpdparser_get_segment_list (GstPeriodNode * + Period, GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation); + +/* Memory management */ +static void gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node); +static void gst_mpdparser_free_prog_info_node (GstProgramInformationNode * + prog_info_node); +static void gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node); +static void gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * + metrics_range_node); +static void gst_mpdparser_free_period_node (GstPeriodNode * period_node); +static void gst_mpdparser_free_subset_node (GstSubsetNode * subset_node); +static void gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode * + segment_template_node); +static void +gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * + representation_base); +static void gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * + adaptation_set_node); +static void gst_mpdparser_free_representation_node (GstRepresentationNode * + representation_node); +static void gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode + * subrep_node); +static void gst_mpdparser_free_s_node (GstSNode * s_node); +static void gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * + seg_timeline); +static void gst_mpdparser_free_url_type_node (GstURLType * url_type_node); +static void gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * + seg_base_type); +static void gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType * + mult_seg_base_type); +static void gst_mpdparser_free_segment_list_node (GstSegmentListNode * + segment_list_node); +static void gst_mpdparser_free_segment_url_node (GstSegmentURLNode * + segment_url); +static void gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node); +static void gst_mpdparser_free_descriptor_type_node (GstDescriptorType * + descriptor_type); +static void gst_mpdparser_free_content_component_node (GstContentComponentNode * + content_component_node); +static void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period); +static void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment); +static void gst_mpdparser_free_active_stream (GstActiveStream * active_stream); + +/* functions to parse node namespaces, content and properties */ +static gchar * +gst_mpdparser_get_xml_prop_string (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + GST_LOG (" - %s: %s", property, prop_string); + } + + return (gchar *) prop_string; +} + +static gchar ** +gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node, + const gchar * property) +{ + xmlChar *prop_string; + gchar **prop_string_vector = NULL; + guint i = 0; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + prop_string_vector = g_strsplit ((gchar *) prop_string, " ", -1); + if (!prop_string_vector) { + GST_WARNING ("Scan of string vector property failed!"); + return NULL; + } + GST_LOG (" - %s:", property); + while (prop_string_vector[i]) { + GST_LOG (" %s", prop_string_vector[i]); + i++; + } + xmlFree (prop_string); + } + + return prop_string_vector; +} + +static guint +gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node, + const gchar * property, guint default_val) +{ + xmlChar *prop_string; + guint prop_unsigned_integer = default_val; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%u", &prop_unsigned_integer)) { + GST_LOG (" - %s: %u", property, prop_unsigned_integer); + } else { + GST_WARNING + ("failed to parse unsigned integer property %s from xml string %s", + property, prop_string); + } + xmlFree (prop_string); + } + + return prop_unsigned_integer; +} + +static guint * +gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node, + const gchar * property, guint * size) +{ + xmlChar *prop_string; + gchar **str_vector; + guint *prop_uint_vector = NULL, i; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + str_vector = g_strsplit ((gchar *) prop_string, " ", -1); + if (!str_vector) { + GST_WARNING ("Scan of uint vector property failed!"); + return NULL; + } + *size = g_strv_length (str_vector); + prop_uint_vector = g_malloc (*size * sizeof (guint)); + if (!prop_uint_vector) { + GST_WARNING ("Array allocation failed!"); + } else { + GST_LOG (" - %s:", property); + for (i = 0; i < *size; i++) { + if (sscanf ((gchar *) str_vector[i], "%u", &prop_uint_vector[i])) { + GST_LOG (" %u", prop_uint_vector[i]); + } else { + GST_WARNING + ("failed to parse uint vector type property %s from xml string %s", + property, str_vector[i]); + } + } + } + xmlFree (prop_string); + g_strfreev (str_vector); + } + + return prop_uint_vector; +} + +static gdouble +gst_mpdparser_get_xml_prop_double (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + gdouble prop_double = 0; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%lf", &prop_double)) { + GST_LOG (" - %s: %lf", property, prop_double); + } else { + GST_WARNING ("failed to parse double property %s from xml string %s", + property, prop_string); + } + xmlFree (prop_string); + } + + return prop_double; +} + +static gboolean +gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + gboolean prop_bool = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + if (xmlStrcmp (prop_string, (xmlChar *) "false") == 0) { + GST_LOG (" - %s: false", property); + } else if (xmlStrcmp (prop_string, (xmlChar *) "true") == 0) { + GST_LOG (" - %s: true", property); + prop_bool = TRUE; + } else { + GST_WARNING ("failed to parse boolean property %s from xml string %s", + property, prop_string); + } + xmlFree (prop_string); + } + + return prop_bool; +} + +static GstMPDFileType +gst_mpdparser_get_xml_prop_type (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + GstMPDFileType prop_type = GST_MPD_FILE_TYPE_STATIC; /* default */ + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + if (xmlStrcmp (prop_string, (xmlChar *) "OnDemand") == 0 + || xmlStrcmp (prop_string, (xmlChar *) "static") == 0) { + GST_LOG (" - %s: static", property); + prop_type = GST_MPD_FILE_TYPE_STATIC; + } else if (xmlStrcmp (prop_string, (xmlChar *) "Live") == 0 + || xmlStrcmp (prop_string, (xmlChar *) "dynamic") == 0) { + GST_LOG (" - %s: dynamic", property); + prop_type = GST_MPD_FILE_TYPE_DYNAMIC; + } else { + GST_WARNING ("failed to parse MPD type property %s from xml string %s", + property, prop_string); + } + xmlFree (prop_string); + } + + return prop_type; +} + +static GstSAPType +gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + guint prop_SAP_type = 0; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%u", &prop_SAP_type) + && prop_SAP_type <= 6) { + GST_LOG (" - %s: %u", property, prop_SAP_type); + } else { + GST_WARNING + ("failed to parse unsigned integer property %s from xml string %s", + property, prop_string); + } + xmlFree (prop_string); + } + + return (GstSAPType) prop_SAP_type; +} + +static GstRange * +gst_mpdparser_get_xml_prop_range (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + GstRange *prop_range = NULL; + guint64 first_byte_pos = 0, last_byte_pos = 0; + guint len, pos; + gchar *str; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("range: %s, len %d", str, len); + + /* read "-" */ + pos = strcspn (str, "-"); + if (pos >= len) { + GST_TRACE ("pos %d >= len %d", pos, len); + goto error; + } + /* read first_byte_pos */ + if (pos != 0) { + if (sscanf (str, "%llu", &first_byte_pos) != 1) { + goto error; + } + } + /* read last_byte_pos */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%llu", &last_byte_pos) != 1) { + goto error; + } + } + /* malloc return data structure */ + prop_range = g_slice_new0 (GstRange); + if (prop_range == NULL) { + GST_WARNING ("Allocation of GstRange failed!"); + goto error; + } + prop_range->first_byte_pos = first_byte_pos; + prop_range->last_byte_pos = last_byte_pos; + GST_LOG (" - %s: %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, + property, first_byte_pos, last_byte_pos); + xmlFree (prop_string); + } + + return prop_range; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property, + prop_string); + return NULL; +} + +static GstRatio * +gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + GstRatio *prop_ratio = NULL; + guint num = 0, den = 1; + guint len, pos; + gchar *str; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("ratio: %s, len %d", str, len); + + /* read ":" */ + pos = strcspn (str, ":"); + if (pos >= len) { + GST_TRACE ("pos %d >= len %d", pos, len); + goto error; + } + /* read num */ + if (pos != 0) { + if (sscanf (str, "%u", &num) != 1) { + goto error; + } + } + /* read den */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%u", &den) != 1) { + goto error; + } + } + /* malloc return data structure */ + prop_ratio = g_slice_new0 (GstRatio); + if (prop_ratio == NULL) { + GST_WARNING ("Allocation of GstRatio failed!"); + goto error; + } + prop_ratio->num = num; + prop_ratio->den = den; + GST_LOG (" - %s: %u:%u", property, num, den); + xmlFree (prop_string); + } + + return prop_ratio; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property, + prop_string); + return NULL; +} + +static GstFrameRate * +gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + GstFrameRate *prop_framerate = NULL; + guint num = 0, den = 1; + guint len, pos; + gchar *str; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("framerate: %s, len %d", str, len); + + /* read "/" if available */ + pos = strcspn (str, "/"); + /* read num */ + if (pos != 0) { + if (sscanf (str, "%u", &num) != 1) { + goto error; + } + } + /* read den (if available) */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%u", &den) != 1) { + goto error; + } + } + /* alloc return data structure */ + prop_framerate = g_slice_new0 (GstFrameRate); + if (prop_framerate == NULL) { + GST_WARNING ("Allocation of GstFrameRate failed!"); + goto error; + } + prop_framerate->num = num; + prop_framerate->den = den; + if (den == 1) + GST_LOG (" - %s: %u", property, num); + else + GST_LOG (" - %s: %u/%u", property, num, den); + xmlFree (prop_string); + } + + return prop_framerate; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property, + prop_string); + return NULL; +} + +static GstConditionalUintType * +gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + GstConditionalUintType *prop_cond_uint = NULL; + gchar *str; + gboolean flag; + guint val; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + str = (gchar *) prop_string; + GST_TRACE ("conditional uint: %s", str); + + if (strcmp (str, "false") == 0) { + flag = FALSE; + val = 0; + } else if (strcmp (str, "true") == 0) { + flag = TRUE; + val = 0; + } else { + flag = TRUE; + if (sscanf (str, "%u", &val) != 1) + goto error; + } + + /* alloc return data structure */ + prop_cond_uint = g_slice_new0 (GstConditionalUintType); + if (prop_cond_uint == NULL) { + GST_WARNING ("Allocation of GstConditionalUintType failed!"); + goto error; + } + prop_cond_uint->flag = flag; + prop_cond_uint->value = val; + GST_LOG (" - %s: flag=%s val=%u", property, flag ? "true" : "false", val); + xmlFree (prop_string); + } + + return prop_cond_uint; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property, + prop_string); + return NULL; +} + +/* + DateTime Data Type + + The dateTime data type is used to specify a date and a time. + + The dateTime is specified in the following form "YYYY-MM-DDThh:mm:ss" where: + + * YYYY indicates the year + * MM indicates the month + * DD indicates the day + * T indicates the start of the required time section + * hh indicates the hour + * mm indicates the minute + * ss indicates the second + + Note: All components are required! +*/ + +static GstDateTime * +gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + gchar *str; + gint ret, len, pos; + gint year, month, day, hour, minute, second; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("dateTime: %s, len %d", str, len); + /* parse year */ + ret = sscanf (str, "%d", &year); + if (ret != 1) + goto error; + pos = strcspn (str, "-"); + str += (pos + 1); + GST_TRACE (" - year %d", year); + /* parse month */ + ret = sscanf (str, "%d", &month); + if (ret != 1) + goto error; + pos = strcspn (str, "-"); + str += (pos + 1); + GST_TRACE (" - month %d", month); + /* parse day */ + ret = sscanf (str, "%d", &day); + if (ret != 1) + goto error; + pos = strcspn (str, "T"); + str += (pos + 1); + GST_TRACE (" - day %d", day); + /* parse hour */ + ret = sscanf (str, "%d", &hour); + if (ret != 1) + goto error; + pos = strcspn (str, ":"); + str += (pos + 1); + GST_TRACE (" - hour %d", hour); + /* parse minute */ + ret = sscanf (str, "%d", &minute); + if (ret != 1) + goto error; + pos = strcspn (str, ":"); + str += (pos + 1); + GST_TRACE (" - minute %d", minute); + /* parse second */ + ret = sscanf (str, "%d", &second); + if (ret != 1) + goto error; + GST_TRACE (" - second %d", second); + + GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%02d", property, + year, month, day, hour, minute, second); + + return gst_date_time_new (0, year, month, day, hour, minute, second); + } + + return NULL; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property, + prop_string); + return NULL; +} + +/* + Duration Data Type + + The duration data type is used to specify a time interval. + + The time interval is specified in the following form "-PnYnMnDTnHnMnS" where: + + * - indicates the negative sign (optional) + * P indicates the period (required) + * nY indicates the number of years + * nM indicates the number of months + * nD indicates the number of days + * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds) + * nH indicates the number of hours + * nM indicates the number of minutes + * nS indicates the number of seconds +*/ + +/* this function computes decimals * 10 ^ (3 - pos) */ +static gint +convert_to_millisecs (gint decimals, gint pos) +{ + gint num = 1, den = 1, i = 3 - pos; + + while (i < 0) { + den *= 10; + i++; + } + while (i > 0) { + num *= 10; + i--; + } + /* if i == 0 we have exactly 3 decimals and nothing to do */ + return decimals * num / den; +} + +static gint64 +gst_mpdparser_get_xml_prop_duration (xmlNode * a_node, const gchar * property) +{ + xmlChar *prop_string; + gchar *str; + gint64 prop_duration = -1; + gint ret, read, len, pos, posT; + gint years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = + 0, decimals = 0; + gint sign = 1; + gboolean have_ms = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("duration: %s, len %d", str, len); + /* read "-" for sign, if present */ + pos = strcspn (str, "-"); + if (pos < len) { /* found "-" */ + if (pos != 0) { + GST_WARNING ("sign \"-\" non at the beginning of the string"); + return -1; + } + GST_TRACE ("found - sign at the beginning"); + sign = -1; + str++; + len--; + } + /* read "P" for period */ + pos = strcspn (str, "P"); + if (pos != 0) { + GST_WARNING ("P not found at the beginning of the string!"); + return -1; + } + str++; + len--; + /* read "T" for time (if present) */ + posT = strcspn (str, "T"); + len -= posT; + if (posT > 0) { + /* there is some room between P and T, so there must be a period section */ + /* read years, months, days */ + do { + GST_TRACE ("parsing substring %s", str); + pos = strcspn (str, "YMD"); + ret = sscanf (str, "%d", &read); + if (ret != 1) { + GST_WARNING ("can not read integer value from string %s!", str); + return -1; + } + switch (str[pos]) { + case 'Y': + years = read; + break; + case 'M': + months = read; + break; + case 'D': + days = read; + break; + default: + GST_WARNING ("unexpected char %c!", str[pos]); + return -1; + break; + } + GST_TRACE ("read number %d type %c", read, str[pos]); + str += (pos + 1); + posT -= (pos + 1); + } while (posT > 0); + + GST_TRACE ("Y:M:D=%d:%d:%d", years, months, days); + } + /* read "T" for time (if present) */ + /* here T is at pos == 0 */ + str++; + len--; + pos = 0; + if (pos < len) { + /* T found, there is a time section */ + /* read hours, minutes, seconds, cents of second */ + do { + GST_TRACE ("parsing substring %s", str); + pos = strcspn (str, "HMS,."); + ret = sscanf (str, "%d", &read); + if (ret != 1) { + GST_WARNING ("can not read integer value from string %s!", str); + return -1; + } + switch (str[pos]) { + case 'H': + hours = read; + break; + case 'M': + minutes = read; + break; + case 'S': + if (have_ms) { + /* we have read the decimal part of the seconds */ + decimals = convert_to_millisecs (read, pos); + GST_TRACE ("decimal number %d (%d digits) -> %d ms", read, pos, + decimals); + } else { + /* no decimals */ + seconds = read; + } + break; + case '.': + case ',': + /* we have read the integer part of a decimal number in seconds */ + seconds = read; + have_ms = TRUE; + break; + default: + GST_WARNING ("unexpected char %c!", str[pos]); + return -1; + break; + } + GST_TRACE ("read number %d type %c", read, str[pos]); + str += pos + 1; + len -= (pos + 1); + } while (len > 0); + + GST_TRACE ("H:M:S.MS=%d:%d:%d.%03d", hours, minutes, seconds, decimals); + } + + xmlFree (prop_string); + prop_duration = + sign * ((((((gint64) years * 365 + months * 30 + days) * 24 + + hours) * 60 + minutes) * 60 + seconds) * 1000 + decimals); + GST_LOG (" - %s: %" G_GINT64_FORMAT, property, prop_duration); + } + + return prop_duration; +} + +static gchar * +gst_mpdparser_get_xml_node_content (xmlNode * a_node) +{ + xmlChar *content = NULL; + + content = xmlNodeGetContent (a_node); + if (content) { + GST_LOG (" - %s: %s", a_node->name, content); + } + + return (gchar *) content; +} + +static gchar * +gst_mpdparser_get_xml_node_namespace (xmlNode * a_node, const gchar * prefix) +{ + xmlNs *curr_ns; + gchar *namespace = NULL; + + if (prefix == NULL) { + /* return the default namespace */ + namespace = g_strdup ((gchar *) a_node->ns->href); + if (namespace) { + GST_LOG (" - default namespace: %s", namespace); + } + } else { + /* look for the specified prefix in the namespace list */ + for (curr_ns = a_node->ns; curr_ns; curr_ns = curr_ns->next) { + if (xmlStrcmp (curr_ns->prefix, (xmlChar *) prefix) == 0) { + namespace = g_strdup ((gchar *) curr_ns->href); + if (namespace) { + GST_LOG (" - %s namespace: %s", curr_ns->prefix, curr_ns->href); + } + } + } + } + + return namespace; +} + +static void +gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node) +{ + GstBaseURL *new_base_url; + + new_base_url = g_slice_new0 (GstBaseURL); + if (new_base_url == NULL) { + GST_WARNING ("Allocation of BaseURL node failed!"); + return; + } + *list = g_list_append (*list, new_base_url); + + GST_LOG ("content of BaseURL node:"); + new_base_url->baseURL = gst_mpdparser_get_xml_node_content (a_node); + GST_LOG ("attributes of BaseURL node:"); + new_base_url->serviceLocation = + gst_mpdparser_get_xml_prop_string (a_node, "serviceLocation"); + new_base_url->byteRange = + gst_mpdparser_get_xml_prop_string (a_node, "byteRange"); +} + +static void +gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node) +{ + GstDescriptorType *new_descriptor; + + new_descriptor = g_slice_new0 (GstDescriptorType); + if (new_descriptor == NULL) { + GST_WARNING ("Allocation of DescriptorType node failed!"); + return; + } + *list = g_list_append (*list, new_descriptor); + + GST_LOG ("attributes of %s node:", a_node->name); + new_descriptor->schemeIdUri = + gst_mpdparser_get_xml_prop_string (a_node, "schemeIdUri"); + new_descriptor->value = gst_mpdparser_get_xml_prop_string (a_node, "value"); + + /* For adding ContentProtection to caps , get string of */ + if(xmlStrcmp (a_node->name,(xmlChar *) "ContentProtection") == 0) { + if (a_node->children->next){ + if(xmlStrcmp (a_node->children->next->name,(xmlChar *) "pro") == 0){ + if (a_node->children->next->type == XML_ELEMENT_NODE) { + new_descriptor->msprPro = gst_mpdparser_get_xml_node_content (a_node->children->next); + } + } + } + } +} + +static void +gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstContentComponentNode *new_content_component; + + new_content_component = g_slice_new0 (GstContentComponentNode); + if (new_content_component == NULL) { + GST_WARNING ("Allocation of ContentComponent node failed!"); + return; + } + *list = g_list_append (*list, new_content_component); + + GST_LOG ("attributes of ContentComponent node:"); + new_content_component->id = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0); + new_content_component->lang = + gst_mpdparser_get_xml_prop_string (a_node, "lang"); + new_content_component->contentType = + gst_mpdparser_get_xml_prop_string (a_node, "contentType"); + new_content_component->par = gst_mpdparser_get_xml_prop_ratio (a_node, "par"); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_content_component-> + Accessibility, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_content_component->Role, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_content_component-> + Rating, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_content_component-> + Viewpoint, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node) +{ + gchar *location; + + GST_LOG ("content of Location node:"); + location = gst_mpdparser_get_xml_node_content (a_node); + + *list = g_list_append (*list, location); +} + +static void +gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node) +{ + GstSubRepresentationNode *new_subrep; + + new_subrep = g_slice_new0 (GstSubRepresentationNode); + if (new_subrep == NULL) { + GST_WARNING ("Allocation of SubRepresentation node failed!"); + return; + } + *list = g_list_append (*list, new_subrep); + + GST_LOG ("attributes of SubRepresentation node:"); + new_subrep->level = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "level", 0); + new_subrep->dependencyLevel = + gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "dependencyLevel", + &new_subrep->size); + new_subrep->bandwidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0); + new_subrep->contentComponent = + gst_mpdparser_get_xml_prop_string_vector_type (a_node, + "contentComponent"); + + /* RepresentationBase extension */ + gst_mpdparser_parse_representation_base_type (&new_subrep->RepresentationBase, + a_node); +} + +static void +gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node) +{ + GstSegmentURLNode *new_segment_url; + + new_segment_url = g_slice_new0 (GstSegmentURLNode); + if (new_segment_url == NULL) { + GST_WARNING ("Allocation of SegmentURL node failed!"); + return; + } + *list = g_list_append (*list, new_segment_url); + + GST_LOG ("attributes of SegmentURL node:"); + new_segment_url->media = gst_mpdparser_get_xml_prop_string (a_node, "media"); + new_segment_url->mediaRange = + gst_mpdparser_get_xml_prop_range (a_node, "mediaRange"); + new_segment_url->index = gst_mpdparser_get_xml_prop_string (a_node, "index"); + new_segment_url->indexRange = + gst_mpdparser_get_xml_prop_range (a_node, "indexRange"); +} + +static void +gst_mpdparser_parse_url_type_node (GstURLType ** pointer, xmlNode * a_node) +{ + GstURLType *new_url_type; + + gst_mpdparser_free_url_type_node (*pointer); + *pointer = new_url_type = g_slice_new0 (GstURLType); + if (new_url_type == NULL) { + GST_WARNING ("Allocation of URLType node failed!"); + return; + } + + GST_LOG ("attributes of URLType node:"); + new_url_type->sourceURL = + gst_mpdparser_get_xml_prop_string (a_node, "sourceURL"); + new_url_type->range = gst_mpdparser_get_xml_prop_range (a_node, "range"); +} + +static void +gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer, + xmlNode * a_node) +{ + xmlNode *cur_node; + GstSegmentBaseType *seg_base_type; + + gst_mpdparser_free_seg_base_type_ext (*pointer); + *pointer = seg_base_type = g_slice_new0 (GstSegmentBaseType); + if (seg_base_type == NULL) { + GST_WARNING ("Allocation of SegmentBaseType node failed!"); + return; + } + + GST_LOG ("attributes of SegmentBaseType extension:"); + seg_base_type->timescale = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "timescale", 0); + seg_base_type->presentationTimeOffset = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, + "presentationTimeOffset", 0); + seg_base_type->indexRange = + gst_mpdparser_get_xml_prop_string (a_node, "indexRange"); + seg_base_type->indexRangeExact = + gst_mpdparser_get_xml_prop_boolean (a_node, "indexRangeExact"); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Initialization") == 0 || + xmlStrcmp (cur_node->name, (xmlChar *) "Initialisation") == 0) { + gst_mpdparser_parse_url_type_node (&seg_base_type->Initialization, + cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "RepresentationIndex") == 0) { + gst_mpdparser_parse_url_type_node (&seg_base_type->RepresentationIndex, + cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_s_node (GList ** list, xmlNode * a_node) +{ + GstSNode *new_s_node; + + new_s_node = g_slice_new0 (GstSNode); + if (new_s_node == NULL) { + GST_WARNING ("Allocation of S node failed!"); + return; + } + *list = g_list_append (*list, new_s_node); + + GST_LOG ("attributes of S node:"); + new_s_node->t = gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "t", 0); + new_s_node->d = gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "d", 0); + new_s_node->r = gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "r", 0); +} + +static void +gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer, + xmlNode * a_node) +{ + xmlNode *cur_node; + GstSegmentTimelineNode *new_seg_timeline; + + gst_mpdparser_free_segment_timeline_node (*pointer); + *pointer = new_seg_timeline = g_slice_new0 (GstSegmentTimelineNode); + if (new_seg_timeline == NULL) { + GST_WARNING ("Allocation of SegmentTimeline node failed!"); + return; + } + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "S") == 0) { + gst_mpdparser_parse_s_node (&new_seg_timeline->S, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer, + xmlNode * a_node) +{ + xmlNode *cur_node; + GstMultSegmentBaseType *mult_seg_base_type; + + gst_mpdparser_free_mult_seg_base_type_ext (*pointer); + *pointer = mult_seg_base_type = g_slice_new0 (GstMultSegmentBaseType); + if (mult_seg_base_type == NULL) { + GST_WARNING ("Allocation of MultipleSegmentBaseType node failed!"); + return; + } + + GST_LOG ("attributes of MultipleSegmentBaseType extension:"); + mult_seg_base_type->duration = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "duration", 0); + mult_seg_base_type->startNumber = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "startNumber", 1); + + GST_LOG ("extension of MultipleSegmentBaseType extension:"); + gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_type->SegBaseType, + a_node); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTimeline") == 0) { + gst_mpdparser_parse_segment_timeline_node (&mult_seg_base_type-> + SegmentTimeline, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "BitstreamSwitching") == 0) { + gst_mpdparser_parse_url_type_node (&mult_seg_base_type-> + BitstreamSwitching, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** pointer, + xmlNode * a_node) +{ + xmlNode *cur_node; + GstSegmentListNode *new_segment_list; + + gst_mpdparser_free_segment_list_node (*pointer); + *pointer = new_segment_list = g_slice_new0 (GstSegmentListNode); + if (new_segment_list == NULL) { + GST_WARNING ("Allocation of SegmentList node failed!"); + return; + } + + GST_LOG ("extension of SegmentList node:"); + gst_mpdparser_parse_mult_seg_base_type_ext (&new_segment_list-> + MultSegBaseType, a_node); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentURL") == 0) { + gst_mpdparser_parse_segment_url_node (&new_segment_list->SegmentURL, + cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType ** + pointer, xmlNode * a_node) +{ + xmlNode *cur_node; + GstRepresentationBaseType *representation_base; + + gst_mpdparser_free_representation_base_type (*pointer); + *pointer = representation_base = g_slice_new0 (GstRepresentationBaseType); + if (representation_base == NULL) { + GST_WARNING ("Allocation of RepresentationBaseType node failed!"); + return; + } + + GST_LOG ("attributes of RepresentationBaseType extension:"); + representation_base->profiles = + gst_mpdparser_get_xml_prop_string (a_node, "profiles"); + representation_base->width = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "width", 0); + representation_base->height = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "height", 0); + representation_base->bandwidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0); + representation_base->sar = gst_mpdparser_get_xml_prop_ratio (a_node, "sar"); + representation_base->frameRate = + gst_mpdparser_get_xml_prop_framerate (a_node, "frameRate"); + representation_base->audioSamplingRate = + gst_mpdparser_get_xml_prop_string (a_node, "audioSamplingRate"); + representation_base->mimeType = + gst_mpdparser_get_xml_prop_string (a_node, "mimeType"); + representation_base->segmentProfiles = + gst_mpdparser_get_xml_prop_string (a_node, "segmentProfiles"); + representation_base->codecs = + gst_mpdparser_get_xml_prop_string (a_node, "codecs"); + representation_base->maximumSAPPeriod = + gst_mpdparser_get_xml_prop_double (a_node, "maximumSAPPeriod"); + representation_base->startWithSAP = + gst_mpdparser_get_xml_prop_SAP_type (a_node, "startWithSAP"); + representation_base->maxPlayoutRate = + gst_mpdparser_get_xml_prop_double (a_node, "maxPlayoutRate"); + representation_base->codingDependency = + gst_mpdparser_get_xml_prop_boolean (a_node, "codingDependency"); + representation_base->scanType = + gst_mpdparser_get_xml_prop_string (a_node, "scanType"); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) { + gst_mpdparser_parse_descriptor_type_node (&representation_base-> + FramePacking, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "AudioChannelConfiguration") == 0) { + gst_mpdparser_parse_descriptor_type_node (&representation_base-> + AudioChannelConfiguration, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "ContentProtection") == 0) { + gst_mpdparser_parse_descriptor_type_node (&representation_base-> + ContentProtection, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstRepresentationNode *new_representation; + + new_representation = g_slice_new0 (GstRepresentationNode); + if (new_representation == NULL) { + GST_WARNING ("Allocation of Representation node failed!"); + return; + } + *list = g_list_append (*list, new_representation); + + GST_LOG ("attributes of Representation node:"); + new_representation->id = gst_mpdparser_get_xml_prop_string (a_node, "id"); + new_representation->bandwidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0); + new_representation->qualityRanking = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "qualityRanking", 0); + new_representation->dependencyId = + gst_mpdparser_get_xml_prop_string_vector_type (a_node, "dependencyId"); + new_representation->mediaStreamStructureId = + gst_mpdparser_get_xml_prop_string_vector_type (a_node, + "mediaStreamStructureId"); + + /* RepresentationBase extension */ + gst_mpdparser_parse_representation_base_type (&new_representation-> + RepresentationBase, a_node); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) { + gst_mpdparser_parse_seg_base_type_ext (&new_representation->SegmentBase, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { + gst_mpdparser_parse_segment_template_node (&new_representation-> + SegmentTemplate, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { + gst_mpdparser_parse_segment_list_node (&new_representation->SegmentList, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { + gst_mpdparser_parse_baseURL_node (&new_representation->BaseURLs, + cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "SubRepresentation") == 0) { + gst_mpdparser_parse_subrepresentation_node (&new_representation-> + SubRepresentations, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstAdaptationSetNode *new_adap_set; + + new_adap_set = g_slice_new0 (GstAdaptationSetNode); + if (new_adap_set == NULL) { + GST_WARNING ("Allocation of AdaptationSet node failed!"); + return; + } + *list = g_list_append (*list, new_adap_set); + + GST_LOG ("attributes of AdaptationSet node:"); + new_adap_set->id = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0); + new_adap_set->group = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "group", 0); + new_adap_set->lang = gst_mpdparser_get_xml_prop_string (a_node, "lang"); + new_adap_set->contentType = + gst_mpdparser_get_xml_prop_string (a_node, "contentType"); + new_adap_set->par = gst_mpdparser_get_xml_prop_ratio (a_node, "par"); + new_adap_set->minBandwidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minBandwidth", 0); + new_adap_set->maxBandwidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxBandwidth", 0); + new_adap_set->minWidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minWidth", 0); + new_adap_set->maxWidth = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxWidth", 0); + new_adap_set->minHeight = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minHeight", 0); + new_adap_set->maxHeight = + gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxHeight", 0); + new_adap_set->minFrameRate = + gst_mpdparser_get_xml_prop_framerate (a_node, "minFrameRate"); + new_adap_set->maxFrameRate = + gst_mpdparser_get_xml_prop_framerate (a_node, "maxFrameRate"); + new_adap_set->segmentAlignment = + gst_mpdparser_get_xml_prop_cond_uint (a_node, "segmentAlignment"); + new_adap_set->subsegmentAlignment = + gst_mpdparser_get_xml_prop_cond_uint (a_node, "subsegmentAlignment"); + new_adap_set->subsegmentStartsWithSAP = + gst_mpdparser_get_xml_prop_SAP_type (a_node, "subsegmentStartsWithSAP"); + new_adap_set->bitstreamSwitching = + gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching"); + + /* RepresentationBase extension */ + gst_mpdparser_parse_representation_base_type (&new_adap_set-> + RepresentationBase, a_node); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Accessibility, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Role, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Rating, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Viewpoint, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Representation") == 0) { + gst_mpdparser_parse_representation_node (&new_adap_set->Representations, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { + gst_mpdparser_parse_baseURL_node (&new_adap_set->BaseURLs, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) { + gst_mpdparser_parse_seg_base_type_ext (&new_adap_set->SegmentBase, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { + gst_mpdparser_parse_segment_list_node (&new_adap_set->SegmentList, + cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "ContentComponent") == 0) { + gst_mpdparser_parse_content_component_node (&new_adap_set-> + ContentComponents, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { + gst_mpdparser_parse_segment_template_node (&new_adap_set-> + SegmentTemplate, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node) +{ + GstSubsetNode *new_subset; + + new_subset = g_slice_new0 (GstSubsetNode); + if (new_subset == NULL) { + GST_WARNING ("Allocation of Subset node failed!"); + return; + } + *list = g_list_append (*list, new_subset); + + GST_LOG ("attributes of Subset node:"); + new_subset->contains = + gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "contains", + &new_subset->size); +} + +static void +gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer, + xmlNode * a_node) +{ + GstSegmentTemplateNode *new_segment_template; + + gst_mpdparser_free_segment_template_node (*pointer); + *pointer = new_segment_template = g_slice_new0 (GstSegmentTemplateNode); + if (new_segment_template == NULL) { + GST_WARNING ("Allocation of SegmentTemplate node failed!"); + return; + } + + GST_LOG ("extension of SegmentTemplate node:"); + gst_mpdparser_parse_mult_seg_base_type_ext (&new_segment_template-> + MultSegBaseType, a_node); + + GST_LOG ("attributes of SegmentTemplate node:"); + new_segment_template->media = + gst_mpdparser_get_xml_prop_string (a_node, "media"); + new_segment_template->index = + gst_mpdparser_get_xml_prop_string (a_node, "index"); + new_segment_template->initialization = + gst_mpdparser_get_xml_prop_string (a_node, "initialization"); + new_segment_template->bitstreamSwitching = + gst_mpdparser_get_xml_prop_string (a_node, "bitstreamSwitching"); +} + +static void +gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstPeriodNode *new_period; + + new_period = g_slice_new0 (GstPeriodNode); + if (new_period == NULL) { + GST_WARNING ("Allocation of Period node failed!"); + return; + } + *list = g_list_append (*list, new_period); + + GST_LOG ("attributes of Period node:"); + new_period->id = gst_mpdparser_get_xml_prop_string (a_node, "id"); + new_period->start = gst_mpdparser_get_xml_prop_duration (a_node, "start"); + new_period->duration = + gst_mpdparser_get_xml_prop_duration (a_node, "duration"); + new_period->bitstreamSwitching = + gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching"); + + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "AdaptationSet") == 0) { + gst_mpdparser_parse_adaptation_set_node (&new_period->AdaptationSets, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) { + gst_mpdparser_parse_seg_base_type_ext (&new_period->SegmentBase, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { + gst_mpdparser_parse_segment_list_node (&new_period->SegmentList, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { + gst_mpdparser_parse_segment_template_node (&new_period->SegmentTemplate, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Subset") == 0) { + gst_mpdparser_parse_subset_node (&new_period->Subsets, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { + gst_mpdparser_parse_baseURL_node (&new_period->BaseURLs, cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstProgramInformationNode *new_prog_info; + + new_prog_info = g_slice_new0 (GstProgramInformationNode); + if (new_prog_info == NULL) { + GST_WARNING ("Allocation of ProgramInfo node failed!"); + return; + } + *list = g_list_append (*list, new_prog_info); + + GST_LOG ("attributes of ProgramInformation node:"); + new_prog_info->lang = gst_mpdparser_get_xml_prop_string (a_node, "lang"); + new_prog_info->moreInformationURL = + gst_mpdparser_get_xml_prop_string (a_node, "moreInformationURL"); + + /* explore children nodes */ + GST_LOG ("children of ProgramInformation node:"); + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Title") == 0) { + new_prog_info->Title = gst_mpdparser_get_xml_node_content (cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) { + new_prog_info->Source = gst_mpdparser_get_xml_node_content (cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) { + new_prog_info->Copyright = + gst_mpdparser_get_xml_node_content (cur_node); + } + } + } +} + +static void +gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node) +{ + GstMetricsRangeNode *new_metrics_range; + + new_metrics_range = g_slice_new0 (GstMetricsRangeNode); + if (new_metrics_range == NULL) { + GST_WARNING ("Allocation of Metrics Range node failed!"); + return; + } + *list = g_list_append (*list, new_metrics_range); + + GST_LOG ("attributes of Metrics Range node:"); + new_metrics_range->starttime = + gst_mpdparser_get_xml_prop_duration (a_node, "starttime"); + new_metrics_range->duration = + gst_mpdparser_get_xml_prop_duration (a_node, "duration"); +} + +static void +gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstMetricsNode *new_metrics; + + new_metrics = g_slice_new0 (GstMetricsNode); + if (new_metrics == NULL) { + GST_WARNING ("Allocation of Metrics node failed!"); + return; + } + *list = g_list_append (*list, new_metrics); + + GST_LOG ("attributes of Metrics node:"); + new_metrics->metrics = gst_mpdparser_get_xml_prop_string (a_node, "metrics"); + + /* explore children nodes */ + GST_LOG ("children of Metrics node:"); + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Range") == 0) { + gst_mpdparser_parse_metrics_range_node (&new_metrics->MetricsRanges, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Reporting") == 0) { + /* No reporting scheme is specified in this part of ISO/IEC 23009. + * It is expected that external specifications may define formats + * and delivery for the reporting data. */ + GST_LOG (" - Reporting node found (unknown structure)"); + } + } + } +} + +static void +gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node) +{ + xmlNode *cur_node; + GstMPDNode *new_mpd; + + gst_mpdparser_free_mpd_node (*pointer); + *pointer = new_mpd = g_slice_new0 (GstMPDNode); + if (new_mpd == NULL) { + GST_WARNING ("Allocation of MPD node failed!"); + return; + } + + GST_LOG ("namespaces of root MPD node:"); + new_mpd->default_namespace = + gst_mpdparser_get_xml_node_namespace (a_node, NULL); + new_mpd->namespace_xsi = gst_mpdparser_get_xml_node_namespace (a_node, "xsi"); + new_mpd->namespace_ext = gst_mpdparser_get_xml_node_namespace (a_node, "ext"); + + GST_LOG ("attributes of root MPD node:"); + new_mpd->schemaLocation = + gst_mpdparser_get_xml_prop_string (a_node, "schemaLocation"); + new_mpd->id = gst_mpdparser_get_xml_prop_string (a_node, "id"); + new_mpd->profiles = gst_mpdparser_get_xml_prop_string (a_node, "profiles"); + new_mpd->type = gst_mpdparser_get_xml_prop_type (a_node, "type"); + new_mpd->availabilityStartTime = + gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityStartTime"); + new_mpd->availabilityEndTime = + gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityEndTime"); + new_mpd->mediaPresentationDuration = + gst_mpdparser_get_xml_prop_duration (a_node, "mediaPresentationDuration"); + new_mpd->minimumUpdatePeriod = + gst_mpdparser_get_xml_prop_duration (a_node, "minimumUpdatePeriod"); + new_mpd->minBufferTime = + gst_mpdparser_get_xml_prop_duration (a_node, "minBufferTime"); + new_mpd->timeShiftBufferDepth = + gst_mpdparser_get_xml_prop_duration (a_node, "timeShiftBufferDepth"); + new_mpd->suggestedPresentationDelay = + gst_mpdparser_get_xml_prop_duration (a_node, + "suggestedPresentationDelay"); + new_mpd->maxSegmentDuration = + gst_mpdparser_get_xml_prop_duration (a_node, "maxSegmentDuration"); + new_mpd->maxSubsegmentDuration = + gst_mpdparser_get_xml_prop_duration (a_node, "maxSubsegmentDuration"); + + /* explore children Period nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Period") == 0) { + gst_mpdparser_parse_period_node (&new_mpd->Periods, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "ProgramInformation") == 0) { + gst_mpdparser_parse_program_info_node (&new_mpd->ProgramInfo, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { + gst_mpdparser_parse_baseURL_node (&new_mpd->BaseURLs, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Location") == 0) { + gst_mpdparser_parse_location_node (&new_mpd->Locations, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) { + gst_mpdparser_parse_metrics_node (&new_mpd->Metrics, cur_node); + } + } + } +} + +/* comparison functions */ +static int +strncmp_ext (const char *s1, const char *s2) +{ + if (s1 == NULL && s2 == NULL) + return 0; + if (s1 == NULL && s2 != NULL) + return 1; + if (s2 == NULL && s1 != NULL) + return 1; + return strncmp (s1, s2, strlen (s2)); +} + +/* navigation functions */ +static GstAdaptationSetNode * +gst_mpdparser_get_first_adapt_set_with_mimeType (GList * AdaptationSets, + const gchar * mimeType) +{ + GList *list; + GstAdaptationSetNode *adapt_set; + + if (AdaptationSets == NULL) + return NULL; + + for (list = g_list_first (AdaptationSets); list; list = g_list_next (list)) { + adapt_set = (GstAdaptationSetNode *) list->data; + if (adapt_set) { + gchar *this_mimeType = NULL; + GstRepresentationNode *rep; + rep = + gst_mpdparser_get_lowest_representation (adapt_set->Representations); + if (!rep) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + return NULL; + } + if (rep->RepresentationBase) + this_mimeType = rep->RepresentationBase->mimeType; + if (!this_mimeType && adapt_set->RepresentationBase) { + this_mimeType = adapt_set->RepresentationBase->mimeType; + } + if (strncmp_ext (this_mimeType, mimeType) == 0) + return adapt_set; + } + } + + return NULL; +} + +/* if idx < 0, returns the highest adaptation set with the given mimeType + * if idx >= 0, returns the highest adaptation set with the given mimeType and an index <= idx + */ +static GstAdaptationSetNode * +gst_mpdparser_get_adapt_set_with_mimeType_and_idx (GList * AdaptationSets, + const gchar * mimeType, gint idx) +{ + GList *list; + GstAdaptationSetNode *adapt_set, *selected = NULL; + gint i = 0; + + if (AdaptationSets == NULL) + return NULL; + + for (list = g_list_first (AdaptationSets); list; list = g_list_next (list)) { + adapt_set = (GstAdaptationSetNode *) list->data; + if (adapt_set) { + gchar *this_mimeType = NULL; + GstRepresentationNode *rep; + rep = + gst_mpdparser_get_lowest_representation (adapt_set->Representations); + if (!rep) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + return NULL; + } + if (rep->RepresentationBase) + this_mimeType = rep->RepresentationBase->mimeType; + if (!this_mimeType && adapt_set->RepresentationBase) { + this_mimeType = adapt_set->RepresentationBase->mimeType; + } + if (strncmp_ext (this_mimeType, mimeType) == 0) { + if (idx < 0 || i <= idx) + selected = adapt_set; + i++; + } + } + } + + return selected; +} + +static GstAdaptationSetNode * +gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang (GList * + AdaptationSets, const gchar * mimeType, const gchar * lang) +{ + GList *list; + GstAdaptationSetNode *adapt_set; + + if (AdaptationSets == NULL) + return NULL; + + for (list = g_list_first (AdaptationSets); list; list = g_list_next (list)) { + adapt_set = (GstAdaptationSetNode *) list->data; + if (adapt_set) { + GstRepresentationNode *rep; + gchar *this_lang = adapt_set->lang; + gchar *this_mimeType = NULL; + rep = + gst_mpdparser_get_lowest_representation (adapt_set->Representations); + if (!rep) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + return NULL; + } + if (rep->RepresentationBase) + this_mimeType = rep->RepresentationBase->mimeType; + if (!this_mimeType && adapt_set->RepresentationBase) { + this_mimeType = adapt_set->RepresentationBase->mimeType; + } + if (strncmp_ext (this_mimeType, mimeType) == 0 + && strncmp_ext (this_lang, lang) == 0) + return adapt_set; + } + } + + return NULL; +} + +static GstRepresentationNode * +gst_mpdparser_get_lowest_representation (GList * Representations) +{ + GList *list = NULL; + + if (Representations == NULL) + return NULL; + + list = g_list_first (Representations); + + return list ? (GstRepresentationNode *) list->data : NULL; +} + +#if 0 +static GstRepresentationNode * +gst_mpdparser_get_highest_representation (GList * Representations) +{ + GList *list = NULL; + + if (Representations == NULL) + return NULL; + + list = g_list_last (Representations); + + return list ? (GstRepresentationNode *) list->data : NULL; +} + +static GstRepresentationNode * +gst_mpdparser_get_representation_with_max_bandwidth (GList * Representations, + gint max_bandwidth) +{ + GList *list = NULL; + GstRepresentationNode *representation, *best_rep = NULL; + + if (Representations == NULL) + return NULL; + + if (max_bandwidth <= 0) /* 0 => get highest representation available */ + return gst_mpdparser_get_highest_representation (Representations); + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + representation = (GstRepresentationNode *) list->data; + if (representation && representation->bandwidth <= max_bandwidth) { + best_rep = representation; + } + } + + return best_rep; +} +#endif + +static GstSegmentBaseType * +gst_mpdparser_get_segment_base (GstPeriodNode * Period, + GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation) +{ + GstSegmentBaseType *SegmentBase = NULL; + + if (Representation && Representation->SegmentBase + && Representation->SegmentBase->Initialization) { + SegmentBase = Representation->SegmentBase; + } else if (AdaptationSet && AdaptationSet->SegmentBase + && AdaptationSet->SegmentBase->Initialization) { + SegmentBase = AdaptationSet->SegmentBase; + } else if (Period && Period->SegmentBase + && Period->SegmentBase->Initialization) { + SegmentBase = Period->SegmentBase; + } + /* the SegmentBase element could be encoded also inside a SegmentList element */ + if (SegmentBase == NULL) { + if (Representation && Representation->SegmentList + && Representation->SegmentList->MultSegBaseType + && Representation->SegmentList->MultSegBaseType->SegBaseType + && Representation->SegmentList->MultSegBaseType->SegBaseType-> + Initialization) { + SegmentBase = Representation->SegmentList->MultSegBaseType->SegBaseType; + } else if (AdaptationSet && AdaptationSet->SegmentList + && AdaptationSet->SegmentList->MultSegBaseType + && AdaptationSet->SegmentList->MultSegBaseType->SegBaseType + && AdaptationSet->SegmentList->MultSegBaseType->SegBaseType-> + Initialization) { + SegmentBase = AdaptationSet->SegmentList->MultSegBaseType->SegBaseType; + } else if (Period && Period->SegmentList + && Period->SegmentList->MultSegBaseType + && Period->SegmentList->MultSegBaseType->SegBaseType + && Period->SegmentList->MultSegBaseType->SegBaseType->Initialization) { + SegmentBase = Period->SegmentList->MultSegBaseType->SegBaseType; + } + } + + return SegmentBase; +} + +gint +gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, + guint64 max_bandwidth) +{ + GList *list = NULL, *best = NULL; + GstRepresentationNode *representation; + guint64 best_bandwidth = 0; + + GST_DEBUG ("max bandwidth %" G_GUINT64_FORMAT, max_bandwidth); + + if (Representations == NULL) + return -1; + + if (max_bandwidth <= 0) /* 0 => get lowest representation available */ + return 0; + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + representation = (GstRepresentationNode *) list->data; + if (representation && representation->bandwidth <= max_bandwidth && + representation->bandwidth > best_bandwidth) { + best = list; + best_bandwidth = representation->bandwidth; + } + } + + return best ? g_list_position (Representations, best) : -1; +} + +static GstSegmentListNode * +gst_mpdparser_get_segment_list (GstPeriodNode * Period, + GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation) +{ + GstSegmentListNode *SegmentList = NULL; + + if (Representation && Representation->SegmentList) { + SegmentList = Representation->SegmentList; + } else if (AdaptationSet && AdaptationSet->SegmentList) { + SegmentList = AdaptationSet->SegmentList; + } else { + SegmentList = Period->SegmentList; + } + + return SegmentList; +} + +/* memory management functions */ +static void +gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node) +{ + if (mpd_node) { + g_free (mpd_node->default_namespace); + g_free (mpd_node->namespace_xsi); + g_free (mpd_node->namespace_ext); + g_free (mpd_node->schemaLocation); + g_free (mpd_node->id); + g_free (mpd_node->profiles); + if (mpd_node->availabilityStartTime) + gst_date_time_unref (mpd_node->availabilityStartTime); + if (mpd_node->availabilityEndTime) + gst_date_time_unref (mpd_node->availabilityEndTime); + g_list_foreach (mpd_node->ProgramInfo, + (GFunc) gst_mpdparser_free_prog_info_node, NULL); + g_list_free (mpd_node->ProgramInfo); + g_list_foreach (mpd_node->BaseURLs, + (GFunc) gst_mpdparser_free_base_url_node, NULL); + g_list_free (mpd_node->BaseURLs); + g_list_foreach (mpd_node->Locations, (GFunc) g_free, NULL); + g_list_free (mpd_node->Locations); + g_list_foreach (mpd_node->Periods, (GFunc) gst_mpdparser_free_period_node, + NULL); + g_list_free (mpd_node->Periods); + g_list_foreach (mpd_node->Metrics, (GFunc) gst_mpdparser_free_metrics_node, + NULL); + g_list_free (mpd_node->Metrics); + g_slice_free (GstMPDNode, mpd_node); + } +} + +static void +gst_mpdparser_free_prog_info_node (GstProgramInformationNode * prog_info_node) +{ + if (prog_info_node) { + g_free (prog_info_node->lang); + g_free (prog_info_node->moreInformationURL); + g_free (prog_info_node->Title); + g_free (prog_info_node->Source); + g_free (prog_info_node->Copyright); + g_slice_free (GstProgramInformationNode, prog_info_node); + } +} + +static void +gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node) +{ + if (metrics_node) { + g_free (metrics_node->metrics); + g_list_foreach (metrics_node->MetricsRanges, + (GFunc) gst_mpdparser_free_metrics_range_node, NULL); + g_list_free (metrics_node->MetricsRanges); + } +} + +static void +gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * metrics_range_node) +{ + if (metrics_range_node) { + g_slice_free (GstMetricsRangeNode, metrics_range_node); + } +} + +static void +gst_mpdparser_free_period_node (GstPeriodNode * period_node) +{ + if (period_node) { + g_free (period_node->id); + gst_mpdparser_free_seg_base_type_ext (period_node->SegmentBase); + gst_mpdparser_free_segment_list_node (period_node->SegmentList); + gst_mpdparser_free_segment_template_node (period_node->SegmentTemplate); + g_list_foreach (period_node->AdaptationSets, + (GFunc) gst_mpdparser_free_adaptation_set_node, NULL); + g_list_free (period_node->AdaptationSets); + g_list_foreach (period_node->Subsets, + (GFunc) gst_mpdparser_free_subset_node, NULL); + g_list_free (period_node->Subsets); + g_list_foreach (period_node->BaseURLs, + (GFunc) gst_mpdparser_free_base_url_node, NULL); + g_list_free (period_node->BaseURLs); + g_slice_free (GstPeriodNode, period_node); + } +} + +static void +gst_mpdparser_free_subset_node (GstSubsetNode * subset_node) +{ + if (subset_node) { + g_free (subset_node->contains); + g_slice_free (GstSubsetNode, subset_node); + } +} + +static void +gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode * + segment_template_node) +{ + if (segment_template_node) { + g_free (segment_template_node->media); + g_free (segment_template_node->index); + g_free (segment_template_node->initialization); + g_free (segment_template_node->bitstreamSwitching); + /* MultipleSegmentBaseType extension */ + gst_mpdparser_free_mult_seg_base_type_ext + (segment_template_node->MultSegBaseType); + g_slice_free (GstSegmentTemplateNode, segment_template_node); + } +} + +static void +gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * + representation_base) +{ + if (representation_base) { + g_free (representation_base->profiles); + g_slice_free (GstRatio, representation_base->sar); + g_slice_free (GstFrameRate, representation_base->frameRate); + g_free (representation_base->audioSamplingRate); + g_free (representation_base->mimeType); + g_free (representation_base->segmentProfiles); + g_free (representation_base->codecs); + g_free (representation_base->scanType); + g_list_foreach (representation_base->FramePacking, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (representation_base->FramePacking); + g_list_foreach (representation_base->AudioChannelConfiguration, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (representation_base->AudioChannelConfiguration); + g_list_foreach (representation_base->ContentProtection, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (representation_base->ContentProtection); + g_slice_free (GstRepresentationBaseType, representation_base); + } +} + +static void +gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * + adaptation_set_node) +{ + if (adaptation_set_node) { + g_free (adaptation_set_node->lang); + g_free (adaptation_set_node->contentType); + g_slice_free (GstRatio, adaptation_set_node->par); + g_slice_free (GstFrameRate, adaptation_set_node->minFrameRate); + g_slice_free (GstFrameRate, adaptation_set_node->maxFrameRate); + g_slice_free (GstConditionalUintType, + adaptation_set_node->segmentAlignment); + g_slice_free (GstConditionalUintType, + adaptation_set_node->subsegmentAlignment); + g_list_foreach (adaptation_set_node->Accessibility, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (adaptation_set_node->Accessibility); + g_list_foreach (adaptation_set_node->Role, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (adaptation_set_node->Role); + g_list_foreach (adaptation_set_node->Rating, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (adaptation_set_node->Rating); + g_list_foreach (adaptation_set_node->Viewpoint, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (adaptation_set_node->Viewpoint); + gst_mpdparser_free_representation_base_type + (adaptation_set_node->RepresentationBase); + gst_mpdparser_free_seg_base_type_ext (adaptation_set_node->SegmentBase); + gst_mpdparser_free_segment_list_node (adaptation_set_node->SegmentList); + gst_mpdparser_free_segment_template_node (adaptation_set_node-> + SegmentTemplate); + g_list_foreach (adaptation_set_node->BaseURLs, + (GFunc) gst_mpdparser_free_base_url_node, NULL); + g_list_free (adaptation_set_node->BaseURLs); + g_list_foreach (adaptation_set_node->Representations, + (GFunc) gst_mpdparser_free_representation_node, NULL); + g_list_free (adaptation_set_node->Representations); + g_list_foreach (adaptation_set_node->ContentComponents, + (GFunc) gst_mpdparser_free_content_component_node, NULL); + g_list_free (adaptation_set_node->ContentComponents); + g_slice_free (GstAdaptationSetNode, adaptation_set_node); + } +} + +static void +gst_mpdparser_free_representation_node (GstRepresentationNode * + representation_node) +{ + if (representation_node) { + g_free (representation_node->id); + g_strfreev (representation_node->dependencyId); + g_strfreev (representation_node->mediaStreamStructureId); + gst_mpdparser_free_representation_base_type + (representation_node->RepresentationBase); + g_list_foreach (representation_node->SubRepresentations, + (GFunc) gst_mpdparser_free_subrepresentation_node, NULL); + g_list_free (representation_node->SubRepresentations); + gst_mpdparser_free_seg_base_type_ext (representation_node->SegmentBase); + gst_mpdparser_free_segment_template_node (representation_node-> + SegmentTemplate); + gst_mpdparser_free_segment_list_node (representation_node->SegmentList); + g_list_foreach (representation_node->BaseURLs, + (GFunc) gst_mpdparser_free_base_url_node, NULL); + g_list_free (representation_node->BaseURLs); + g_slice_free (GstRepresentationNode, representation_node); + } +} + +static void +gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode * + subrep_node) +{ + if (subrep_node) { + gst_mpdparser_free_representation_base_type (subrep_node-> + RepresentationBase); + g_free (subrep_node->dependencyLevel); + g_strfreev (subrep_node->contentComponent); + } +} + +static void +gst_mpdparser_free_s_node (GstSNode * s_node) +{ + if (s_node) { + g_slice_free (GstSNode, s_node); + } +} + +static void +gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * seg_timeline) +{ + if (seg_timeline) { + g_list_foreach (seg_timeline->S, (GFunc) gst_mpdparser_free_s_node, NULL); + g_list_free (seg_timeline->S); + g_slice_free (GstSegmentTimelineNode, seg_timeline); + } +} + +static void +gst_mpdparser_free_url_type_node (GstURLType * url_type_node) +{ + if (url_type_node) { + g_free (url_type_node->sourceURL); + g_slice_free (GstRange, url_type_node->range); + g_slice_free (GstURLType, url_type_node); + } +} + +static void +gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * seg_base_type) +{ + if (seg_base_type) { + g_free (seg_base_type->indexRange); + gst_mpdparser_free_url_type_node (seg_base_type->Initialization); + gst_mpdparser_free_url_type_node (seg_base_type->RepresentationIndex); + g_slice_free (GstSegmentBaseType, seg_base_type); + } +} + +static void +gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType * + mult_seg_base_type) +{ + if (mult_seg_base_type) { + /* SegmentBaseType extension */ + gst_mpdparser_free_seg_base_type_ext (mult_seg_base_type->SegBaseType); + gst_mpdparser_free_segment_timeline_node + (mult_seg_base_type->SegmentTimeline); + gst_mpdparser_free_url_type_node (mult_seg_base_type->BitstreamSwitching); + g_slice_free (GstMultSegmentBaseType, mult_seg_base_type); + } +} + +static void +gst_mpdparser_free_segment_list_node (GstSegmentListNode * segment_list_node) +{ + if (segment_list_node) { + g_list_foreach (segment_list_node->SegmentURL, + (GFunc) gst_mpdparser_free_segment_url_node, NULL); + g_list_free (segment_list_node->SegmentURL); + /* MultipleSegmentBaseType extension */ + gst_mpdparser_free_mult_seg_base_type_ext + (segment_list_node->MultSegBaseType); + g_slice_free (GstSegmentListNode, segment_list_node); + } +} + +static void +gst_mpdparser_free_segment_url_node (GstSegmentURLNode * segment_url) +{ + if (segment_url) { + g_free (segment_url->media); + g_slice_free (GstRange, segment_url->mediaRange); + g_free (segment_url->index); + g_slice_free (GstRange, segment_url->indexRange); + g_slice_free (GstSegmentURLNode, segment_url); + } +} + +static void +gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node) +{ + if (base_url_node) { + g_free (base_url_node->baseURL); + g_free (base_url_node->serviceLocation); + g_free (base_url_node->byteRange); + g_slice_free (GstBaseURL, base_url_node); + } +} + +static void +gst_mpdparser_free_descriptor_type_node (GstDescriptorType * descriptor_type) +{ + if (descriptor_type) { + g_free (descriptor_type->schemeIdUri); + g_free (descriptor_type->value); + } +} + +static void +gst_mpdparser_free_content_component_node (GstContentComponentNode * + content_component_node) +{ + if (content_component_node) { + g_free (content_component_node->lang); + g_free (content_component_node->contentType); + g_slice_free (GstRatio, content_component_node->par); + g_list_foreach (content_component_node->Accessibility, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (content_component_node->Accessibility); + g_list_foreach (content_component_node->Role, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (content_component_node->Role); + g_list_foreach (content_component_node->Rating, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (content_component_node->Rating); + g_list_foreach (content_component_node->Viewpoint, + (GFunc) gst_mpdparser_free_descriptor_type_node, NULL); + g_list_free (content_component_node->Viewpoint); + g_slice_free (GstContentComponentNode, content_component_node); + } +} + +static void +gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period) +{ + if (stream_period) { + g_slice_free (GstStreamPeriod, stream_period); + } +} + +static void +gst_mpdparser_free_media_segment (GstMediaSegment * media_segment) +{ + if (media_segment) { + g_slice_free (GstMediaSegment, media_segment); + } +} + +static void +gst_mpdparser_free_active_stream (GstActiveStream * active_stream) +{ + if (active_stream) { + g_list_foreach (active_stream->segments, + (GFunc) gst_mpdparser_free_media_segment, NULL); + if (active_stream->segments) + g_list_free (active_stream->segments); + g_slice_free (GstActiveStream, active_stream); + } +} + +static gchar * +gst_mpdparser_get_segmentURL_for_range (const gchar * url, GstRange * range) +{ + gchar *segmentURL; + + if (range) { + gchar *range_suffix; + range_suffix = + g_strdup_printf ("?range=%llu-%llu", range->first_byte_pos, + range->last_byte_pos); + segmentURL = g_strconcat (url, range_suffix, NULL); + g_free (range_suffix); + } else { + segmentURL = g_strdup (url); + } + + return segmentURL; +} + +static gchar * +gst_mpdparser_get_mediaURL (GstMpdClient * client, + GstSegmentURLNode * segmentURL) +{ + const gchar *url_prefix; + + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (segmentURL != NULL, NULL); + + url_prefix = + segmentURL->media ? segmentURL-> + media : gst_mpdparser_get_baseURL (client); + g_return_val_if_fail (url_prefix != NULL, NULL); + + return gst_mpdparser_get_segmentURL_for_range (url_prefix, + segmentURL->mediaRange);//if not present @media attribute, it is mapped to baseURL +} + +static gchar * +gst_mpdparser_get_initializationURL (GstURLType * InitializationURL) +{ + g_return_val_if_fail (InitializationURL != NULL, NULL); + g_return_val_if_fail (InitializationURL->sourceURL != NULL, NULL); + + return gst_mpdparser_get_segmentURL_for_range (InitializationURL->sourceURL, + InitializationURL->range); +} + +static gchar * +gst_mpdparser_build_URL_from_template (const gchar * url_template, + const gchar * id, guint number, guint bandwidth, guint time) +{ + static gchar default_format[] = "%01d"; + gchar **tokens, *token, *ret, *format; + gint i, num_tokens; + gboolean last_token_par = TRUE; /* last token was a parameter */ + + g_return_val_if_fail (url_template != NULL, NULL); + tokens = g_strsplit_set (url_template, "$", -1); + if (!tokens) { + GST_WARNING ("Scan of URL template failed!"); + return NULL; + } + num_tokens = g_strv_length (tokens); + + for (i = 0; i < num_tokens; i++) { + token = tokens[i]; + format = default_format; + + if (!g_strcmp0 (token, "RepresentationID")) { + tokens[i] = g_strdup_printf ("%s", id); + g_free (token); + last_token_par = TRUE; + } else if (!strncmp (token, "Number", 6)) { + if (strlen (token) > 6) { + format = token + 6; /* format tag */ + } + tokens[i] = g_strdup_printf (format, number); + g_free (token); + last_token_par = TRUE; + } else if (!strncmp (token, "Bandwidth", 9)) { + if (strlen (token) > 9) { + format = token + 9; /* format tag */ + } + tokens[i] = g_strdup_printf (format, bandwidth); + g_free (token); + last_token_par = TRUE; + } else if (!strncmp (token, "Time", 4)) { + if (strlen (token) > 4) { + format = token + 4; /* format tag */ + } + tokens[i] = g_strdup_printf (format, time); + g_free (token); + last_token_par = TRUE; + } else if (!g_strcmp0 (token, "")) { + if (!last_token_par) { + tokens[i] = g_strdup_printf ("%s", "$"); + g_free (token); + last_token_par = TRUE; + } + } else { + last_token_par = FALSE; + } + } + + ret = g_strjoinv (NULL, tokens); + g_strfreev (tokens); + + return ret; +} + +static GstStreamPeriod * +gst_mpdparser_get_stream_period (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->periods != NULL, NULL); + + return g_list_nth_data (client->periods, client->period_idx); +} + +/* select a stream and extract the baseURL (if present) */ +static gchar * +gst_mpdparser_parse_baseURL (GstMpdClient * client) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + GstBaseURL *baseURL; + GList *list; + static gchar *baseURL_array[5]; + static gchar empty[] = ""; + gchar *ret = NULL; + + stream = + gst_mpdparser_get_active_stream_by_index (client, client->stream_idx); + g_return_val_if_fail (stream != NULL, empty); + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, empty); + g_return_val_if_fail (stream_period->period != NULL, empty); + + baseURL_array[0] = baseURL_array[1] = baseURL_array[2] = baseURL_array[3] = + empty; + baseURL_array[4] = NULL; + + /* FIXME: this simple implementation is not fully compliant with RFC 3986 */ + if ((list = client->mpd_node->BaseURLs) != NULL) { + baseURL = g_list_nth_data (list, stream->mpd_baseURL_idx); + if (!baseURL) { + baseURL = list->data; + } + baseURL_array[0] = baseURL->baseURL; + } + if ((list = stream_period->period->BaseURLs) != NULL) { + baseURL = g_list_nth_data (list, stream->period_baseURL_idx); + if (!baseURL) { + baseURL = list->data; + } + baseURL_array[1] = baseURL->baseURL; + } + if ((list = stream->cur_adapt_set->BaseURLs) != NULL) { + baseURL = g_list_nth_data (list, stream->adaptset_baseURL_idx); + if (!baseURL) { + baseURL = list->data; + } + baseURL_array[2] = baseURL->baseURL; + } + if ((list = stream->cur_representation->BaseURLs) != NULL) { + baseURL = g_list_nth_data (list, stream->repr_baseURL_idx); + if (!baseURL) { + baseURL = list->data; + } + baseURL_array[3] = baseURL->baseURL; + } + + ret = g_strjoinv (NULL, baseURL_array); + /* get base URI from MPD file URI, if the "http" scheme is missing */ + if (client->mpd_uri != NULL && strncmp (ret, "http://", 7) != 0) { + gchar *last_sep, *tmp1, *tmp2; + last_sep = strrchr (client->mpd_uri, '/'); + if (last_sep) { + tmp1 = g_strndup (client->mpd_uri, last_sep - client->mpd_uri + 1); + tmp2 = ret; + GST_DEBUG ("Got base URI from MPD file URI %s", tmp1); + ret = g_strconcat (tmp1, tmp2, NULL); + g_free (tmp1); + g_free (tmp2); + } + } + + return ret; +} + +static GstClockTime +gst_mpd_client_get_segment_duration (GstMpdClient * client, GstActiveStream *stream) +{ + GstStreamPeriod *stream_period; + GstMultSegmentBaseType *base = NULL; + GstClockTime duration; + guint timescale; + + g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE); + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, GST_CLOCK_TIME_NONE); + + if (stream->cur_segment_list) { + base = stream->cur_segment_list->MultSegBaseType; + } else if (stream->cur_seg_template) { + base = stream->cur_seg_template->MultSegBaseType; + } + + if (base == NULL || base->SegBaseType == NULL) { + /* this may happen when we have a single segment */ + duration = stream_period->duration; + } else { + duration = base->duration * GST_SECOND; + timescale = base->SegBaseType->timescale; + + if (timescale > 1) + duration /= timescale; + } + + return duration; +} + +/*****************************/ +/******* API functions *******/ +/*****************************/ + +GstMpdClient * +gst_mpd_client_new () +{ + GstMpdClient *client; + + client = g_new0 (GstMpdClient, 1); + client->lock = g_mutex_new (); + + client->download_failed_count = 0; + + return client; +} + +void +gst_active_streams_free (GstMpdClient * client) +{ + if (client->active_streams) { + g_list_foreach (client->active_streams, + (GFunc) gst_mpdparser_free_active_stream, NULL); + g_list_free (client->active_streams); + client->active_streams = NULL; + } +} + +void +gst_mpd_client_free (GstMpdClient * client) +{ + g_return_if_fail (client != NULL); + + if (client->mpd_node) + gst_mpdparser_free_mpd_node (client->mpd_node); + + if (client->periods) { + g_list_foreach (client->periods, + (GFunc) gst_mpdparser_free_stream_period, NULL); + g_list_free (client->periods); + } + + gst_active_streams_free (client); + + if (client->lock) + g_mutex_free (client->lock); + + g_free (client->mpd_uri); + + g_free (client); +} + +gboolean +gst_mpd_parse (GstMpdClient * client, const gchar * data, gint size) +{ + if (data) { + xmlDocPtr doc; + xmlNode *root_element = NULL; + + GST_DEBUG ("MPD file fully buffered, start parsing..."); + + GST_MPD_CLIENT_LOCK (client); + /* parse the complete MPD file into a tree (using the libxml2 default parser API) */ + + /* this initialize the library and check potential ABI mismatches + * between the version it was compiled for and the actual shared + * library used + */ + LIBXML_TEST_VERSION + /* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */ + doc = xmlReadMemory (data, size, "noname.xml", NULL, 0); + if (doc == NULL) { + GST_ERROR ("failed to parse the MPD file"); + GST_MPD_CLIENT_UNLOCK (client); + return FALSE; + } else { + /* get the root element node */ + root_element = xmlDocGetRootElement (doc); + + if (root_element->type != XML_ELEMENT_NODE + || xmlStrcmp (root_element->name, (xmlChar *) "MPD") != 0) { + GST_ERROR + ("can not find the root element MPD, failed to parse the MPD file"); + } else { + /* now we can parse the MPD root node and all children nodes, recursively */ + gst_mpdparser_parse_root_node (&client->mpd_node, root_element); + } + /* free the document */ + xmlFreeDoc (doc); + /* dump XML library memory for debugging */ + xmlMemoryDump (); + } + GST_MPD_CLIENT_UNLOCK (client); + + return TRUE; + } + + return FALSE; +} + +const gchar * +gst_mpdparser_get_baseURL (GstMpdClient * client) +{ + GstActiveStream *stream; + + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->active_streams != NULL, NULL); + stream = g_list_nth_data (client->active_streams, client->stream_idx); + g_return_val_if_fail (stream != NULL, NULL); + + return stream->baseURL; +} + +gboolean +gst_mpdparser_get_chunk_by_index (GstMpdClient * client, guint indexStream, + guint indexChunk, GstMediaSegment *segment) +{ + GstActiveStream *stream; + + /* select stream */ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (segment != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + stream = g_list_nth_data (client->active_streams, indexStream); + g_return_val_if_fail (stream != NULL, FALSE); + + if(stream->segments) { + GstMediaSegment *list_segment; + if ( indexChunk >= g_list_length (stream->segments)) + return FALSE; + list_segment = g_list_nth_data (stream->segments, indexChunk); + memcpy (segment, list_segment, sizeof (GstMediaSegment)); + } else { + GstClockTime duration; + GstStreamPeriod *stream_period; + GstClockTime start_time; + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType->SegmentTimeline == NULL, FALSE); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + duration = gst_mpd_client_get_segment_duration (client, stream); + if (!GST_CLOCK_TIME_IS_VALID (duration)) + return FALSE; + + start_time = duration * indexChunk; + if (GST_CLOCK_TIME_IS_VALID (stream_period->start) && GST_CLOCK_TIME_IS_VALID (stream_period->duration) + && start_time >= stream_period->start + stream_period->duration) + return FALSE; + + segment->number = indexChunk + stream->cur_seg_template->MultSegBaseType->startNumber; + segment->start_time = start_time; + segment->duration = duration; + segment->SegmentURL = NULL; + } + + return TRUE; +} + +static gboolean +gst_mpd_client_add_media_segment (GstActiveStream * stream, + GstSegmentURLNode * url_node, guint number, guint start, + GstClockTime start_time, GstClockTime duration) +{ + GstMediaSegment *media_segment; + + media_segment = g_slice_new0 (GstMediaSegment); + if (media_segment == NULL) { + GST_WARNING ("Allocation of GstMediaSegment struct failed!"); + return FALSE; + } + stream->segments = g_list_append (stream->segments, media_segment); + media_segment->SegmentURL = url_node; + media_segment->number = number; + media_segment->start = start; + media_segment->start_time = start_time; + media_segment->duration = duration; + + return TRUE; +} + +gboolean +gst_mpd_client_setup_representation (GstMpdClient * client, + GstActiveStream * stream, GstRepresentationNode * representation) +{ + GstStreamPeriod *stream_period; + GList *rep_list; + GstClockTime PeriodStart, PeriodEnd, start_time, duration; + GstMediaSegment *last_media_segment; + guint i, start; + + if (stream->cur_adapt_set == NULL) { + GST_WARNING ("No valid AdaptationSet node in the MPD file, aborting..."); + return FALSE; + } + + rep_list = stream->cur_adapt_set->Representations; + stream->cur_representation = representation; + stream->representation_idx = g_list_index (rep_list, representation); + + /* clean the old segment list, if any */ + if (stream->segments) { + g_list_foreach (stream->segments, + (GFunc) gst_mpdparser_free_media_segment, NULL); + g_list_free (stream->segments); + stream->segments = NULL; + } + + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + PeriodStart = stream_period->start; + if (GST_CLOCK_TIME_IS_VALID (stream_period->duration)) + PeriodEnd = stream_period->start + stream_period->duration; + else + PeriodEnd = GST_CLOCK_TIME_NONE; + + GST_LOG ("Building segment list for Period from %" GST_TIME_FORMAT " to %" + GST_TIME_FORMAT, GST_TIME_ARGS (PeriodStart), GST_TIME_ARGS (PeriodEnd)); + + if (representation->SegmentBase != NULL + || representation->SegmentList != NULL) { + GList *SegmentURL; + + /* get the first segment_base of the selected representation */ + if ((stream->cur_segment_base = + gst_mpdparser_get_segment_base (stream_period->period, + stream->cur_adapt_set, representation)) == NULL) { + GST_DEBUG ("No useful SegmentBase node for the current Representation"); + } + + /* get the first segment_list of the selected representation */ + if ((stream->cur_segment_list = + gst_mpdparser_get_segment_list (stream_period->period, + stream->cur_adapt_set, representation)) == NULL) { + GST_DEBUG ("No useful SegmentList node for the current Representation"); + /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ + if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, PeriodStart, + PeriodEnd)) { + return FALSE; + } + } else { + /* build the list of GstMediaSegment nodes from the SegmentList node */ + SegmentURL = stream->cur_segment_list->SegmentURL; + if (SegmentURL == NULL) { + GST_WARNING + ("No valid list of SegmentURL nodes in the MPD file, aborting..."); + return FALSE; + } + + /* build segment list */ + i = stream->cur_segment_list->MultSegBaseType->startNumber; + start = 0; + start_time = PeriodStart; + + GST_LOG ("Building media segment list using a SegmentList node"); + if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) { + GstSegmentTimelineNode *timeline; + GstSNode *S; + GList *list; + + timeline = stream->cur_segment_list->MultSegBaseType->SegmentTimeline; + for (list = g_list_first (timeline->S); list; list = g_list_next (list)) { + guint j, timescale; + + S = (GstSNode *) list->data; + GST_LOG ("Processing S node: d=%d r=%d t=%d", S->d, S->r, S->t); + duration = S->d * GST_SECOND; + timescale = + stream->cur_segment_list->MultSegBaseType->SegBaseType->timescale; + if (timescale > 1) + duration /= timescale; + if (S->t > 0) { + start = S->t; + start_time = S->t * GST_SECOND; + if (timescale > 1) + start_time /= timescale; + } + + for (j = 0; j <= S->r && SegmentURL != NULL; j++) { + if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, + start, start_time, duration)) { + return FALSE; + } + i++; + start += S->d; + start_time += duration; + SegmentURL = g_list_next (SegmentURL); + } + } + } else { + duration =gst_mpd_client_get_segment_duration (client, stream); + if (!GST_CLOCK_TIME_IS_VALID (duration)) + return FALSE; + + while (SegmentURL) { + if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, 0, + start_time, duration)) { + return FALSE; + } + i++; + start_time += duration; + SegmentURL = g_list_next (SegmentURL); + } + } + } + } else { + if (representation->SegmentTemplate != NULL) { + stream->cur_seg_template = representation->SegmentTemplate; + } else if (stream->cur_adapt_set->SegmentTemplate != NULL) { + stream->cur_seg_template = stream->cur_adapt_set->SegmentTemplate; + } else if (stream_period->period->SegmentTemplate != NULL) { + stream->cur_seg_template = stream_period->period->SegmentTemplate; + } + + if (stream->cur_seg_template == NULL + || stream->cur_seg_template->MultSegBaseType == NULL) { + /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ + if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0, PeriodEnd)) { + return FALSE; + } + } else { + /* build segment list */ + i = stream->cur_seg_template->MultSegBaseType->startNumber; + start = 0; + start_time = PeriodStart; + + GST_LOG ("Building media segment list using this template: %s", + stream->cur_seg_template->media); + if (stream->cur_seg_template->MultSegBaseType->SegmentTimeline) { + GstSegmentTimelineNode *timeline; + GstSNode *S; + GList *list; + + timeline = stream->cur_seg_template->MultSegBaseType->SegmentTimeline; + for (list = g_list_first (timeline->S); list; list = g_list_next (list)) { + guint j, timescale; + + S = (GstSNode *) list->data; + GST_LOG ("Processing S node: d=%d r=%d t=%d", S->d, S->r, S->t); + duration = S->d * GST_SECOND; + timescale = + stream->cur_seg_template->MultSegBaseType->SegBaseType->timescale; + if (timescale > 1) + duration /= timescale; + if (S->t > 0) { + start = S->t; + start_time = S->t * GST_SECOND; + if (timescale > 1) + start_time /= timescale; + } + + for (j = 0; j <= S->r; j++) { + if (!gst_mpd_client_add_media_segment (stream, NULL, i, start, + start_time, duration)) { + return FALSE; + } + i++; + start += S->d; + start_time += duration; + } + } + } else { + /*The segment is created on demand with the template, no need to build a list */ + } + } + } + + /* check duration of last segment */ + if(stream->segments) + last_media_segment = g_list_last (stream->segments)->data; + else + last_media_segment = NULL; + if (last_media_segment && GST_CLOCK_TIME_IS_VALID (PeriodEnd)) { + if (last_media_segment->start_time + last_media_segment->duration > + PeriodEnd) { + last_media_segment->duration = PeriodEnd - last_media_segment->start_time; + GST_LOG ("Fixed duration of last segment: %" GST_TIME_FORMAT, + GST_TIME_ARGS (last_media_segment->duration)); + } + GST_LOG ("Built a list of %d segments", last_media_segment->number); + } + + g_free (stream->baseURL); + stream->baseURL = gst_mpdparser_parse_baseURL (client); + + return TRUE; +} + +gboolean +gst_mpd_client_setup_media_presentation (GstMpdClient * client) +{ + GstStreamPeriod *stream_period; + GstPeriodNode *period_node; + GstClockTime start, duration; + GList *list, *next; + guint idx; + gboolean ret = FALSE; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->mpd_node != NULL, FALSE); + + GST_DEBUG ("Building the list of Periods in the Media Presentation"); + GST_MPD_CLIENT_LOCK (client); + /* clean the old period list, if any */ + if (client->periods) { + g_list_foreach (client->periods, + (GFunc) gst_mpdparser_free_stream_period, NULL); + g_list_free (client->periods); + client->periods = NULL; + } + + idx = 0; + start = 0; + duration = GST_CLOCK_TIME_NONE; + for (list = g_list_first (client->mpd_node->Periods); list; + list = g_list_next (list)) { + period_node = (GstPeriodNode *) list->data; + if (period_node->start != -1) { + /* we have a regular period */ + start = period_node->start * GST_MSECOND; + } else if (duration != GST_CLOCK_TIME_NONE) { + /* start time inferred from previous period, this is still a regular period */ + start += duration; + } else if (idx == 0 && client->mpd_node->type == GST_MPD_FILE_TYPE_STATIC) { + /* first period of a static MPD file, start time is 0 */ + start = 0; + } else if (gst_mpd_client_is_live (client)) { + /*it should be a live stream, let this pass.*/ + } else { + /* this is an 'Early Available Period' */ + goto early; + } + + if (period_node->duration != -1) { + duration = period_node->duration * GST_MSECOND; + } else if ((next = g_list_next (list)) != NULL) { + /* try to infer this period duration from the start time of the next period */ + GstPeriodNode *next_period_node = next->data; + if (next_period_node->start != -1) { + duration = next_period_node->start * GST_MSECOND - start; + } else { + /* Invalid MPD file! */ + goto syntax_error; + } + } else if (client->mpd_node->mediaPresentationDuration != -1) { + /* last Period of the Media Presentation */ + duration = + client->mpd_node->mediaPresentationDuration * GST_MSECOND - start; + } else if (gst_mpd_client_is_live (client)) { + /*it should be a live stream, let this pass.*/ + } else { + /* Invalid MPD file! */ + goto syntax_error; + } + + stream_period = g_slice_new0 (GstStreamPeriod); + if (stream_period == NULL) { + goto no_mem; + } + client->periods = g_list_append (client->periods, stream_period); + stream_period->period = period_node; + stream_period->number = idx++; + stream_period->start = start; + stream_period->duration = duration; + ret = TRUE; + GST_LOG (" - added Period %d start=%" GST_TIME_FORMAT " duration=%" + GST_TIME_FORMAT, idx, GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); + } + + GST_MPD_CLIENT_UNLOCK (client); + GST_DEBUG ("Found a total of %d valid Periods in the Media Presentation", + idx); + return ret; + +early: + GST_MPD_CLIENT_UNLOCK (client); + GST_WARNING + ("Found an Early Available Period, skipping the rest of the Media Presentation"); + return ret; + +syntax_error: + GST_MPD_CLIENT_UNLOCK (client); + GST_WARNING + ("Cannot get the duration of the Period %d, skipping the rest of the Media Presentation", + idx); + return ret; + +no_mem: + GST_MPD_CLIENT_UNLOCK (client); + GST_WARNING ("Allocation of GstStreamPeriod struct failed!"); + return FALSE; +} + +gboolean +gst_mpd_client_setup_streaming (GstMpdClient * client, + GstStreamMimeType mimeType, gchar * lang) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + GstAdaptationSetNode *adapt_set; + GstRepresentationNode *representation; + GList *rep_list = NULL; + + stream_period = gst_mpdparser_get_stream_period (client); + if (stream_period == NULL || stream_period->period == NULL) { + GST_DEBUG ("No more Period nodes in the MPD file, terminating..."); + return FALSE; + } + + switch (mimeType) { + case GST_STREAM_VIDEO: + /* select the adaptation set for the video pipeline */ + adapt_set = + gst_mpdparser_get_adapt_set_with_mimeType_and_idx (stream_period-> + period->AdaptationSets, "video", 0); + if (!adapt_set) { + GST_INFO ("No video adaptation set found"); + return FALSE; + } + /* retrive the list of representations */ + rep_list = adapt_set->Representations; + if (!rep_list) { + GST_WARNING ("Can not retrieve any representation, aborting..."); + return FALSE; + } + break; + case GST_STREAM_AUDIO: + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang + (stream_period->period->AdaptationSets, "audio", lang); + /* if we did not found the requested audio language, get the first one */ + if (!adapt_set) + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType (stream_period-> + period->AdaptationSets, "audio"); + if (!adapt_set) { + GST_INFO ("No audio adaptation set found"); + return FALSE; + } + rep_list = adapt_set->Representations; + if (!rep_list) { + GST_WARNING ("Can not retrieve any representation, aborting..."); + return FALSE; + } + break; + case GST_STREAM_APPLICATION: + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang + (stream_period->period->AdaptationSets, "application", lang); + /* if we did not found the requested subtitles language, get the first one */ + if (!adapt_set) + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType (stream_period-> + period->AdaptationSets, "application"); + if (!adapt_set) { + GST_INFO ("No application adaptation set found"); + return FALSE; + } + rep_list = adapt_set->Representations; + if (!rep_list) { + GST_WARNING ("Can not retrieve any representation, aborting..."); + return FALSE; + } + break; + default: + GST_WARNING ("Unsupported mimeType %d", mimeType); + return FALSE; + } + + stream = g_slice_new0 (GstActiveStream); + if (stream == NULL) { + GST_WARNING ("Allocation of active stream struct failed!"); + return FALSE; + } + client->active_streams = g_list_append (client->active_streams, stream); + + stream->mpd_baseURL_idx = 0; + stream->period_baseURL_idx = 0; + stream->adaptset_baseURL_idx = 0; + stream->repr_baseURL_idx = 0; + + stream->mimeType = mimeType; + stream->cur_adapt_set = adapt_set; + + /* retrive representation list */ + if (stream->cur_adapt_set != NULL) + rep_list = stream->cur_adapt_set->Representations; +#if 0 + /* fast start */ + representation = + gst_mpdparser_get_representation_with_max_bandwidth (rep_list, + stream->max_bandwidth); + + if (!representation) { + GST_WARNING + ("Can not retrieve a representation with the requested bandwidth"); + representation = gst_mpdparser_get_lowest_representation (rep_list); + } +#else + /* slow start */ + representation = gst_mpdparser_get_lowest_representation (rep_list); +#endif + + if (!representation) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + return FALSE; + } + + if (!gst_mpd_client_setup_representation (client, stream, representation)) + return FALSE; + + GST_INFO ("Successfully setup the download pipeline for mimeType %d", + mimeType); + + return TRUE; +} + +gboolean +gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, + guint stream_idx, GstClockTime * ts) +{ + GstActiveStream *stream; + gint segment_idx; + GstMediaSegment currentChunk; + + GST_DEBUG ("Stream index: %i", stream_idx); + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, 0); + + GST_MPD_CLIENT_LOCK (client); + segment_idx = gst_mpd_client_get_segment_index (stream); + GST_DEBUG ("Looking for fragment sequence chunk %d", segment_idx); + + + if (!gst_mpdparser_get_chunk_by_index (client, stream_idx, segment_idx, ¤tChunk)) { + GST_MPD_CLIENT_UNLOCK (client); + return FALSE; + } + + *ts = currentChunk.start_time; + GST_MPD_CLIENT_UNLOCK (client); + + return TRUE; +} + +gboolean +gst_mpd_client_get_last_fragment_timestamp (GstMpdClient * client, + guint stream_idx, GstClockTime * ts) +{ + GstActiveStream *stream; + gint segment_idx = 0; + GstMediaSegment currentChunk; + + GST_DEBUG ("Stream index: %i", stream_idx); + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, 0); + + GST_MPD_CLIENT_LOCK (client); + if (stream->segments) + segment_idx = g_list_length (stream->segments) - 1; + GST_DEBUG ("Looking for fragment sequence chunk %d", segment_idx); + + if (!gst_mpdparser_get_chunk_by_index (client, stream_idx, segment_idx, + ¤tChunk)) { + GST_MPD_CLIENT_UNLOCK (client); + return FALSE; + } + + *ts = currentChunk.start_time; + GST_MPD_CLIENT_UNLOCK (client); + + return TRUE; +} + +gboolean +gst_mpd_client_get_next_fragment (GstMpdClient * client, + guint indexStream, gboolean * discontinuity, gchar ** uri, + GstClockTime * duration, GstClockTime * timestamp) +{ + GstActiveStream *stream = NULL; + GstMediaSegment currentChunk; + gchar *mediaURL = NULL; + guint segment_idx; + + /* select stream */ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + stream = g_list_nth_data (client->active_streams, indexStream); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != NULL, FALSE); + g_return_val_if_fail (discontinuity != NULL, FALSE); + + GST_MPD_CLIENT_LOCK (client); + segment_idx = gst_mpd_client_get_segment_index (stream); + GST_DEBUG ("Looking for fragment sequence chunk %d", segment_idx); + + if (!gst_mpdparser_get_chunk_by_index (client, indexStream, segment_idx, ¤tChunk)) { + GST_MPD_CLIENT_UNLOCK (client); + return FALSE; + } + + if (currentChunk.SegmentURL != NULL) { + mediaURL = gst_mpdparser_get_mediaURL (client, currentChunk.SegmentURL); + } else if (stream->cur_seg_template != NULL) { + mediaURL = + gst_mpdparser_build_URL_from_template (stream->cur_seg_template->media, + stream->cur_representation->id, currentChunk.number, + stream->cur_representation->bandwidth, currentChunk.start); + } + + *timestamp = currentChunk.start_time; + *duration = currentChunk.duration; + *discontinuity = segment_idx != currentChunk.number; + if (mediaURL == NULL) { + /* single segment with URL encoded in the baseURL syntax element */ + *uri = g_strdup (gst_mpdparser_get_baseURL (client)); + } else if (strncmp (mediaURL, "http://", 7) != 0) { + *uri = g_strconcat (gst_mpdparser_get_baseURL (client), mediaURL, NULL); + g_free (mediaURL); + } else { + *uri = mediaURL; + } + gst_mpd_client_set_segment_index (stream, segment_idx + 1); + GST_MPD_CLIENT_UNLOCK (client); + + GST_DEBUG ("Loading chunk with URL %s", *uri); + + return TRUE; +} + +gboolean +gst_mpd_client_get_next_header (GstMpdClient * client, const gchar ** uri, + guint stream_idx) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + + stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != NULL, FALSE); + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + GST_DEBUG ("Looking for current representation header"); + GST_MPD_CLIENT_LOCK (client); + *uri = NULL; + if (stream->cur_segment_base && stream->cur_segment_base->Initialization) { + + if (stream->cur_segment_base->Initialization->sourceURL == NULL) { + stream->cur_segment_base->Initialization->sourceURL = gst_mpdparser_get_baseURL (client); + }//if not present initialization @sourceURL attribute, it's mapped to baseURL + + *uri = + gst_mpdparser_get_initializationURL (stream->cur_segment_base-> + Initialization); + } else if (stream->cur_seg_template) { + const gchar *initialization = NULL; + if (stream->cur_seg_template->initialization) { + initialization = stream->cur_seg_template->initialization; + } else if (stream->cur_adapt_set->SegmentTemplate + && stream->cur_adapt_set->SegmentTemplate->initialization) { + initialization = stream->cur_adapt_set->SegmentTemplate->initialization; + } else if (stream_period->period->SegmentTemplate + && stream_period->period->SegmentTemplate->initialization) { + initialization = stream_period->period->SegmentTemplate->initialization; + } + *uri = gst_mpdparser_build_URL_from_template (initialization, + stream->cur_representation->id, 0, + stream->cur_representation->bandwidth, 0); + } + GST_MPD_CLIENT_UNLOCK (client); + + return *uri == NULL ? FALSE : TRUE; +} + +gboolean +gst_mpd_client_get_stream_current_position (GstMpdClient * client, guint stream_idx, GstClockTime *ts) +{ + GstActiveStream *stream; + GstMediaSegment media_segment; + stream = gst_mpdparser_get_active_stream_by_index( client, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + if(!gst_mpdparser_get_chunk_by_index(client, stream_idx, + gst_mpd_client_get_segment_index (stream), &media_segment)) + { + return FALSE; + } + + if (GST_CLOCK_TIME_IS_VALID (media_segment.start_time)) { + *ts = media_segment.start_time; + return TRUE; + } else { + return FALSE; + } +} + +GstClockTime +gst_mpd_client_get_next_fragment_duration (GstMpdClient * client) +{ + GstActiveStream *stream; + GstMediaSegment *media_segment; + gint seg_idx; + + stream = g_list_nth_data (client->active_streams, client->stream_idx); + g_return_val_if_fail (stream != NULL, 0); + seg_idx = gst_mpd_client_get_segment_index (stream); + + if(stream->segments) { + media_segment = + g_list_nth_data (stream->segments, seg_idx); + return media_segment == NULL ? 0 : media_segment->duration; + } else { + GstClockTime duration = + gst_mpd_client_get_segment_duration (client, stream); + g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType-> + SegmentTimeline == NULL, 0); + + if (GST_CLOCK_TIME_IS_VALID (duration)) + return duration; + return 0; + } +} + +GstClockTime +gst_mpd_client_get_media_presentation_duration (GstMpdClient * client) +{ + GstClockTime duration; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + GST_MPD_CLIENT_LOCK (client); + if (client->mpd_node->mediaPresentationDuration != -1) { + duration = client->mpd_node->mediaPresentationDuration * GST_MSECOND; + } else { + /* We can only get the duration for on-demand streams */ + duration = GST_CLOCK_TIME_NONE; + } + GST_MPD_CLIENT_UNLOCK (client); + + return duration; +} + +gboolean +gst_mpd_client_set_period_index (GstMpdClient * client, guint period_idx) +{ + GstStreamPeriod *next_stream_period; + gboolean ret = FALSE; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + + GST_MPD_CLIENT_LOCK (client); + next_stream_period = g_list_nth_data (client->periods, period_idx); + if (next_stream_period != NULL) { + client->period_idx = period_idx; + ret = TRUE; + } + GST_MPD_CLIENT_UNLOCK (client); + + return ret; +} + +guint +gst_mpd_client_get_period_index (GstMpdClient * client) +{ + guint period_idx; + + g_return_val_if_fail (client != NULL, 0); + GST_MPD_CLIENT_LOCK (client); + period_idx = client->period_idx; + GST_MPD_CLIENT_UNLOCK (client); + + return period_idx; +} + +gboolean +gst_mpd_client_set_period_id (GstMpdClient * client, const gchar * period_id) +{ + GstStreamPeriod *next_stream_period; + gboolean ret = FALSE; + GList *iter; + gint index = 0; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + g_return_val_if_fail (period_id != NULL, FALSE); + + GST_MPD_CLIENT_LOCK (client); + for (iter = client->periods; iter; iter = g_list_next (iter)) { + next_stream_period = iter->data; + + if (next_stream_period->period->id + && strcmp (next_stream_period->period->id, period_id) == 0) { + client->period_idx = index; + ret = TRUE; + break; + } + index++; + } + GST_MPD_CLIENT_UNLOCK (client); + + return ret; +} + +const gchar * +gst_mpd_client_get_period_id (GstMpdClient * client) +{ + GstStreamPeriod *period; + gchar *period_id = NULL; + + g_return_val_if_fail (client != NULL, 0); + GST_MPD_CLIENT_LOCK (client); + period = g_list_nth_data (client->periods, client->period_idx); + if (period && period->period) + period_id = period->period->id; + GST_MPD_CLIENT_UNLOCK (client); + + return period_id; +} + +gboolean +gst_mpd_client_has_next_period (GstMpdClient * client) +{ + GList *next_stream_period; + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + + GST_MPD_CLIENT_LOCK (client); + next_stream_period = + g_list_nth_data (client->periods, client->period_idx + 1); + GST_MPD_CLIENT_UNLOCK (client); + + return next_stream_period != NULL; +} + +void +gst_mpd_client_set_segment_index_for_all_streams (GstMpdClient * client, + guint segment_idx) +{ + GList *list; + + g_return_if_fail (client != NULL); + g_return_if_fail (client->active_streams != NULL); + + /* FIXME: support multiple streams with different segment duration */ + for (list = g_list_first (client->active_streams); list; + list = g_list_next (list)) { + GstActiveStream *stream = (GstActiveStream *) list->data; + if (stream) { + stream->segment_idx = segment_idx; + } + } +} + +gboolean +gst_mpd_client_set_next_baseURL_for_stream (GstMpdClient * client) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + GList *list; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + + stream = g_list_nth_data (client->active_streams, + client->stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + + if ((list = stream->cur_representation->BaseURLs) != NULL && + stream->repr_baseURL_idx < g_list_length (list) - 1) { + stream->repr_baseURL_idx++; + } else if ((list = stream->cur_adapt_set->BaseURLs) != NULL && + stream->adaptset_baseURL_idx < g_list_length (list) - 1) { + stream->adaptset_baseURL_idx++; + stream->repr_baseURL_idx = 0; + } else if ((list = stream_period->period->BaseURLs) != NULL && + stream->period_baseURL_idx < g_list_length (list) - 1) { + stream->period_baseURL_idx++; + stream->adaptset_baseURL_idx = 0; + stream->repr_baseURL_idx = 0; + } else if ((list = client->mpd_node->BaseURLs) != NULL && + stream->mpd_baseURL_idx < g_list_length (list) - 1) { + stream->mpd_baseURL_idx++; + stream->period_baseURL_idx = 0; + stream->adaptset_baseURL_idx = 0; + stream->repr_baseURL_idx = 0; + } else { + GST_WARNING ("Unable to change URI: No alternative baseURL available"); + return FALSE; + } + + stream->baseURL = gst_mpdparser_parse_baseURL (client); + + return TRUE; +} + +void +gst_mpd_client_set_segment_index (GstActiveStream * stream, guint segment_idx) +{ + g_return_if_fail (stream != NULL); + + stream->segment_idx = segment_idx; +} + +guint +gst_mpd_client_get_segment_index (GstActiveStream * stream) +{ + g_return_val_if_fail (stream != NULL, 0); + + return stream->segment_idx; +} + +gboolean +gst_mpd_client_is_live (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->mpd_node != NULL, FALSE); + + return client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC; +} + +guint +gst_mpdparser_get_nb_active_stream (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, 0); + + return g_list_length (client->active_streams); +} + +guint +gst_mpdparser_get_nb_adaptationSet (GstMpdClient * client) +{ + GstStreamPeriod *stream_period; + + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, 0); + g_return_val_if_fail (stream_period->period != NULL, 0); + + return g_list_length (stream_period->period->AdaptationSets); +} + +GstActiveStream * +gst_mpdparser_get_active_stream_by_index (GstMpdClient * client, + guint stream_idx) +{ + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->active_streams != NULL, NULL); + + return g_list_nth_data (client->active_streams, stream_idx); +} + +gint +gst_mpd_client_get_video_active_stream_id(GstMpdClient *client) { + guint idx; + g_return_val_if_fail (client != NULL, -1); + g_return_val_if_fail (client->active_streams != NULL, -1); + for (idx = 0; idx < gst_mpdparser_get_nb_active_stream(client); idx++) { + GstActiveStream *stream = gst_mpdparser_get_active_stream_by_index(client, idx); + if( strncmp_ext (gst_mpd_client_get_stream_mimeType (stream), "video") == 0 ) + return idx; + } + return -1; +} + +gboolean +gst_mpd_client_stream_seek (GstMpdClient * client, gint stream_idx, GstClockTime ts) +{ + guint segment_idx = 0; + GstActiveStream *stream; + GST_MPD_CLIENT_LOCK (client); + GST_DEBUG ("mpd client seeking to %"GST_TIME_FORMAT" on stream %d", GST_TIME_ARGS(ts), stream_idx); + stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + + if ( GST_CLOCK_TIME_IS_VALID (gst_mpd_client_stream_find_segment (client, + stream_idx, ts, &segment_idx))) { + gst_mpd_client_set_segment_index(stream, segment_idx); + } + GST_MPD_CLIENT_UNLOCK (client); + return TRUE; +} + +static GDateTime * +_gst_date_time_to_g_date_time (GstDateTime * datetime) +{ + g_return_val_if_fail (datetime != NULL, NULL); + return g_date_time_new_utc ( gst_date_time_get_year (datetime), gst_date_time_get_month(datetime), + gst_date_time_get_day(datetime), gst_date_time_get_hour(datetime), + gst_date_time_get_minute(datetime),gst_date_time_get_second(datetime)); +} + +gboolean +gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time) +{ + GDateTime *start = + _gst_date_time_to_g_date_time (client->mpd_node->availabilityStartTime); + GTimeSpan ts_microseconds; + GstClockTime ts; + gboolean ret = TRUE; + gint i; + + g_return_val_if_fail (gst_mpd_client_is_live (client), 0); + + ts_microseconds = g_date_time_difference (time, start); + g_date_time_unref (start); + + ts = ts_microseconds * GST_USECOND; + for (i=0; i < g_list_length (client->active_streams); i++) { + ret = ret & gst_mpd_client_stream_seek (client, i, ts); + } + return ret; +} + +gint +gst_mpd_client_check_time_position (GstMpdClient * client, + GstActiveStream * stream, GstClockTime ts, gint64 * diff) +{ + GDateTime *now = g_date_time_new_now_utc (); + GDateTime *start = + _gst_date_time_to_g_date_time (client->mpd_node->availabilityStartTime); + GTimeSpan stream_now; + GTimeSpan ts_microseconds; + GstClockTime duration; + + g_return_val_if_fail (gst_mpd_client_is_live (client), 0); + + duration = gst_mpd_client_get_segment_duration (client, stream); + stream_now = g_date_time_difference (now, start); + g_date_time_unref (now); + g_date_time_unref (start); + + /* sum duration to check if the segment is fully ready */ + ts_microseconds = (ts + duration) / GST_USECOND; + + /* + * This functions checks if a given ts is in the 'available range' of + * a DASH presentation. This only makes sense for live streams, which + * are continuously adding new segments and removing old ones. + * + * Note: Both the client and the server should use UTC as a time reference. + * + * @ts is the time since the beginning of the stream and we need to find out + * if it is currently available. The server should be hosting segments + * + * * ---------------- ... --- * ----------- * ---- ... + * | + * | past(unavailable) | | available | future(unavailable yet) + * | + * * ---------------- ... --- * ----------- * ---- ... + * | | | + * availabilitStartTime | UTC now + * UTC now - timeShiftBufferDepth + * + * This function should return 0 if @ts is in the 'available' area, 1 for + * 'future' and '-1' for past and the corresponding distance to the + * 'available' area is set to @diff + * + * TODO untested with live presentations with multiple periods as no + * examples for it could be found/generated + */ + + if (ts_microseconds > stream_now) { + *diff = ts_microseconds - stream_now; + return 1; + } + if (GST_CLOCK_TIME_IS_VALID (client->mpd_node->timeShiftBufferDepth) + && ts_microseconds < + stream_now - client->mpd_node->timeShiftBufferDepth * GST_USECOND) { + *diff = ts_microseconds - stream_now; + return -1; + } + + *diff = 0; + return 0; +} + +/*Finds the segment with appropriate time*/ +GstClockTime +gst_mpd_client_stream_find_segment(GstMpdClient *client, guint stream_idx, + GstClockTime seek_pos, guint *seg_num) +{ + guint cur_segment_idx=0; + GstMediaSegment *chunk; + GList *list = NULL; + GstClockTime current_pos = 0; + GstActiveStream *stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx); + g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE); + if(stream->segments != NULL) { + for (list = g_list_first (stream->segments); list; + list = g_list_next (list)) { + chunk = list->data; + current_pos = chunk->start_time; + if (current_pos <= seek_pos + && seek_pos < current_pos + chunk->duration) { + break; + } + cur_segment_idx++; + } + if(list != NULL) { + *seg_num = cur_segment_idx; + return current_pos; + } else { + return GST_CLOCK_TIME_NONE; + } + } else { + GstClockTime duration; + duration = gst_mpd_client_get_segment_duration (client, stream); + if( !GST_CLOCK_TIME_IS_VALID (duration) ) + return FALSE; + *seg_num = seek_pos / duration; + return *seg_num * duration; + } +} + +static const gchar * +gst_mpdparser_mimetype_to_caps (const gchar * mimeType) +{ + if (mimeType == NULL) + return NULL; + if (strcmp (mimeType, "video/mp2t") == 0) { + return "video/mpegts"; + } else if (strcmp (mimeType, "video/mp4") == 0) { + return "video/quicktime"; + } else if (strcmp (mimeType, "audio/mp4") == 0) { + return "audio/x-m4a"; + } else + return mimeType; +} + +const gchar * +gst_mpd_client_get_stream_mimeType (GstActiveStream * stream) +{ + const gchar *mimeType; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return NULL; + + mimeType = stream->cur_representation->RepresentationBase->mimeType; + if (mimeType == NULL) { + mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; + } + + return gst_mpdparser_mimetype_to_caps (mimeType); +} + +const gboolean +gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream) +{ + if (stream == NULL || stream->cur_adapt_set == NULL) + return FALSE; + + return stream->cur_adapt_set->bitstreamSwitching; +} + +gboolean +gst_mpd_client_get_max_video_dimensions (GstMpdClient *client, gint *max_width, gint *max_height) +{ + GList *period_it, *repr_it; + GstStreamPeriod *period; + GstAdaptationSetNode *adaptset; + GstRepresentationNode *repr; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (max_height != NULL, FALSE); + g_return_val_if_fail (max_width != NULL, FALSE); + + *max_width = 0; + *max_height = 0; + + period_it = client->periods; + while(period_it) { + period = period_it->data; + adaptset = gst_mpdparser_get_adapt_set_with_mimeType_and_idx ( + period->period->AdaptationSets, "video", 0); + if(adaptset == NULL) + continue; + repr_it = adaptset->Representations; + + if(adaptset->RepresentationBase->width > *max_width) + *max_width = adaptset->RepresentationBase->width; + if(adaptset->RepresentationBase->height > *max_height) + *max_height = adaptset->RepresentationBase->height; + + while(repr_it) { + repr = repr_it->data; + if(repr->RepresentationBase->width > *max_width) + *max_width = repr->RepresentationBase->width; + if(repr->RepresentationBase->height > *max_height) + *max_height = repr->RepresentationBase->height; + repr_it = repr_it->next; + } + + period_it = period_it->next; + } + if(*max_width == 0 && *max_height == 0) + return FALSE; + else + return TRUE; +} + +guint +gst_mpd_client_get_video_stream_width (GstActiveStream * stream) +{ + guint width; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + width = stream->cur_representation->RepresentationBase->width; + if (width == 0) { + width = stream->cur_adapt_set->RepresentationBase->width; + } + + return width; +} + +guint +gst_mpd_client_get_video_stream_height (GstActiveStream * stream) +{ + guint height; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + height = stream->cur_representation->RepresentationBase->height; + if (height == 0) { + height = stream->cur_adapt_set->RepresentationBase->height; + } + + return height; +} + +guint +gst_mpd_client_get_video_stream_bandwidth (GstActiveStream * stream) +{ + guint bandwidth; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + bandwidth = stream->cur_representation->RepresentationBase->bandwidth; + if (bandwidth == 0) { + bandwidth = stream->cur_adapt_set->RepresentationBase->bandwidth; + } + + return bandwidth; +} + +guint +gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream) +{ + const gchar *rate; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + rate = stream->cur_representation->RepresentationBase->audioSamplingRate; + if (rate == NULL) { + rate = stream->cur_adapt_set->RepresentationBase->audioSamplingRate; + } + + return rate ? atoi (rate) : 0; +} + +guint +gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream) +{ + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + /* TODO: here we have to parse the AudioChannelConfiguration descriptors */ + return 0; +} + +guint +gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient * client, + GList ** lang) +{ + GstStreamPeriod *stream_period; + GstAdaptationSetNode *adapt_set; + GList *list; + gchar *this_mimeType = "audio"; + gchar *mimeType = NULL; + guint nb_adapatation_set = 0; + + stream_period = gst_mpdparser_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, 0); + g_return_val_if_fail (stream_period->period != NULL, 0); + + for (list = g_list_first (stream_period->period->AdaptationSets); list; + list = g_list_next (list)) { + adapt_set = (GstAdaptationSetNode *) list->data; + if (adapt_set) { + gchar *this_lang = adapt_set->lang; + GstRepresentationNode *rep; + rep = + gst_mpdparser_get_lowest_representation (adapt_set->Representations); + if (!rep) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + return 0; + } + if (rep->RepresentationBase) + mimeType = rep->RepresentationBase->mimeType; + if (!mimeType && adapt_set->RepresentationBase) { + mimeType = adapt_set->RepresentationBase->mimeType; + } + + if (strncmp_ext (mimeType, this_mimeType) == 0) { + if (this_lang) { + nb_adapatation_set++; + *lang = g_list_append (*lang, this_lang); + } + } + } + } + + return nb_adapatation_set; +} diff --git a/dashdemux/src/gstmpdparser.h b/dashdemux/src/gstmpdparser.h new file mode 100755 index 0000000..3231d62 --- /dev/null +++ b/dashdemux/src/gstmpdparser.h @@ -0,0 +1,532 @@ +/* + * DASH MPD parsing library + * + * gstmpdparser.h + * + * Copyright (C) 2012 STMicroelectronics + * + * Authors: + * Gianluca Gennari + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_MPDPARSER_H__ +#define __GST_MPDPARSER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstMpdClient GstMpdClient; +typedef struct _GstActiveStream GstActiveStream; +typedef struct _GstStreamPeriod GstStreamPeriod; +typedef struct _GstMediaSegment GstMediaSegment; +typedef struct _GstMPDNode GstMPDNode; +typedef struct _GstPeriodNode GstPeriodNode; +typedef struct _GstRepresentationBaseType GstRepresentationBaseType; +typedef struct _GstDescriptorType GstDescriptorType; +typedef struct _GstContentComponentNode GstContentComponentNode; +typedef struct _GstAdaptationSetNode GstAdaptationSetNode; +typedef struct _GstRepresentationNode GstRepresentationNode; +typedef struct _GstSubRepresentationNode GstSubRepresentationNode; +typedef struct _GstSegmentListNode GstSegmentListNode; +typedef struct _GstSegmentTemplateNode GstSegmentTemplateNode; +typedef struct _GstSegmentURLNode GstSegmentURLNode; +typedef struct _GstBaseURL GstBaseURL; +typedef struct _GstRange GstRange; +typedef struct _GstRatio GstRatio; +typedef struct _GstFrameRate GstFrameRate; +typedef struct _GstConditionalUintType GstConditionalUintType; +typedef struct _GstSubsetNode GstSubsetNode; +typedef struct _GstProgramInformationNode GstProgramInformationNode; +typedef struct _GstMetricsRangeNode GstMetricsRangeNode; +typedef struct _GstMetricsNode GstMetricsNode; +typedef struct _GstSNode GstSNode; +typedef struct _GstSegmentTimelineNode GstSegmentTimelineNode; +typedef struct _GstSegmentBaseType GstSegmentBaseType; +typedef struct _GstURLType GstURLType; +typedef struct _GstMultSegmentBaseType GstMultSegmentBaseType; + +#define GST_MPD_CLIENT_LOCK(c) g_mutex_lock (c->lock); +#define GST_MPD_CLIENT_UNLOCK(c) g_mutex_unlock (c->lock); + +typedef enum +{ + GST_STREAM_VIDEO, /* video stream (the main one) */ + GST_STREAM_AUDIO, /* audio stream (optional) */ + GST_STREAM_APPLICATION /* application stream (optional): for timed text/subtitles */ +} GstStreamMimeType; + +typedef enum +{ + GST_MPD_FILE_TYPE_STATIC, + GST_MPD_FILE_TYPE_DYNAMIC +} GstMPDFileType; + +typedef enum +{ + GST_SAP_TYPE_0 = 0, + GST_SAP_TYPE_1, + GST_SAP_TYPE_2, + GST_SAP_TYPE_3, + GST_SAP_TYPE_4, + GST_SAP_TYPE_5, + GST_SAP_TYPE_6 +} GstSAPType; + +struct _GstBaseURL +{ + gchar *baseURL; + gchar *serviceLocation; + gchar *byteRange; +}; + +struct _GstRange +{ + guint64 first_byte_pos; + guint64 last_byte_pos; +}; + +struct _GstRatio +{ + guint num; + guint den; +}; + +struct _GstFrameRate +{ + guint num; + guint den; +}; + +struct _GstConditionalUintType +{ + gboolean flag; + guint value; +}; + +struct _GstSNode +{ + guint t; + guint d; + guint r; +}; + +struct _GstSegmentTimelineNode +{ + /* list of S nodes */ + GList *S; +}; + +struct _GstURLType +{ + gchar *sourceURL; + GstRange *range; +}; + +struct _GstSegmentBaseType +{ + guint timescale; + guint presentationTimeOffset; + gchar *indexRange; + gboolean indexRangeExact; + /* Initialization node */ + GstURLType *Initialization; + /* RepresentationIndex node */ + GstURLType *RepresentationIndex; +}; + +struct _GstMultSegmentBaseType +{ + guint duration; /* in seconds */ + guint startNumber; + /* SegmentBaseType extension */ + GstSegmentBaseType *SegBaseType; + /* SegmentTimeline node */ + GstSegmentTimelineNode *SegmentTimeline; + /* BitstreamSwitching node */ + GstURLType *BitstreamSwitching; +}; + +struct _GstSegmentListNode +{ + /* extension */ + GstMultSegmentBaseType *MultSegBaseType; + /* list of SegmentURL nodes */ + GList *SegmentURL; +}; + +struct _GstSegmentTemplateNode +{ + /* extension */ + GstMultSegmentBaseType *MultSegBaseType; + gchar *media; + gchar *index; + gchar *initialization; + gchar *bitstreamSwitching; +}; + +struct _GstSegmentURLNode +{ + gchar *media; + GstRange *mediaRange; + gchar *index; + GstRange *indexRange; +}; + +struct _GstRepresentationBaseType +{ + gchar *profiles; + guint width; + guint height; + guint bandwidth; + GstRatio *sar; + GstFrameRate *frameRate; + gchar *audioSamplingRate; + gchar *mimeType; + gchar *segmentProfiles; + gchar *codecs; + gdouble maximumSAPPeriod; + GstSAPType startWithSAP; + gdouble maxPlayoutRate; + gboolean codingDependency; + gchar *scanType; + /* list of FramePacking DescriptorType nodes */ + GList *FramePacking; + /* list of AudioChannelConfiguration DescriptorType nodes */ + GList *AudioChannelConfiguration; + /* list of ContentProtection DescriptorType nodes */ + GList *ContentProtection; +}; + +struct _GstSubRepresentationNode +{ + /* RepresentationBase extension */ + GstRepresentationBaseType *RepresentationBase; + guint level; + guint *dependencyLevel; /* UIntVectorType */ + guint size; /* size of "dependencyLevel" array */ + guint64 bandwidth; + gchar **contentComponent; /* StringVectorType */ +}; + +struct _GstRepresentationNode +{ + gchar *id; + guint bandwidth; + guint qualityRanking; + gchar **dependencyId; /* StringVectorType */ + gchar **mediaStreamStructureId; /* StringVectorType */ + /* RepresentationBase extension */ + GstRepresentationBaseType *RepresentationBase; + /* list of BaseURL nodes */ + GList *BaseURLs; + /* list of SubRepresentation nodes */ + GList *SubRepresentations; + /* SegmentBase node */ + GstSegmentBaseType *SegmentBase; + /* SegmentTemplate node */ + GstSegmentTemplateNode *SegmentTemplate; + /* SegmentList node */ + GstSegmentListNode *SegmentList; +}; + +struct _GstDescriptorType +{ + gchar *schemeIdUri; + gchar *value; + /* For adding ContentProtection to caps , get string of */ + gchar *msprPro; +}; + +struct _GstContentComponentNode +{ + guint id; + gchar *lang; /* LangVectorType RFC 5646 */ + gchar *contentType; + GstRatio *par; + /* list of Accessibility DescriptorType nodes */ + GList *Accessibility; + /* list of Role DescriptorType nodes */ + GList *Role; + /* list of Rating DescriptorType nodes */ + GList *Rating; + /* list of Viewpoint DescriptorType nodes */ + GList *Viewpoint; +}; + +struct _GstAdaptationSetNode +{ + guint id; + guint group; + gchar *lang; /* LangVectorType RFC 5646 */ + gchar *contentType; + GstRatio *par; + guint minBandwidth; + guint maxBandwidth; + guint minWidth; + guint maxWidth; + guint minHeight; + guint maxHeight; + GstFrameRate *minFrameRate; + GstFrameRate *maxFrameRate; + GstConditionalUintType *segmentAlignment; + GstConditionalUintType *subsegmentAlignment; + GstSAPType subsegmentStartsWithSAP; + gboolean bitstreamSwitching; + /* list of Accessibility DescriptorType nodes */ + GList *Accessibility; + /* list of Role DescriptorType nodes */ + GList *Role; + /* list of Rating DescriptorType nodes */ + GList *Rating; + /* list of Viewpoint DescriptorType nodes */ + GList *Viewpoint; + /* RepresentationBase extension */ + GstRepresentationBaseType *RepresentationBase; + /* SegmentBase node */ + GstSegmentBaseType *SegmentBase; + /* SegmentList node */ + GstSegmentListNode *SegmentList; + /* SegmentTemplate node */ + GstSegmentTemplateNode *SegmentTemplate; + /* list of BaseURL nodes */ + GList *BaseURLs; + /* list of Representation nodes */ + GList *Representations; + /* list of ContentComponent nodes */ + GList *ContentComponents; +}; + +struct _GstSubsetNode +{ + guint *contains; /* UIntVectorType */ + guint size; /* size of the "contains" array */ +}; + +struct _GstPeriodNode +{ + gchar *id; + gint64 start; /* [ms] */ + gint64 duration; /* [ms] */ + gboolean bitstreamSwitching; + /* SegmentBase node */ + GstSegmentBaseType *SegmentBase; + /* SegmentList node */ + GstSegmentListNode *SegmentList; + /* SegmentTemplate node */ + GstSegmentTemplateNode *SegmentTemplate; + /* list of Adaptation Set nodes */ + GList *AdaptationSets; + /* list of Representation nodes */ + GList *Subsets; + /* list of BaseURL nodes */ + GList *BaseURLs; +}; + +struct _GstProgramInformationNode +{ + gchar *lang; /* LangVectorType RFC 5646 */ + gchar *moreInformationURL; + /* children nodes */ + gchar *Title; + gchar *Source; + gchar *Copyright; +}; + +struct _GstMetricsRangeNode +{ + gint64 starttime; /* [ms] */ + gint64 duration; /* [ms] */ +}; + +struct _GstMetricsNode +{ + gchar *metrics; + /* list of Metrics Range nodes */ + GList *MetricsRanges; + /* list of Reporting nodes */ + GList *Reportings; +}; + +struct _GstMPDNode +{ + gchar *default_namespace; + gchar *namespace_xsi; + gchar *namespace_ext; + gchar *schemaLocation; + gchar *id; + gchar *profiles; + GstMPDFileType type; + GstDateTime *availabilityStartTime; + GstDateTime *availabilityEndTime; + gint64 mediaPresentationDuration; /* [ms] */ + gint64 minimumUpdatePeriod; /* [ms] */ + gint64 minBufferTime; /* [ms] */ + gint64 timeShiftBufferDepth; /* [ms] */ + gint64 suggestedPresentationDelay; /* [ms] */ + gint64 maxSegmentDuration; /* [ms] */ + gint64 maxSubsegmentDuration; /* [ms] */ + /* list of BaseURL nodes */ + GList *BaseURLs; + /* list of Location nodes */ + GList *Locations; + /* List of ProgramInformation nodes */ + GList *ProgramInfo; + /* list of Periods nodes */ + GList *Periods; + /* list of Metrics nodes */ + GList *Metrics; +}; + +/** + * GstStreamPeriod: + * + * Stream period data structure + */ +struct _GstStreamPeriod +{ + GstPeriodNode *period; /* Stream period */ + guint number; /* Period number */ + GstClockTime start; /* Period start time */ + GstClockTime duration; /* Period duration */ +}; + +/** + * GstMediaSegment: + * + * Media segment data structure + */ +struct _GstMediaSegment +{ + GstSegmentURLNode *SegmentURL; /* this is NULL when using a SegmentTemplate */ + guint number; /* segment number */ + guint start; /* segment start time in timescale units */ + GstClockTime start_time; /* segment start time */ + GstClockTime duration; /* segment duration */ +}; + +/** + * GstActiveStream: + * + * Active stream data structure + */ +struct _GstActiveStream +{ + GstStreamMimeType mimeType; /* video/audio/application */ + + guint mpd_baseURL_idx; /* MPD baseURL index */ + guint period_baseURL_idx; /* Period baseURL index */ + guint adaptset_baseURL_idx; /* AdaptationSet baseURL index */ + guint repr_baseURL_idx; /* Representation baseURL index */ + + gchar *baseURL; /* active baseURL used for last request */ + guint max_bandwidth; /* max bandwidth allowed for this mimeType */ + + GstAdaptationSetNode *cur_adapt_set; /* active adaptation set */ + gint representation_idx; /* index of current representation */ + GstRepresentationNode *cur_representation; /* active representation */ + GstSegmentBaseType *cur_segment_base; /* active segment base */ + GstSegmentListNode *cur_segment_list; /* active segment list */ + GstSegmentTemplateNode *cur_seg_template; /* active segment template */ + guint segment_idx; /* index of next sequence chunk */ + GList *segments; /* list of GstMediaSegment */ +}; + +struct _GstMpdClient +{ + GstMPDNode *mpd_node; /* active MPD manifest file */ + + GList *periods; /* list of GstStreamPeriod */ + guint period_idx; /* index of current Period */ + + GList *active_streams; /* list of GstActiveStream */ + guint stream_idx; /* currently active stream */ + + guint download_failed_count; + gchar *mpd_uri; /* manifest file URI */ + GMutex *lock; +}; + +/* Basic initialization/deinitialization functions */ +GstMpdClient *gst_mpd_client_new (); +void gst_active_streams_free (GstMpdClient * client); +void gst_mpd_client_free (GstMpdClient * client); + +/* MPD file parsing */ +gboolean gst_mpd_parse (GstMpdClient *client, const gchar *data, gint size); + +/* Streaming management */ +gboolean gst_mpd_client_setup_media_presentation (GstMpdClient *client); +gboolean gst_mpd_client_setup_streaming (GstMpdClient *client, GstStreamMimeType mimeType, gchar* lang); +gboolean gst_mpd_client_setup_representation (GstMpdClient *client, GstActiveStream *stream, GstRepresentationNode *representation); +gboolean gst_mpd_client_get_stream_current_position (GstMpdClient * client, guint stream_idx, GstClockTime *ts); +GstClockTime gst_mpd_client_get_next_fragment_duration (GstMpdClient * client); +GstClockTime gst_mpd_client_get_media_presentation_duration (GstMpdClient *client); +gboolean gst_mpd_client_get_next_fragment (GstMpdClient *client, guint indexStream, gboolean *discontinuity, gchar **uri, GstClockTime *duration, GstClockTime *timestamp); +gboolean gst_mpd_client_get_last_fragment_timestamp (GstMpdClient * client, guint stream_idx, GstClockTime * ts); +gboolean gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, guint stream_idx, GstClockTime * ts); +gboolean gst_mpd_client_get_next_header (GstMpdClient *client, const gchar **uri, guint stream_idx); +gboolean gst_mpd_client_is_live (GstMpdClient * client); + +/* Period selection */ +gboolean gst_mpd_client_set_period_index (GstMpdClient *client, guint period_idx); +guint gst_mpd_client_get_period_index (GstMpdClient *client); +const gchar* gst_mpd_client_get_period_id (GstMpdClient * client); +gboolean gst_mpd_client_set_period_id (GstMpdClient * client, const gchar * period_id); +gboolean gst_mpd_client_has_next_period (GstMpdClient * client); + +/* Representation selection */ +gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, guint64 max_bandwidth); + +/* URL management */ +const gchar *gst_mpdparser_get_baseURL (GstMpdClient *client); +gboolean gst_mpdparser_get_chunk_by_index (GstMpdClient * client, guint indexStream, guint indexChunk, GstMediaSegment *segment); +gboolean gst_mpd_client_set_next_baseURL_for_stream (GstMpdClient * client); + +/* Active stream */ +guint gst_mpdparser_get_nb_active_stream (GstMpdClient *client); +GstActiveStream *gst_mpdparser_get_active_stream_by_index (GstMpdClient *client, guint stream_idx); +gint gst_mpd_client_get_video_active_stream_id(GstMpdClient *client); +gboolean gst_mpd_client_stream_seek (GstMpdClient * client, gint stream_idx, GstClockTime ts); +gboolean gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time); +gint gst_mpd_client_check_time_position (GstMpdClient * client, GstActiveStream * stream, GstClockTime ts, gint64 * diff); +GstClockTime gst_mpd_client_stream_find_segment(GstMpdClient *client, guint stream_idx, GstClockTime seek_pos, guint *seg_num); + +/* AdaptationSet */ +guint gst_mpdparser_get_nb_adaptationSet (GstMpdClient *client); + +/* Segment */ +void gst_mpd_client_set_segment_index_for_all_streams (GstMpdClient * client, guint segment_idx); +void gst_mpd_client_set_segment_index (GstActiveStream * stream, guint segment_idx); +guint gst_mpd_client_get_segment_index (GstActiveStream * stream); + +/* Get audio/video stream parameters (mimeType, width, height, rate, number of channels) */ +const gchar *gst_mpd_client_get_stream_mimeType (GstActiveStream * stream); +const gboolean gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream); +gboolean gst_mpd_client_get_max_video_dimensions(GstMpdClient *client, gint *max_width, gint *max_height); +guint gst_mpd_client_get_video_stream_width (GstActiveStream * stream); +guint gst_mpd_client_get_video_stream_height (GstActiveStream * stream); +guint gst_mpd_client_get_video_stream_bandwidth (GstActiveStream * stream); +guint gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream); +guint gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream); + +/* Support multi language */ +guint gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient *client, GList **lang); +G_END_DECLS + +#endif /* __GST_MPDPARSER_H__ */ diff --git a/dashdemux/src/gstplugin.c b/dashdemux/src/gstplugin.c new file mode 100755 index 0000000..8584caf --- /dev/null +++ b/dashdemux/src/gstplugin.c @@ -0,0 +1,109 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include "gstfragmented.h" +#include "gstdashdemux.h" + +GST_DEBUG_CATEGORY (fragmented_debug); + +#define XML_BUFFER_SIZE 16 +#define XML_INC_BUFFER { \ + pos++; \ + if (pos == XML_BUFFER_SIZE) { \ + pos = 0; \ + offset += XML_BUFFER_SIZE; \ + data = gst_type_find_peek (tf, offset, XML_BUFFER_SIZE); \ + if (data == NULL) return FALSE; \ + } else { \ + data++; \ + } \ +} + +static gboolean +xml_check_first_element (GstTypeFind * tf, const gchar * element, guint elen, + gboolean strict) +{ + gboolean got_xmldec; + const guint8 *data; + guint offset = 0; + guint pos = 0; + + data = gst_type_find_peek (tf, 0, XML_BUFFER_SIZE); + if (!data) + return FALSE; + + /* look for the XMLDec + * see XML spec 2.8, Prolog and Document Type Declaration + * http://www.w3.org/TR/2004/REC-xml-20040204/#sec-prolog-dtd */ + got_xmldec = (memcmp (data, " + * + * gstfragment.c: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "gstfragmented.h" +#include "gstfragment.h" +#include "gsturidownloader.h" + +GST_DEBUG_CATEGORY_STATIC (uridownloader_debug); +#define GST_CAT_DEFAULT (uridownloader_debug) + +#define GST_URI_DOWNLOADER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + GST_TYPE_URI_DOWNLOADER, GstUriDownloaderPrivate)) + +struct _GstUriDownloaderPrivate +{ + /* Fragments fetcher */ + GstElement *urisrc; + GstBus *bus; + GstPad *pad; + GTimeVal *timeout; + GstFragment *download; + GMutex *lock; + GCond *cond; + gboolean canceled; +/* add setting UA,cookies */ + gchar *user_agent; + gchar **cookies; +}; + +static void gst_uri_downloader_finalize (GObject * object); +static void gst_uri_downloader_dispose (GObject * object); + +static GstFlowReturn gst_uri_downloader_chain (GstPad * pad, GstBuffer * buf); +static gboolean gst_uri_downloader_sink_event (GstPad * pad, GstEvent * event); +static GstBusSyncReply gst_uri_downloader_bus_handler (GstBus * bus, + GstMessage * message, gpointer data); + +static GstStaticPadTemplate sinkpadtemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +#define _do_init \ +{ \ + GST_DEBUG_CATEGORY_INIT (uridownloader_debug, "uridownloader", 0, "URI downloader"); \ +} + +G_DEFINE_TYPE_WITH_CODE (GstUriDownloader, gst_uri_downloader, GST_TYPE_OBJECT, + _do_init); + +static void +gst_uri_downloader_class_init (GstUriDownloaderClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GstUriDownloaderPrivate)); + + gobject_class->dispose = gst_uri_downloader_dispose; + gobject_class->finalize = gst_uri_downloader_finalize; +} + +static void +gst_uri_downloader_init (GstUriDownloader * downloader) +{ + downloader->priv = GST_URI_DOWNLOADER_GET_PRIVATE (downloader); + + /* Initialize the sink pad. This pad will be connected to the src pad of the + * element created with gst_element_make_from_uri and will handle the download */ + downloader->priv->pad = + gst_pad_new_from_static_template (&sinkpadtemplate, "sink"); + gst_pad_set_chain_function (downloader->priv->pad, + GST_DEBUG_FUNCPTR (gst_uri_downloader_chain)); + gst_pad_set_event_function (downloader->priv->pad, + GST_DEBUG_FUNCPTR (gst_uri_downloader_sink_event)); + gst_pad_set_element_private (downloader->priv->pad, downloader); + gst_pad_activate_push (downloader->priv->pad, TRUE); + + /* Create a bus to handle error and warning message from the source element */ + downloader->priv->bus = gst_bus_new (); + + downloader->priv->lock = g_mutex_new (); + downloader->priv->cond = g_cond_new (); +/* add setting UA, cookies */ + downloader->priv->user_agent = NULL; + downloader->priv->cookies = NULL; + downloader->priv->canceled = FALSE; +} + +void +gst_uri_downloader_reset(GstUriDownloader *downloader) +{ + downloader->priv->canceled = FALSE; +} + +static void +gst_uri_downloader_dispose (GObject * object) +{ + GstUriDownloader *downloader = GST_URI_DOWNLOADER (object); + + if (downloader->priv->urisrc != NULL) { + gst_object_unref (downloader->priv->urisrc); + downloader->priv->urisrc = NULL; + } + + if (downloader->priv->bus != NULL) { + gst_object_unref (downloader->priv->bus); + downloader->priv->bus = NULL; + } + + if (downloader->priv->pad) { + gst_object_unref (downloader->priv->pad); + downloader->priv->pad = NULL; + } + + if (downloader->priv->download) { + g_object_unref (downloader->priv->download); + downloader->priv->download = NULL; + } + +/* add setting UA, cookies */ + if (downloader->priv->user_agent) { + g_free (downloader->priv->user_agent); + downloader->priv->user_agent = NULL; + } + if (downloader->priv->cookies) { + g_strfreev (downloader->priv->cookies); + downloader->priv->cookies = NULL; + } + G_OBJECT_CLASS (gst_uri_downloader_parent_class)->dispose (object); +} + +static void +gst_uri_downloader_finalize (GObject * object) +{ + GstUriDownloader *downloader = GST_URI_DOWNLOADER (object); + + g_mutex_free (downloader->priv->lock); + g_cond_free (downloader->priv->cond); + + G_OBJECT_CLASS (gst_uri_downloader_parent_class)->finalize (object); +} + +GstUriDownloader * +gst_uri_downloader_new (void) +{ + return g_object_new (GST_TYPE_URI_DOWNLOADER, NULL); +} + +static gboolean +gst_uri_downloader_sink_event (GstPad * pad, GstEvent * event) +{ + GstUriDownloader *downloader = + (GstUriDownloader *) (gst_pad_get_element_private (pad)); + + switch (event->type) { + case GST_EVENT_EOS:{ + GST_DEBUG_OBJECT (downloader, "Got EOS on the fetcher pad"); + g_mutex_lock (downloader->priv->lock); + if (downloader->priv->download != NULL) { + /* signal we have fetched the URI */ + downloader->priv->download->completed = TRUE; + downloader->priv->download->download_stop_time = g_get_real_time (); + GST_DEBUG_OBJECT (downloader, "Signaling chain funtion"); + g_cond_signal (downloader->priv->cond); + } + g_mutex_unlock (downloader->priv->lock); + break; + } + default: + break; + } + + gst_event_unref (event); + return FALSE; +} + +static GstBusSyncReply +gst_uri_downloader_bus_handler (GstBus * bus, + GstMessage * message, gpointer data) +{ + GstUriDownloader *downloader = (GstUriDownloader *) (data); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR || + GST_MESSAGE_TYPE (message) == GST_MESSAGE_WARNING) { + GST_WARNING_OBJECT (downloader, + "Received error in bus from the source element, " + "the download will be cancelled"); + /* remove the sync handler to avoid duplicated messages */ + gst_bus_set_sync_handler (downloader->priv->bus, NULL, NULL); + gst_uri_downloader_cancel (downloader); + } + + gst_message_unref (message); + return GST_BUS_DROP; +} + +static GstFlowReturn +gst_uri_downloader_chain (GstPad * pad, GstBuffer * buf) +{ + GstUriDownloader *downloader = + (GstUriDownloader *) gst_pad_get_element_private (pad); + + /* HTML errors (404, 500, etc...) are also pushed through this pad as + * response but the source element will also post a warning or error message + * in the bus, which is handled synchronously cancelling the download. + */ + g_mutex_lock (downloader->priv->lock); + if (downloader->priv->download == NULL) { + /* Download cancelled, quit */ + goto done; + } + + GST_LOG_OBJECT (downloader, + "The uri fetcher received a new buffer of size %u", + GST_BUFFER_SIZE (buf)); + if (!gst_fragment_add_buffer (downloader->priv->download, buf)) + GST_WARNING_OBJECT (downloader, "Could not add buffer to fragment"); + +done: + { + g_mutex_unlock (downloader->priv->lock); + return GST_FLOW_OK; + } +} + +static void +gst_uri_downloader_stop (GstUriDownloader * downloader) +{ + GstPad *pad; + GST_DEBUG_OBJECT (downloader, "Stopping source element"); + + /* remove the bus' sync handler */ + gst_bus_set_sync_handler (downloader->priv->bus, NULL, NULL); + /* unlink the source element from the internal pad */ + pad = gst_pad_get_peer (downloader->priv->pad); + if (pad) { + gst_pad_unlink (pad, downloader->priv->pad); + gst_object_unref (pad); + } + /* set the element state to NULL */ + if (downloader->priv->urisrc) + { + gst_element_set_state (downloader->priv->urisrc, GST_STATE_NULL); + gst_element_get_state (downloader->priv->urisrc, NULL, NULL, + GST_CLOCK_TIME_NONE); + } +} + +void +gst_uri_downloader_cancel (GstUriDownloader * downloader) +{ + g_mutex_lock (downloader->priv->lock); + if (downloader->priv->download != NULL) { + GST_DEBUG_OBJECT (downloader, "Cancelling download"); + g_object_unref (downloader->priv->download); + downloader->priv->download = NULL; + } else { + GST_DEBUG_OBJECT (downloader, + "Trying to cancell a download that was alredy cancelled"); + } + + GST_DEBUG_OBJECT (downloader, "Signaling chain funtion"); + downloader->priv->canceled = TRUE; + g_cond_signal (downloader->priv->cond); + g_mutex_unlock (downloader->priv->lock); +} + +static gboolean +gst_uri_downloader_set_uri (GstUriDownloader * downloader, const gchar * uri) +{ + GstPad *pad; + + if (!gst_uri_is_valid (uri)) + return FALSE; + + if (downloader->priv->urisrc == NULL) { + GST_DEBUG_OBJECT (downloader, "Creating source element for the URI:%s", uri); + downloader->priv->urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, NULL); + if (!downloader->priv->urisrc) + return FALSE; + } else { + GST_DEBUG_OBJECT (downloader, "Reusing existing source element for the URI:%s", uri); + if (!gst_uri_handler_set_uri (GST_URI_HANDLER (downloader->priv->urisrc), uri)) + return FALSE; + } + g_object_set(downloader->priv->urisrc,"ahs-streaming", TRUE,NULL); + + /* add a sync handler for the bus messages to detect errors in the download */ + gst_element_set_bus (GST_ELEMENT (downloader->priv->urisrc), + downloader->priv->bus); + gst_bus_set_sync_handler (downloader->priv->bus, + gst_uri_downloader_bus_handler, downloader); + + pad = gst_element_get_static_pad (downloader->priv->urisrc, "src"); + if (!pad) + return FALSE; + gst_pad_link (pad, downloader->priv->pad); + gst_object_unref (pad); + return TRUE; +} + +GstFragment * +gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri) +{ + GstStateChangeReturn ret; + GstFragment *download = NULL; +/* add setting UA,cookies */ + gchar **cookies; + + g_mutex_lock (downloader->priv->lock); + + if(downloader->priv->canceled) { + GST_DEBUG_OBJECT (downloader, "Uridownloader was canceled."); + goto quit; + } + + if (!gst_uri_downloader_set_uri (downloader, uri)) { + goto quit; + } + + /* add setting UA,cookies */ + g_object_set (downloader->priv->urisrc, "cookies", downloader->priv->cookies, NULL); + g_object_set (downloader->priv->urisrc, "user-agent", downloader->priv->user_agent, NULL); + + downloader->priv->download = gst_fragment_new (); + + ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + g_object_unref (downloader->priv->download); + downloader->priv->download = NULL; + goto quit; + } + + /* wait until: + * - the download succeed (EOS in the src pad) + * - the download failed (Error message on the fetcher bus) + * - the download was canceled + */ + GST_DEBUG_OBJECT (downloader, "Waiting to fetch the URI"); + g_cond_wait (downloader->priv->cond, downloader->priv->lock); + +/* add setting UA,cookies */ + g_object_get (downloader->priv->urisrc, "cookies", &cookies, NULL); + gst_uri_downloader_set_cookies (downloader, cookies); + + download = downloader->priv->download; + downloader->priv->download = NULL; + + if (download != NULL) + GST_INFO_OBJECT (downloader, "URI fetched successfully"); + else + GST_INFO_OBJECT (downloader, "Error fetching URI"); + +quit: + { + g_mutex_unlock (downloader->priv->lock); + gst_uri_downloader_stop (downloader); + return download; + } +} + +/* add setting UA,cookies */ +void +gst_uri_downloader_set_user_agent (GstUriDownloader * downloader, gchar * user_agent) +{ + if (downloader->priv->user_agent) + g_free (downloader->priv->user_agent); + + downloader->priv->user_agent = user_agent; +} + +void +gst_uri_downloader_set_cookies (GstUriDownloader * downloader, gchar ** cookies) +{ + if (downloader->priv->cookies) + g_strfreev (downloader->priv->cookies); + + downloader->priv->cookies = cookies; +} diff --git a/dashdemux/src/gsturidownloader.h b/dashdemux/src/gsturidownloader.h new file mode 100755 index 0000000..b2b5f06 --- /dev/null +++ b/dashdemux/src/gsturidownloader.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2011 Andoni Morales Alastruey + * + * gsturidownloader.h: + * + * 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. + * + * Youshould have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSTURI_DOWNLOADER_H__ +#define __GSTURI_DOWNLOADER_H__ + +#include +#include +#include "gstfragment.h" + +G_BEGIN_DECLS + +#define GST_TYPE_URI_DOWNLOADER (gst_uri_downloader_get_type()) +#define GST_URI_DOWNLOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_URI_DOWNLOADER,GstUriDownloader)) +#define GST_URI_DOWNLOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_URI_DOWNLOADER,GstUriDownloaderClass)) +#define GST_IS_URI_DOWNLOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_URI_DOWNLOADER)) +#define GST_IS_URI_DOWNLOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_URI_DOWNLOADER)) + +typedef struct _GstUriDownloader GstUriDownloader; +typedef struct _GstUriDownloaderPrivate GstUriDownloaderPrivate; +typedef struct _GstUriDownloaderClass GstUriDownloaderClass; + +struct _GstUriDownloader +{ + GstObject parent; + + GstUriDownloaderPrivate *priv; +}; + +struct _GstUriDownloaderClass +{ + GstObjectClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_uri_downloader_get_type (void); + +GstUriDownloader * gst_uri_downloader_new (void); +GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri); +void gst_uri_downloader_cancel (GstUriDownloader *downloader); +/* add setting UA,cookies */ +void gst_uri_downloader_set_user_agent (GstUriDownloader * downloader, gchar * user_agent); +void gst_uri_downloader_set_cookies (GstUriDownloader * downloader, gchar ** cookies); +void gst_uri_downloader_reset(GstUriDownloader *downloader); +G_END_DECLS +#endif /* __GSTURIDOWNLOADER_H__ */ diff --git a/debian/changelog b/debian/changelog new file mode 100755 index 0000000..3a3a820 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,272 @@ +gst-plugins-ext0.10 (0.2.22-0) unstable; urgency=low + + * [encodebin] Add error log for element link failure + * Tag: gst-plugins-ext0.10_0.2.22-0 + + -- Jeongmo Yang Sat, 02 Feb 2013 14:07:59 +0900 + +gst-plugins-ext0.10 (0.2.21-0) unstable; urgency=low + + * [evasimagesink] enhance code for setting a new evas image object during playing + * Tag: gst-plugins-ext0.10_0.2.21-0 + + -- Sangchul Lee Tue, 15 Jan 2013 21:24:33 +0900 + +gst-plugins-ext0.10 (0.2.19-2) unstable; urgency=low + + * [evasimagesink] add defense code to avoid seg.fault + * Tag: gst-plugins-ext0.10_0.2.19-2 + + -- Sangchul Lee Mon, 07 Jan 2013 22:55:11 +0900 + +gst-plugins-ext0.10 (0.2.16-0) unstable; urgency=low + + * Fix prevent issue + * Tag: gst-plugins-ext0.10_0.2.16-0 + + -- Jeongmo Yang Sat, 22 Dec 2012 14:53:41 +0900 + +gst-plugins-ext0.10 (0.2.15-0) unstable; urgency=low + + * [evasimagesink] fix prevent defects + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.15-0 + + -- Sangchul Lee Sat, 22 Dec 2012 14:03:42 +0900 + +gst-plugins-ext0.10 (0.2.14-0) unstable; urgency=low + + * Solving Prevent issues. + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.14-0 + + -- Abhishek Bajaj Fri, 21 Dec 2012 15:30:27 +0530 + +gst-plugins-ext0.10 (0.2.12-0) unstable; urgency=low + + * drm_trusted was changed. + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.12-0 + + -- Haejeong Kim Thu, 06 Dec 2012 14:34:27 +0900 + +gst-plugins-ext0.10 (0.2.9-0) unstable; urgency=low + + * [evasimagesink] modify present_data_addr, not using vir_addr + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.9-0 + + -- Sangchul Lee Tue, 16 Oct 2012 18:37:08 +0900 + +gst-plugins-ext0.10 (0.2.8-1) unstable; urgency=low + + * add default smack manifest + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.8-1 + + -- Sangchul Lee Fri, 21 Sep 2012 11:27:42 +0900 + +gst-plugins-ext0.10 (0.2.5-0) unstable; urgency=low + + * [encodebin] Add new property to select video converting element + * Git: gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.5-0 + + -- Jeongmo Yang Thu, 13 Sep 2012 15:26:46 +0900 + +gst-plugins-ext0.10 (0.2.4-0) unstable; urgency=low + + * [avsysmemsink] change sink pad's capabilities to bgra8888 + * Git: framework/multimedia/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.4-0 + + -- Sangchul Lee Thu, 06 Sep 2012 22:23:15 +0900 + +gst-plugins-ext0.10 (0.2.3-0) unstable; urgency=low + + * [evasimagesink] fix code to show frame if evas image object does not support gl zerocopy + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.3-0 + + -- Sangchul Lee Mon, 09 Jul 2012 16:26:40 +0900 + +gst-plugins-ext0.10 (0.2.2-0) unstable; urgency=low + + * [evasimagesink] add evas_object_event_callback_del() for EVAS_CALLBACK_DEL and EVAS_CALLBACK_RESIZE event + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.2-0 + + -- Sangchul Lee Mon, 18 Jun 2012 14:19:04 +0900 + +gst-plugins-ext0.10 (0.2.1-0) unstable; urgency=low + + * [evasimagesink] 1.handling GST_EVENT_CUSTOM_DOWNSTREAM_OOB event to skip memory copy in ecore pipe callback 2.remove redundancy ecore_pipe_del() 3.add evas image object resize callback + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.1-0 + + -- Sangchul Lee Tue, 05 Jun 2012 12:03:39 +0900 + +gst-plugins-ext0.10 (0.2.0-1) unstable; urgency=low + + * Apply new drm + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.0-1 + + -- Seungbae Shin Wed, 23 May 2012 11:59:22 +0900 + +gst-plugins-ext0.10 (0.2.0-0) unstable; urgency=low + + * [AudioRoute] remove sound path in avsysaudiosink + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.2.0-0 + + -- Seungbae Shin Mon, 23 Apr 2012 14:10:06 +0900 + +gst-plugins-ext0.10 (0.1.10-1) unstable; urgency=low + + * Enable trickplay audio in case of rate > 0 + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.10-1 + + -- Dowan Kim Tue, 24 Apr 2012 14:44:26 +0900 + +gst-plugins-ext0.10 (0.1.10-0) unstable; urgency=low + + * [evasimagesink] 1. enhance code for multi-instance usage 2. apply MMTA for time analysis + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.10-0 + + -- Sangchul Lee Thu, 19 Apr 2012 19:35:13 +0900 + +gst-plugins-ext0.10 (0.1.9-0) unstable; urgency=low + + * [avsysmemsink] update caps format + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.9-0 + + -- Younghwan Ahn Mon, 02 Apr 2012 19:03:39 +0900 + +gst-plugins-ext0.10 (0.1.8-0) unstable; urgency=low + + * Audiotp plugin added + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.8-0 + + -- Santhoshi.KS Fri, 2 Mar 2012 18:52:26 +0900 + +gst-plugins-ext0.10 (0.1.7-0) unstable; urgency=low + + * [avsysaudiosink] remove memcpy when pcm write + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.7-0 + + -- Seungbae Shin Mon, 13 Feb 2012 13:24:37 +0900 + +gst-plugins-ext0.10 (0.1.6-0) unstable; urgency=low + + * [evasimagesink] 1. add visible property 2. add GST_DEBUG log 3. code cleanup + * [avsystem] revise a parameter of g_signal_emit() in gstavsysmemsink.c + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.6-0 + + -- Sangchul Lee Wed, 08 Feb 2012 14:34:10 +0900 + +gst-plugins-ext0.10 (0.1.5-1) unstable; urgency=low + + * Remove unused build dependency + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.5-1 + + -- Sangchul Lee Tue, 20 Dec 2011 16:26:58 +0900 + +gst-plugins-ext0.10 (0.1.5-0) unstable; urgency=low + + * Remove avsysvideosrc plugin, gettext related and unused files + * Change license from Apache-2.0 to LGPL + * Git: slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.5-0 + + -- Sangchul Lee Tue, 20 Dec 2011 14:41:14 +0900 + +gst-plugins-ext0.10 (0.1.4-2) unstable; urgency=low + + * Add missing files : gettext.h gst-i18n-lib.h for drmsrc + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.4-2 + + -- Sangchul Lee Wed, 07 Dec 2011 13:55:23 +0900 + +gst-plugins-ext0.10 (0.1.4-1) unstable; urgency=low + + * Replace boilerplate - APACHE v2.0 + * Delete unused files : gstavsysvideosink, encodetest, gstpreviewbin + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.4-1 + + -- Sangchul Lee Fri, 02 Dec 2011 14:43:21 +0900 + +gst-plugins-ext0.10 (0.1.4-0) unstable; urgency=low + + * [avsysaudiosrc] remove audio-route property + * [pdpushsrc] fix Makefile.am + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.4-0 + + -- Seungbae Shin Mon, 31 Oct 2011 18:15:01 +0900 + +gst-plugins-ext0.10 (0.1.3-1) unstable; urgency=low + + * [pdpushsrc] initial version + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.3-1 + + -- Younghwan Ahn Mon, 24 Oct 2011 12:51:49 +0900 + +gst-plugins-ext0.10 (0.1.3-0) unstable; urgency=low + + * 1. [avsysaudiosink] restore sound path according to current policy + * 2. [avsysaudiosink] remove unused function/variables + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.3-0 + + -- Seungbae Shin Thu, 20 Oct 2011 18:03:39 +0900 + +gst-plugins-ext0.10 (0.1.2-1) unstable; urgency=low + + * Fix bugs resulted from prevent test (delete unnecessary compare codes and add preventing memory leak codes) + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.2-1 + + -- Sangchul Lee Tue, 11 Oct 2011 15:50:08 +0900 + +gst-plugins-ext0.10 (0.1.2-0) unstable; urgency=low + + * [avsysvideosrc] Update code for metadata of samsung extension format + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.2-0 + + -- Jeongmo Yang Tue, 11 Oct 2011 15:39:10 +0900 + +gst-plugins-ext0.10 (0.1.1-2) unstable; urgency=low + + * [evasimagesink] Fix BS on U1 - add defensive code for gl extension error + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.1-2 + + -- Jeongmo Yang Thu, 06 Oct 2011 20:14:01 +0900 + +gst-plugins-ext0.10 (0.1.1-1) unstable; urgency=low + + * [avsysvideosrc] Correct thumbnail size when JPEG capture + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.1-1 + + -- Jeongmo Yang Mon, 26 Sep 2011 15:27:25 +0900 + +gst-plugins-ext0.10 (0.1.1-0) unstable; urgency=low + + * Initial Release. + * Git: slp-source.sec.samsung.net:slp/pkgs/g/gst-plugins-ext0.10 + * Tag: gst-plugins-ext0.10_0.1.1-0 + + -- Sangchul Lee Wed, 24 Aug 2011 09:58:05 +0900 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100755 index 0000000..e4ad9dc --- /dev/null +++ b/debian/control @@ -0,0 +1,18 @@ +Source: gst-plugins-ext0.10 +Section: libs +Priority: extra +Maintainer: JongHyuk Choi , Seungbae Shin , Younghwan Ahn , Jeongmo Yang , Sangchul Lee , Santhoshi KS +Build-Depends: debhelper (>= 5), libgstreamer0.10-dev, libgstreamer-plugins-base0.10-dev, libavsystem-dev, libmm-ta-dev, drm-client-dev, drm-trusted-dev, libecore-dev, libevas-dev, libmm-ta-dev +Standards-Version: 3.7.2 + +Package: gstreamer0.10-plugins-ext +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libgstreamer0.10-0, libgstreamer-plugins-base0.10-0, libavsystem-0, libmm-ta, drm-client-0, drm-trusted-0, libcamsrcjpegenc +Description: GStreamer extra plugins (common) + +Package: gstreamer0.10-plugins-ext-dbg +Section: debug +Architecture: armel +Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer0.10-plugins-ext (= ${Source-Version}) +Description: GStreamer extra plugins (all) (unstripped) diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..bd79a0c --- /dev/null +++ b/debian/copyright @@ -0,0 +1,16 @@ +Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the +Free Software Foundation; either version 2.1 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 Lesser General Public +License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + diff --git a/debian/gstreamer0.10-plugins-ext.install.in b/debian/gstreamer0.10-plugins-ext.install.in new file mode 100644 index 0000000..f93192c --- /dev/null +++ b/debian/gstreamer0.10-plugins-ext.install.in @@ -0,0 +1,9 @@ +@PREFIX@/lib/gstreamer-0.10/libgstavsyssink.so +@PREFIX@/lib/gstreamer-0.10/libgstencodebin.so +@PREFIX@/lib/gstreamer-0.10/libgsttoggle.so +@PREFIX@/lib/gstreamer-0.10/libgstavsysaudiosrc.so +@PREFIX@/lib/gstreamer-0.10/libgstevasimagesink.so +@PREFIX@/lib/gstreamer-0.10/libgstdrmsrc.so +@PREFIX@/lib/gstreamer-0.10/libgstpdpushsrc.so +@PREFIX@/lib/gstreamer-0.10/libgstaudiotp.so +@PREFIX@/lib/gstreamer-0.10/libgstaudioeq.so diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..49e5ea4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,128 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_HOST_ARCH ?= $(shell dpkg-architecture -qDEB_HOST_ARCH) +DEB_HOST_ARCH_OS ?= $(shell dpkg-architecture -qDEB_HOST_GNU_OS) + +CFLAGS += -Wall -g +LDFLAGS ?= +PREFIX ?= /usr +DATADIR ?= /opt + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +MODULES= +# --disable-ext-evasimagesink + +LDFLAGS += -Wl,--rpath=$(PREFIX)/lib -Wl,--as-needed + +config.status: configure + dh_testdir + # Add here commands to configure the package. + ./autogen.sh + CFLAGS="$(CFLAGS) -DGST_EXT_TIME_ANALYSIS -DEXPORT_API=\"__attribute__((visibility(\\\"default\\\")))\" " LDFLAGS="$(LDFLAGS)" ./configure $(MODULES) $(OPTION) --prefix=$(PREFIX) + +build: build-stamp + +build-stamp: config.status + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #docbook-to-man debian/ncurses.sgml > ncurses.1 + + for f in `find $(CURDIR)/debian/ -name "*.in"`; do \ + cat $$f > $${f%.in}; \ + sed -i -e "s#@PREFIX@#$(PREFIX)#g" $${f%.in}; \ + sed -i -e "s#@DATADIR@#$(DATADIR)#g" $${f%.in}; \ + done + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) distclean +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + + for f in `find $(CURDIR)/debian/ -name "*.in"`; do \ + rm -f $${f%.in}; \ + done + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs -s + + # Add here commands to install the package into debian/ncurses. + $(MAKE) DESTDIR=$(CURDIR)/debian/tmp install +#ifneq (, $(findstring arm, $(DEB_HOST_ARCH))) +# execstack -c $(EXECSTACK_FILES) +#endif + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs -s +# dh_installdocs +# dh_installexamples + dh_install -s --list-missing --sourcedir=debian/tmp +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo +# dh_installman + dh_link -s + dh_strip -s --dbg-package=gstreamer0.10-plugins-ext-dbg + dh_compress -s + dh_fixperms -s +# dh_perl + dh_makeshlibs -s + dh_installdeb -s + dh_shlibdeps -s + dh_gencontrol -s + dh_md5sums -s + dh_builddeb -s + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/drmsrc/src/Makefile.am b/drmsrc/src/Makefile.am index 37eb3bb..cd3f88b 100755 --- a/drmsrc/src/Makefile.am +++ b/drmsrc/src/Makefile.am @@ -14,14 +14,14 @@ plugin_LTLIBRARIES = libgstdrmsrc.la ############################################################################## # sources used to compile this plug-in -libgstdrmsrc_la_SOURCES = gstdrmsrc.c +libgstdrmsrc_la_SOURCES = gstdrmsrc.c drm_util.c # flags used to compile this plugin # add other _CFLAGS and _LIBS as needed -libgstdrmsrc_la_CFLAGS = $(GST_CFLAGS) $(MMTA_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -libgstdrmsrc_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(MMTA_LIBS) +libgstdrmsrc_la_CFLAGS = $(GST_CFLAGS) $(DRM_CLIENT_CFLAGS) $(DRM_TRUSTED_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +libgstdrmsrc_la_LIBADD = $(GST_LIBS) $(DRM_CLIENT_LIBS) $(DRM_TRUSTED_LIBS) $(GST_BASE_LIBS) libgstdrmsrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) # headers we need but don't want installed -noinst_HEADERS = gstdrmsrc.h +noinst_HEADERS = gstdrmsrc.h drm_util.h diff --git a/drmsrc/src/drm_util.c b/drmsrc/src/drm_util.c new file mode 100755 index 0000000..dcff886 --- /dev/null +++ b/drmsrc/src/drm_util.c @@ -0,0 +1,178 @@ +/* + * drm-util.c + * + * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Seungbae Shin + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include "drm_util.h" + +gboolean drm_util_open (DRM_DECRYPT_HANDLE *phandle, char* file_path, int file_type) +{ + drm_trusted_result_e drm_trusted_result; + drm_trusted_open_decrypt_info_s open_decrypt_info; + drm_trusted_open_decrypt_resp_data_s open_decrypt_resp; + drm_trusted_set_consumption_state_info_s decrypt_state_data = { DRM_CONSUMPTION_STARTED }; + + if (phandle == NULL || file_path == NULL) { + GST_ERROR ("Invalid parameter, phandle=%p, file_path=%p", phandle, file_path); + return FALSE; + } + + /* Fill parameter structure for opening decrypt session */ + memset (&open_decrypt_info, 0, sizeof (drm_trusted_open_decrypt_info_s)); + memset (&open_decrypt_resp, 0, sizeof (drm_trusted_open_decrypt_resp_data_s)); + + strncpy (open_decrypt_info.filePath, file_path, sizeof (open_decrypt_info.filePath)-1); + open_decrypt_info.file_type = file_type; + open_decrypt_info.permission = DRM_TRUSTED_PERMISSION_TYPE_PLAY; + + /* Open decrypt session */ + drm_trusted_result = drm_trusted_open_decrypt_session(&open_decrypt_info, &open_decrypt_resp, phandle); + if (drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS || *phandle == NULL) { + GST_ERROR ("Error in drm_trusted_open_decrypt_session(), error=%x, *phandle=%p,", drm_trusted_result, *phandle); + return FALSE; + } + GST_DEBUG ("drm_trusted_open_decrypt_session () success!!, ret=%x, *phandle=%p", drm_trusted_result, *phandle); + + /* Set decryption state to STARTED */ + drm_trusted_result = drm_trusted_set_decrypt_state(*phandle, &decrypt_state_data); + if (drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_set_decrypt_state(), error=%x", drm_trusted_result); + drm_trusted_close_decrypt_session (phandle); + *phandle = NULL; + return FALSE; + } + GST_DEBUG ("drm_trusted_set_decrypt_state () success!!, ret=%x, *phandle=%p", drm_trusted_result, *phandle); + + return TRUE; +} + +gboolean drm_util_read (DRM_DECRYPT_HANDLE handle, unsigned char* buf, unsigned int buf_length, unsigned int *read_size) +{ + drm_trusted_result_e drm_trusted_result; + drm_trusted_payload_info_s payload_info; + drm_trusted_read_decrypt_resp_data_s decrypt_resp; + + if (handle == NULL || buf == NULL) { + GST_ERROR ("Invalid parameter, handle=%p, buf=%p", handle, buf); + return FALSE; + } + + GST_DEBUG ("Enter [%s] buf=%p, length=%d, read_size=%p", __func__, buf, buf_length, read_size); + + /* fill input/output data */ + memset (&payload_info, 0, sizeof (drm_trusted_payload_info_s)); + memset (&decrypt_resp, 0, sizeof (drm_trusted_read_decrypt_resp_data_s)); + + payload_info.payload_data = buf; + payload_info.payload_data_len = buf_length; + payload_info.payload_data_output = buf; + + drm_trusted_result = drm_trusted_read_decrypt_session (handle, &payload_info, &decrypt_resp); + if(drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_read_decrypt_session() [%x]", drm_trusted_result); + return FALSE; + } + + GST_DEBUG ("Leave [%s], drm_trusted_read_decrypt_session() success, requested=%d, read_size=%d", __func__, buf_length, decrypt_resp.read_size); + if (read_size) + *read_size = decrypt_resp.read_size; + + return TRUE; +} + +gboolean drm_util_seek (DRM_DECRYPT_HANDLE handle, int offset, int mode) +{ + drm_trusted_result_e drm_trusted_result; + drm_trusted_seek_decrypt_info_s seek_decrypt_info; + memset (&seek_decrypt_info, 0, sizeof(drm_trusted_seek_decrypt_info_s)); + + GST_DEBUG ("Enter [%s] offset=%d, mode=%d", __func__, offset, mode); + + seek_decrypt_info.offset = offset; + seek_decrypt_info.seek_mode = mode; + + drm_trusted_result = drm_trusted_seek_decrypt_session(handle, &seek_decrypt_info); + if (drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_seek_decrypt_session(), error=%x", drm_trusted_result); + return FALSE; + } + + GST_DEBUG ("Leave [%s], drm_trusted_seek_decrypt_session () success!!", __func__); + return TRUE; +} + +gboolean drm_util_tell (DRM_DECRYPT_HANDLE handle, unsigned int *offset) +{ + drm_trusted_result_e drm_trusted_result; + drm_trusted_tell_decrypt_resp_data_s tell_decrypt_resp_data; + + if (handle == NULL || offset == NULL) { + GST_ERROR ("Invalid parameter, handle=%p, offset=%p", handle, offset); + return FALSE; + } + + GST_DEBUG ("Enter [%s] offset=%p", __func__, offset); + + memset (&tell_decrypt_resp_data, 0, sizeof(drm_trusted_tell_decrypt_resp_data_s)); + + drm_trusted_result = drm_trusted_tell_decrypt_session(handle, &tell_decrypt_resp_data); + if (drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_tell_decrypt_session(), error=%x", drm_trusted_result); + return FALSE; + } + + if (offset) + *offset = tell_decrypt_resp_data.offset; + + GST_DEBUG ("Leave [%s], drm_trusted_tell_decrypt_session () success!!", __func__); + + return TRUE; +} + +gboolean drm_util_close (DRM_DECRYPT_HANDLE *phandle) +{ + drm_trusted_result_e drm_trusted_result; + drm_trusted_set_consumption_state_info_s decrypt_state_data = { DRM_CONSUMPTION_STOPPED }; + + if (phandle == NULL) { + GST_ERROR ("Invalid parameter, phandle=%p", phandle); + return FALSE; + } + + /* Set decryption state to STOPPED */ + drm_trusted_result = drm_trusted_set_decrypt_state(*phandle, &decrypt_state_data); + if (drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_set_decrypt_state(), error=%x", drm_trusted_result); + } else { + GST_DEBUG ("drm_trusted_set_decrypt_state () success!!"); + } + + /* Close decrypt session */ + drm_trusted_result = drm_trusted_close_decrypt_session(phandle); + if(drm_trusted_result != DRM_TRUSTED_RETURN_SUCCESS) { + GST_ERROR ("Error in drm_trusted_close_decrypt_session() error=%x", drm_trusted_result); + return FALSE; + } + GST_DEBUG ("drm_trusted_close_decrypt_session() success!!!"); + + return TRUE; +} diff --git a/drmsrc/src/drm_util.h b/drmsrc/src/drm_util.h new file mode 100755 index 0000000..2e7487d --- /dev/null +++ b/drmsrc/src/drm_util.h @@ -0,0 +1,37 @@ +/* + * drm_util.h + * + * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Seungbae Shin + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __DRM_UTIL_H__ +#define __DRM_UTIL_H__ + +#include + +#include + +gboolean drm_util_open (DRM_DECRYPT_HANDLE *phandle, char* file_path, int file_type); +gboolean drm_util_read (DRM_DECRYPT_HANDLE handle, unsigned char* buf, unsigned int buf_length, unsigned int *read_size); +gboolean drm_util_seek (DRM_DECRYPT_HANDLE handle, int offset, int mode); +gboolean drm_util_tell (DRM_DECRYPT_HANDLE handle, unsigned int *offset); +gboolean drm_util_close (DRM_DECRYPT_HANDLE *phandle); + +#endif /* __DRM_UTIL_H__ */ diff --git a/drmsrc/src/gstdrmsrc.c b/drmsrc/src/gstdrmsrc.c index 5e2776d..74bf4d2 100755 --- a/drmsrc/src/gstdrmsrc.c +++ b/drmsrc/src/gstdrmsrc.c @@ -26,21 +26,52 @@ #endif #include "gstdrmsrc.h" +#include "drm_util.h" +#include +#include + +#ifdef CONTROL_PAGECACHE +#include +#define DEFAULT_DO_FADVISE_THRESHOLD (100 * 1024 * 1024) /* 100 MB */ +#endif + #define LOG_TRACE(message) //g_print("DRM_SRC: %s: %d: %s - %s \n", __FILE__, __LINE__, __FUNCTION__, message); #define GST_TAG_PLAYREADY "playready_file_path" static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,GST_STATIC_CAPS_ANY); +#ifdef CONTROL_PAGECACHE +#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64) +#define DRMSRC_FADVISE_DONT(x_fd, x_offset, x_length) \ + do \ + { \ + if (posix_fadvise64(x_fd, x_offset, x_length, POSIX_FADV_DONTNEED) != 0) \ + { \ + GST_ERROR("Set posix_fadvise with POSIX_FADV_DONTNEED failed"); \ + } \ + }while (0); +#else +#define DRMSRC_FADVISE_DONT(x_fd, x_offset, x_length) \ + do \ + { \ + if (posix_fadvise(x_fd, x_offset, x_length, POSIX_FADV_DONTNEED) != 0) \ + { \ + GST_ERROR("Set posix_fadvise with POSIX_FADV_DONTNEED failed"); \ + } \ + }while (0); +#endif +#endif GST_DEBUG_CATEGORY_STATIC (gst_drm_src_debug); #define GST_CAT_DEFAULT gst_drm_src_debug enum { - ARG_0, - ARG_LOCATION, - ARG_FD + ARG_0, + ARG_LOCATION, + ARG_FD, + IS_DRM }; static void gst_drm_src_finalize (GObject * object); static void gst_drm_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -51,7 +82,9 @@ static gboolean gst_drm_src_is_seekable (GstBaseSrc * src); static gboolean gst_drm_src_get_size (GstBaseSrc * src, guint64 * size); static GstFlowReturn gst_drm_src_create (GstBaseSrc * src, guint64 offset, guint length, GstBuffer ** buffer); static void gst_drm_src_uri_handler_init (gpointer g_iface, gpointer iface_data); - +static gboolean gst_drm_src_query (GstBaseSrc * src, GstQuery * query); +static GstStateChangeReturn gst_drm_src_change_state (GstElement * element, + GstStateChange transition); /** * This function does the following: * 1. Initializes GstDrmSrc ( defines gst_drm_get_type) @@ -62,15 +95,15 @@ static void gst_drm_src_uri_handler_init (gpointer g_iface, gpointer iface_data) */ static void _do_init (GType drmsrc_type) { - // 1. Initializes GstDrmSrc ( defines gst_drm_get_type) - static const GInterfaceInfo urihandler_info = { - gst_drm_src_uri_handler_init, - NULL, - NULL - }; - - g_type_add_interface_static (drmsrc_type, GST_TYPE_URI_HANDLER, &urihandler_info); - GST_DEBUG_CATEGORY_INIT (gst_drm_src_debug, "drmsrc", 0, "drmsrc element"); + // 1. Initializes GstDrmSrc ( defines gst_drm_get_type) + static const GInterfaceInfo urihandler_info = { + gst_drm_src_uri_handler_init, + NULL, + NULL + }; + + g_type_add_interface_static (drmsrc_type, GST_TYPE_URI_HANDLER, &urihandler_info); + GST_DEBUG_CATEGORY_INIT (gst_drm_src_debug, "drmsrc", 0, "drmsrc element"); } GST_BOILERPLATE_FULL (GstDrmSrc, gst_drm_src, GstBaseSrc, GST_TYPE_BASE_SRC, _do_init); /** @@ -84,15 +117,17 @@ GST_BOILERPLATE_FULL (GstDrmSrc, gst_drm_src, GstBaseSrc, GST_TYPE_BASE_SRC, _ */ static void gst_drm_src_base_init (gpointer g_class) { - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); - // 1. Sets the class details - gst_element_class_set_details_simple (gstelement_class, - "DRM Source", - "Source/File", - "Read from arbitrary point in a standard/DRM file", - "Kishore Arepalli and Sadanand Dodawadakar "); - // 2. Adds the source pad template - gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&srctemplate)); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + /*Sets the class details */ + gst_element_class_set_details_simple (gstelement_class, + "DRM Source", + "Source/File", + "Read from arbitrary point in a standard/DRM file", + "Kishore Arepalli and Sadanand Dodawadakar "); + + /*Adds the source pad template */ + gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&srctemplate)); } /** * This function does the following: @@ -105,38 +140,46 @@ static void gst_drm_src_base_init (gpointer g_class) */ static void gst_drm_src_class_init (GstDrmSrcClass * klass) { - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSrcClass *gstbasesrc_class; - gobject_class = G_OBJECT_CLASS (klass); - gstelement_class = GST_ELEMENT_CLASS (klass); - gstbasesrc_class = GST_BASE_SRC_CLASS (klass); - // Assigns the function pointers GObject class attributes - gobject_class->set_property = gst_drm_src_set_property; - gobject_class->get_property = gst_drm_src_get_property; - // 1. Installs the properties - g_object_class_install_property (gobject_class, ARG_FD, - g_param_spec_int ("fd", "File-descriptor", - "File-descriptor for the file being mmap()d", 0, G_MAXINT, 0, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, ARG_LOCATION, - g_param_spec_string ("location", "File Location", - "Location of the file to read", NULL, G_PARAM_READWRITE)); - - // 2. Assigns the function pointers GObject class attributes - gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_drm_src_finalize); - gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_drm_src_start); - gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_drm_src_stop); - gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_drm_src_is_seekable); - gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_drm_src_get_size); - gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_drm_src_create); - - - gst_tag_register (GST_TAG_PLAYREADY, GST_TAG_FLAG_META, - G_TYPE_STRING, - "PlayReady File Path", - "a tag that is specific to PlayReady File", - NULL); + GObjectClass *gobject_class; + GstBaseSrcClass *gstbasesrc_class; + gobject_class = G_OBJECT_CLASS (klass); + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + GstElementClass *gstelement_class; + gstelement_class = (GstElementClass *) klass; + /* Assigns the function pointers GObject class attributes */ + gobject_class->set_property = gst_drm_src_set_property; + gobject_class->get_property = gst_drm_src_get_property; + + /* Installs the properties*/ + g_object_class_install_property (gobject_class, ARG_FD, + g_param_spec_int ("fd", "File-descriptor", + "File-descriptor for the file being mmap()d", 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, ARG_LOCATION, + g_param_spec_string ("location", "File Location", + "Location of the file to read", NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, IS_DRM, + g_param_spec_boolean ("is-drm", "whether selected file type is drm or not", + "true, false", FALSE, + G_PARAM_READABLE)); + + /*Assigns the function pointers GObject class attributes */ + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_drm_src_finalize); + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_drm_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_drm_src_stop); + gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_drm_src_is_seekable); + gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_drm_src_get_size); + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_drm_src_create); + gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_drm_src_query); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_drm_src_change_state); + gst_tag_register (GST_TAG_PLAYREADY, GST_TAG_FLAG_META, + G_TYPE_STRING, + "PlayReady File Path", + "a tag that is specific to PlayReady File", + NULL); } /** * This function does the following: @@ -149,13 +192,21 @@ static void gst_drm_src_class_init (GstDrmSrcClass * klass) */ static void gst_drm_src_init (GstDrmSrc * src, GstDrmSrcClass * g_class) { - // 1. Initilizes the parameters of GstDrmSrc - src->filename = NULL; - src->fd = 0; - src->uri = NULL; - src->is_regular = FALSE; - src->seekable = FALSE; - PROFILE_INIT; + /* Initilizes the parameters of GstDrmSrc */ + src->filename = NULL; + src->fd = 0; + src->uri = NULL; + src->is_regular = FALSE; + src->is_oma = FALSE; + src->seekable = FALSE; + src->hfile = NULL; + src->event_posted = FALSE; + src->is_playready = FALSE; + src->is_drm = FALSE; + src->isopen = FALSE; +#ifdef CONTROL_PAGECACHE + src->accum = 0; +#endif } /** * This function does the following: @@ -168,14 +219,14 @@ static void gst_drm_src_init (GstDrmSrc * src, GstDrmSrcClass * g_class) */ static void gst_drm_src_finalize (GObject * object) { - GstDrmSrc *src; - - src = GST_DRM_SRC (object); - // 1. deallocates the filename and uri - g_free (src->filename); - g_free (src->uri); - // 2. calls the parent class->finalize - G_OBJECT_CLASS (parent_class)->finalize (object); + GstDrmSrc *src; + + src = GST_DRM_SRC (object); + /*. deallocates the filename and uri */ + g_free (src->filename); + g_free (src->uri); + /* calls the parent class->finalize */ + G_OBJECT_CLASS (parent_class)->finalize (object); } /** * This function does the following: @@ -190,35 +241,50 @@ static void gst_drm_src_finalize (GObject * object) */ static gboolean gst_drm_src_set_location (GstDrmSrc * src, const gchar * location) { - GstState state; - - GST_OBJECT_LOCK (src); - // 1. Checks the state - state = GST_STATE (src); - if (state != GST_STATE_READY && state != GST_STATE_NULL) - { - GST_DEBUG_OBJECT (src, "setting location in wrong state"); - GST_OBJECT_UNLOCK (src); - return FALSE; - } - GST_OBJECT_UNLOCK (src); - g_free (src->filename); - g_free (src->uri); - // 2. Checks the filename - if (location == NULL) - { - src->filename = NULL; - src->uri = NULL; - } - else - { - // 3. Sets the filename - src->filename = g_strdup (location); - src->uri = gst_uri_construct ("file", src->filename); - } - g_object_notify (G_OBJECT (src), "location"); - gst_uri_handler_new_uri (GST_URI_HANDLER (src), src->uri); - return TRUE; + GstState state; + + GST_OBJECT_LOCK (src); + + /* Checks the state */ + state = GST_STATE (src); + if (state != GST_STATE_READY && state != GST_STATE_NULL) { + GST_DEBUG_OBJECT (src, "setting location in wrong state"); + GST_OBJECT_UNLOCK (src); + return FALSE; + } + GST_OBJECT_UNLOCK (src); + + g_free (src->filename); + g_free (src->uri); + + /* Checks the filename */ + if (location == NULL) { + src->filename = NULL; + src->uri = NULL; + } else { + /*. Sets the filename */ + src->filename = g_strdup (location); +#if 0 + /*This below API is changing the filename in case of punctuation marks in filename*/ + src->uri = gst_filename_to_uri (location, NULL); +#else + src->uri = g_strdup_printf ("%s://%s", "file", src->filename); + + drm_bool_type_e is_drm = DRM_FALSE; + if (drm_is_drm_file(src->filename, &is_drm) == DRM_RETURN_SUCCESS) { + if (is_drm == DRM_TRUE) { + src->is_drm = TRUE; + } + } + GST_DEBUG_OBJECT (src, "is drm : %d", src->is_drm); +#endif + GST_INFO_OBJECT(src, "filename : %s", src->filename); + GST_INFO_OBJECT(src, "uri : %s", src->uri); + } + g_object_notify (G_OBJECT (src), "location"); + gst_uri_handler_new_uri (GST_URI_HANDLER (src), src->uri); + + return TRUE; } /** * This function does the following: @@ -233,20 +299,21 @@ static gboolean gst_drm_src_set_location (GstDrmSrc * src, const gchar * locatio */ static void gst_drm_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstDrmSrc *src; - - g_return_if_fail (GST_IS_DRM_SRC (object)); - src = GST_DRM_SRC (object); - switch (prop_id) - { - // 1. Sets the location of the file. - case ARG_LOCATION: - gst_drm_src_set_location (src, g_value_get_string (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } + GstDrmSrc *src; + + g_return_if_fail (GST_IS_DRM_SRC (object)); + + src = GST_DRM_SRC (object); + + switch (prop_id) { + // 1. Sets the location of the file. + case ARG_LOCATION: + gst_drm_src_set_location (src, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } /** * This function does the following: @@ -262,26 +329,192 @@ static void gst_drm_src_set_property (GObject * object, guint prop_id, const GVa */ static void gst_drm_src_get_property (GObject * object, guint prop_id, GValue * value,GParamSpec * pspec) { - GstDrmSrc *src; - - g_return_if_fail (GST_IS_DRM_SRC (object)); - src = GST_DRM_SRC (object); - switch (prop_id) - { - // 1. Provides the location of the file. - case ARG_LOCATION: - g_value_set_string (value, src->filename); - break; - // 2. Provides the file descriptor. - case ARG_FD: - g_value_set_int (value, src->fd); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } + GstDrmSrc *src; + + g_return_if_fail (GST_IS_DRM_SRC (object)); + src = GST_DRM_SRC (object); + switch (prop_id) { + case ARG_LOCATION: + g_value_set_string (value, src->filename); + break; + case ARG_FD: + g_value_set_int (value, src->fd); + break; + case IS_DRM: + g_value_set_boolean(value, src->is_drm); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_drm_src_change_state (GstElement * element, GstStateChange transition) +{ + + GstDrmSrc *src = GST_DRM_SRC (element); + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + gboolean ret = FALSE; + drm_file_type_e file_type = DRM_TYPE_UNDEFINED; + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY:{ + GST_INFO_OBJECT(src,"change state from NULL to ready"); + drm_result_e drm_result; + drm_result = drm_get_file_type (src->filename, &file_type); + if (file_type == DRM_TYPE_OMA_V1) { + /* Opens the DRM file if it is DRM */ + if (!src->isopen) { + GST_DEBUG_OBJECT (src, "trying to open drm util"); + if (drm_util_open (&src->hfile, src->filename, file_type) == FALSE) { + GST_ERROR_OBJECT (src, "failed to open drm util"); + return FALSE; + } + src->isopen=TRUE; + } + } + break; + } + case GST_STATE_CHANGE_READY_TO_PAUSED:{ + GST_INFO_OBJECT(src,"change state from ready to paused"); + drm_result_e drm_result; + drm_result = drm_get_file_type (src->filename, &file_type); + if (file_type == DRM_TYPE_OMA_V1){ + ret = drm_process_request(DRM_REQUEST_TYPE_CLIENT_CLEAN_UP, NULL, NULL); + if (DRM_RETURN_SUCCESS == ret) { + GST_INFO("Clean Up successful!!"); + } else { + GST_ERROR("Clean Up Failed!!, ret = 0x%x", ret); + } + ret = drm_trusted_handle_request(DRM_TRUSTED_REQ_TYPE_CLIENT_CLEAN_UP, NULL, NULL); + if (DRM_RETURN_SUCCESS == ret) { + GST_INFO("Clean Up successful!!"); + } else { + GST_ERROR("Clean Up Failed!!, ret = 0x%x", ret); + } + } + break; + } + case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ + GST_INFO_OBJECT(src,"change state from paused to playing"); + break; + } + default: + break; + } + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED:{ + GST_INFO_OBJECT(src,"change state from playing to paused"); + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY:{ + GST_INFO_OBJECT(src,"change state from paused to ready"); + if (src->hfile) { + if (src->isopen) + if (drm_util_close(&src->hfile)) + src->isopen=FALSE; + src->hfile = NULL; +#ifdef CONTROL_PAGECACHE + DRMSRC_FADVISE_DONT(src->hfile, 0, 0); + src->accum = 0; +#endif + } + break; + } + case GST_STATE_CHANGE_READY_TO_NULL:{ + GST_INFO_OBJECT(src,"change state from ready to null"); + break; + } + default: + break; + + } + return result; } + + + +/** + * This function does the following: + * 1. Seeks to the specified position for DRM file. + * 2. Allocates a buffer to push the data for DRM file. + * 3. Reads from the file and sets the related params for DRM file. + * + * @param i_pDrmSrc [in] GstDrmSrc Structure + * @param i_uiOffset [in] offset of the file to seek + * @param length [in] size of the data in bytes + * @param o_pBbuffer [out] GstBuffer to hold the contents + * + * @return GstFlowReturn Returns GST_FLOW_OK on success and ERROR on failure + */ +static GstFlowReturn gst_drm_src_create_read_drm_file (GstDrmSrc* src, guint64 i_uiOffset, guint length, GstBuffer ** o_pBbuffer) +{ + GstBuffer *buf = NULL; + unsigned int readSize; + + /* Seeks to the specified position for DRM file. */ + if (G_UNLIKELY (src->read_position != i_uiOffset)) { + if (drm_util_seek (src->hfile, i_uiOffset, DRM_SEEK_SET) == FALSE) + goto FAILED; + + src->read_position = i_uiOffset; + } + + /* Allocates a buffer to push the data for DRM file. */ + buf = gst_buffer_new_and_alloc (length); + if(buf == NULL) { + LOG_TRACE("Exit on error"); + return GST_FLOW_ERROR; + } + + /*. Reads from the file and sets the related params for DRM file. */ + if (drm_util_read (src->hfile, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf), &readSize) == FALSE) + goto FAILED; + + if(readSize <= 0) { + LOG_TRACE("Exit on error"); + return GST_FLOW_ERROR; + } + + #if 0 // Drm service can give lesser size block than requested thing. + if (G_UNLIKELY ((guint) readSize < length && i_pDrmSrc->seekable)) { + GST_ELEMENT_ERROR (i_pDrmSrc, RESOURCE, READ, (NULL),("unexpected end of file.")); + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } + #endif + + if (G_UNLIKELY (readSize == 0 && length > 0)) { + GST_DEBUG ("non-regular file hits EOS"); + gst_buffer_unref (buf); + return GST_FLOW_UNEXPECTED; + } + +#ifdef CONTROL_PAGECACHE + src->accum += readSize; + if (src->accum >= DEFAULT_DO_FADVISE_THRESHOLD) { + DRMSRC_FADVISE_DONT(src->hfile, 0, 0); + src->accum = 0; + } +#endif + + length = readSize; + GST_BUFFER_SIZE (buf) = length; + GST_BUFFER_OFFSET (buf) = i_uiOffset; + GST_BUFFER_OFFSET_END (buf) = i_uiOffset + length; + *o_pBbuffer = buf; + src->read_position += length; + + return GST_FLOW_OK; + +FAILED: +{ + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; +} +} /** * This function does the following: * 1. Seeks to the specified position. @@ -297,50 +530,67 @@ static void gst_drm_src_get_property (GObject * object, guint prop_id, GValue * */ static GstFlowReturn gst_drm_src_create_read (GstDrmSrc * src, guint64 offset, guint length, GstBuffer ** buffer) { - int ret; - GstBuffer *buf; - // 1. Seeks to the specified position. - if (G_UNLIKELY (src->read_position != offset)) - { - off_t res; - res = lseek (src->fd, offset, SEEK_SET); - if (G_UNLIKELY (res < 0 || res != offset)) - { - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); - return GST_FLOW_ERROR; - } - src->read_position = offset; - } - // 2. Allocates a buffer to push the data - buf = gst_buffer_new_and_alloc (length); - GST_LOG_OBJECT (src, "Reading %d bytes", length); - // 3. Reads from the file and sets the related params - ret = read (src->fd, GST_BUFFER_DATA (buf), length); - if (G_UNLIKELY (ret < 0)) - { - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); - gst_buffer_unref (buf); - return GST_FLOW_ERROR; - } - if (G_UNLIKELY ((guint) ret < length && src->seekable)) - { - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL),("unexpected end of file.")); - gst_buffer_unref (buf); - return GST_FLOW_ERROR; - } - if (G_UNLIKELY (ret == 0 && length > 0)) - { - GST_DEBUG ("non-regular file hits EOS"); - gst_buffer_unref (buf); - return GST_FLOW_UNEXPECTED; + int ret; + GstBuffer *buf; + + /* Seeks to the specified position. */ + if (G_UNLIKELY (src->read_position != offset)) { + off_t res; + res = lseek (src->fd, offset, SEEK_SET); + if (G_UNLIKELY (res < 0 || res != offset)) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } + src->read_position = offset; + } + + /* Allocates a buffer to push the data */ + buf = gst_buffer_new_and_alloc (length); + if (NULL == buf) { + GST_ERROR_OBJECT (src, "failed to allocate memory.."); + GST_ELEMENT_ERROR (src, RESOURCE, NO_SPACE_LEFT, (NULL), ("failed to allocate memory")); + return GST_FLOW_ERROR; + } + + GST_LOG_OBJECT (src, "Reading %d bytes", length); + + /* Reads from the file and sets the related params */ + ret = read (src->fd, GST_BUFFER_DATA (buf), length); + if (G_UNLIKELY (ret < 0)) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } + + if (G_UNLIKELY ((guint) ret < length && src->seekable)) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL),("unexpected end of file.")); + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } + + if (G_UNLIKELY (ret == 0 && length > 0)) { + GST_DEBUG ("non-regular file hits EOS"); + gst_buffer_unref (buf); + return GST_FLOW_UNEXPECTED; + } + +#ifdef CONTROL_PAGECACHE + src->accum += ret; + if (src->accum >= DEFAULT_DO_FADVISE_THRESHOLD) { + DRMSRC_FADVISE_DONT(src->fd, 0, 0); + src->accum = 0; } - length = ret; - GST_BUFFER_SIZE (buf) = length; - GST_BUFFER_OFFSET (buf) = offset; - GST_BUFFER_OFFSET_END (buf) = offset + length; - *buffer = buf; - src->read_position += length; - return GST_FLOW_OK; + //DRMSRC_FADVISE_DONT(src->fd, offset, ret); +#endif + + length = ret; + GST_BUFFER_SIZE (buf) = length; + GST_BUFFER_OFFSET (buf) = offset; + GST_BUFFER_OFFSET_END (buf) = offset + length; + *buffer = buf; + src->read_position += length; + + return GST_FLOW_OK; } /** * This function does the following: @@ -354,13 +604,73 @@ static GstFlowReturn gst_drm_src_create_read (GstDrmSrc * src, guint64 offset, g */ static GstFlowReturn gst_drm_src_create (GstBaseSrc * basesrc, guint64 offset, guint length, GstBuffer ** buffer) { - GstDrmSrc *src = GST_DRM_SRC (basesrc); + GstDrmSrc *src = GST_DRM_SRC (basesrc); - // 1. Calls DRM file read chain method for drm files. + if (src->is_playready && src->event_posted == FALSE) { + GstTagList *tags = NULL; + GST_DEBUG_OBJECT (src, "posting playready tags"); + tags = gst_tag_list_new_full (GST_TAG_PLAYREADY, src->filename, NULL); + if (tags) { + GstPad* src_pad = gst_element_get_static_pad (src, "src"); + if (src_pad) { + if(!gst_pad_push_event (src_pad, gst_event_new_tag (tags))) { + GST_ERROR_OBJECT (src, "failed to push tags.."); + gst_object_unref (src_pad); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (src, "posting tags returns [%d]", src->event_posted); + src->event_posted = TRUE; + gst_object_unref (src_pad); + } + } + } - // 2. Calls normal file read chain method for standard files. - return gst_drm_src_create_read (src, offset, length, buffer); + if(src->is_oma == TRUE) /* Calls DRM file read chain method for drm files. */ + return gst_drm_src_create_read_drm_file (src, offset, length, buffer); + else /* Calls normal file read chain method for standard files. */ + return gst_drm_src_create_read (src, offset, length, buffer); } + +static gboolean +gst_drm_src_query (GstBaseSrc * basesrc, GstQuery * query) +{ + gboolean ret = FALSE; + GstDrmSrc *src = GST_DRM_SRC (basesrc); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_URI: + gst_query_set_uri (query, src->uri); + ret = TRUE; + break; + case GST_QUERY_CUSTOM: + { + GstStructure *s; + guint64 size = 0; + GValue v = { 0, { { 0 } } }; + g_value_init(&v, G_TYPE_UINT64); + + s = gst_query_get_structure (query); + if (gst_structure_has_name (s, "dynamic-size")) { + if (gst_drm_src_get_size (basesrc, &size)){ + /* succedded. take size */ + g_value_set_uint64 (&v, size); + gst_structure_set_value(s, "size", &v); + ret = TRUE; + } + } + break; + } + default: + ret = FALSE; + break; + } + + if (!ret) + ret = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query); + + return ret; +} + /** * * @param basesrc [in] BaseSrc Structure @@ -369,8 +679,8 @@ static GstFlowReturn gst_drm_src_create (GstBaseSrc * basesrc, guint64 offset, g */ static gboolean gst_drm_src_is_seekable (GstBaseSrc * basesrc) { - GstDrmSrc *src = GST_DRM_SRC (basesrc); - return src->seekable; + GstDrmSrc *src = GST_DRM_SRC (basesrc); + return src->seekable; } /** * This function does the following: @@ -384,22 +694,43 @@ static gboolean gst_drm_src_is_seekable (GstBaseSrc * basesrc) */ static gboolean gst_drm_src_get_size (GstBaseSrc * basesrc, guint64 * size) { - struct stat stat_results; - GstDrmSrc *src = GST_DRM_SRC (basesrc); - unsigned int offset; + struct stat stat_results; + GstDrmSrc *src = GST_DRM_SRC (basesrc); + unsigned int offset; + + /* Gets the filesize for drm file by using seek oprations */ + if(src->is_oma==TRUE) { + drm_util_seek (src->hfile, 0, DRM_SEEK_END); + if (drm_util_tell(src->hfile, &offset) == TRUE) { + /* FIXME : drm doesn't support 64 */ + *size = offset; + } + drm_util_seek (src->hfile, 0, DRM_SEEK_SET); + src->read_position = 0; + return TRUE; + } + + if (!src->seekable) { + GST_DEBUG_OBJECT (src, "non-seekable"); + return FALSE; + } - // 1. Gets the filesize for drm file by using seek oprations + /* Gets the file size for standard file by using statistics */ + if (fstat (src->fd, &stat_results) < 0) + return FALSE; - // 2. Gets the file size for standard file by using statistics - if (fstat (src->fd, &stat_results) < 0) - return FALSE; - *size = stat_results.st_size; - return TRUE; + *size = stat_results.st_size; + GST_DEBUG_OBJECT (src, "size : %"G_GUINT64_FORMAT, *size); + return TRUE; } /** * This function does the following: * 1. Checks the filename * 2. Opens the file and check statistics of the file + * 3. Checks whether DRM file or not. + * 4. Checks the DRM file type (supports only for OMA) if it is DRM + * 5. Opens the DRM file if it is DRM + * 6. Gets the DRM_FILE_HANDLE and sets the drm, seekable and regular flag. * 7. Checks the seeking for standard files * * @param basesrc [in] BaseSrc Structure @@ -408,67 +739,107 @@ static gboolean gst_drm_src_get_size (GstBaseSrc * basesrc, guint64 * size) */ static gboolean gst_drm_src_start (GstBaseSrc * basesrc) { - GstDrmSrc *src = GST_DRM_SRC (basesrc); - struct stat stat_results; - off_t ret; -PROFILE_FUNC_BEGIN; - // 1. Checks the filename - if (src->filename == NULL || src->filename[0] == '\0') - { - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,("No file name specified for reading."), (NULL)); - return FALSE; - } - // 2. Opens the file and check statistics of the file - GST_INFO_OBJECT (src, "opening file %s", src->filename); - src->fd = open (src->filename, O_RDONLY | O_BINARY); - if (src->fd < 0) - { - if(errno == ENOENT) - { - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL),("No such file \"%s\"", src->filename)); - return FALSE; - } - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("Could not open file \"%s\" for reading.", src->filename), GST_ERROR_SYSTEM); - return FALSE; - } - if (fstat (src->fd, &stat_results) < 0) - { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("Could not get info on \"%s\".", src->filename), (NULL)); - close (src->fd); - return FALSE; - } - if (S_ISDIR (stat_results.st_mode)) - { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("\"%s\" is a directory.", src->filename), (NULL)); - close (src->fd); - return FALSE; - } - if (S_ISSOCK (stat_results.st_mode)) - { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("File \"%s\" is a socket.", src->filename), (NULL)); - close (src->fd); - return FALSE; - } - src->read_position = 0; - - // 7. Checks the seeking for standard files - if (S_ISREG (stat_results.st_mode)) - src->is_regular = TRUE; - ret = lseek (src->fd, 0, SEEK_END); - if (ret < 0) - { - GST_LOG_OBJECT (src, "disabling seeking, not in mmap mode and lseek " - "failed: %s", g_strerror (errno)); - src->seekable = FALSE; - } - else - { - src->seekable = TRUE; - } - lseek (src->fd, 0, SEEK_SET); - src->seekable = src->seekable && src->is_regular; - PROFILE_FUNC_END; - return TRUE; + GstDrmSrc *src = GST_DRM_SRC (basesrc); + struct stat stat_results; + drm_result_e drm_result; + drm_file_type_e file_type; + off_t ret; + + /* Checks the filename */ + if (src->filename == NULL || src->filename[0] == '\0') { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,("No file name specified for reading."), (NULL)); + return FALSE; + } + + /* Opens the file and check statistics of the file */ + GST_INFO_OBJECT (src, "opening file %s", src->filename); + src->fd = open (src->filename, O_RDONLY | O_BINARY); + if (src->fd < 0) { + if(errno == ENOENT) { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL),("No such file \"%s\"", src->filename)); + return FALSE; + } + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("Could not open file \"%s\" for reading.", src->filename), GST_ERROR_SYSTEM); + return FALSE; + } + + if (fstat (src->fd, &stat_results) < 0) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("Could not get info on \"%s\".", src->filename), (NULL)); + close (src->fd); + return FALSE; + } + + if (S_ISDIR (stat_results.st_mode)) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("\"%s\" is a directory.", src->filename), (NULL)); + close (src->fd); + return FALSE; + } + + if (S_ISSOCK (stat_results.st_mode)) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("File \"%s\" is a socket.", src->filename), (NULL)); + close (src->fd); + return FALSE; + } + + src->read_position = 0; + + + /* Checks whether DRM file or not.*/ + drm_result = drm_get_file_type (src->filename, &file_type); + if (drm_result != DRM_RETURN_SUCCESS) { + GST_ERROR_OBJECT (src,"Error in drm_get_file_type(), error=%d", drm_result); + return FALSE; + } + GST_DEBUG_OBJECT (src, "file_path = [%s], file_type = [%d]", src->filename, file_type); + +#if 0 + if (DRM_TYPE_UNDEFINED != file_type) { + drm_bool_type_e is_drm = DRM_FALSE; + if (drm_is_drm_file(src->filename, &is_drm) == DRM_RETURN_SUCCESS) { + if (is_drm == DRM_TRUE) { + src->is_drm = TRUE; + } + } + GST_DEBUG_OBJECT (src, "is drm = [%d]", src->is_drm); + } +#endif + + /* We handles as DRM file if it is drm with OMA type */ + if (file_type == DRM_TYPE_OMA_V1) { /* FIMXE: what about DRM_TYPE_OMA_V2 */ + // Gets the DRM_FILE_HANDLE and sets the drm, seekable and regular flags. + drm_util_seek (src->hfile, 0, DRM_SEEK_END); + drm_util_seek (src->hfile, 0, DRM_SEEK_SET); + + src->seekable = TRUE; + src->is_regular = TRUE; + src->is_oma = TRUE; + + LOG_TRACE("Exit"); + return TRUE; + } + + if (file_type == DRM_TYPE_PLAYREADY || file_type == DRM_TYPE_PLAYREADY_ENVELOPE) { /* FIXME: what is envelope?? */ + src->is_playready = TRUE; + src->event_posted = FALSE; + } + + /* Checks the seeking for standard files */ + if (S_ISREG (stat_results.st_mode)) + src->is_regular = TRUE; + + ret = lseek (src->fd, 0, SEEK_END); + if (ret < 0) { + GST_LOG_OBJECT (src, "disabling seeking, not in mmap mode and lseek failed: %s", g_strerror (errno)); + src->seekable = FALSE; + } else { + src->seekable = TRUE; + } + + lseek (src->fd, 0, SEEK_SET); + src->seekable = src->seekable && src->is_regular; + + + return TRUE; } /** * This function does the following: @@ -480,15 +851,22 @@ PROFILE_FUNC_BEGIN; */ static gboolean gst_drm_src_stop (GstBaseSrc * basesrc) { - GstDrmSrc *src = GST_DRM_SRC (basesrc); - - // 1. Closes the file desciptor and resets the flags - if(src->fd > 0) - close (src->fd); - src->fd = 0; - src->is_regular = FALSE; -// PROFILE_SHOW_RESULT; - return TRUE; + GstDrmSrc *src = GST_DRM_SRC (basesrc); + // 1. Closes the file desciptor and resets the flags + if(src->fd > 0) { +#ifdef CONTROL_PAGECACHE + DRMSRC_FADVISE_DONT(src->fd, 0, 0); + src->accum = 0; +#endif + close (src->fd); + } + + src->fd = 0; + src->is_regular = FALSE; + src->event_posted = FALSE; + src->is_playready = FALSE; + + return TRUE; } /** * @@ -499,7 +877,7 @@ static gboolean gst_drm_src_stop (GstBaseSrc * basesrc) static GstURIType gst_drm_src_uri_get_type (void) { - return GST_URI_SRC; + return GST_URI_SRC; } /** @@ -513,8 +891,8 @@ static GstURIType gst_drm_src_uri_get_type (void) static gchar ** gst_drm_src_uri_get_protocols (void) { - static gchar *protocols[] = { "file", NULL }; - return protocols; + static gchar *protocols[] = { "file", NULL }; + return protocols; } /** * @@ -524,8 +902,8 @@ static gchar ** gst_drm_src_uri_get_protocols (void) */ static const gchar * gst_drm_src_uri_get_uri (GstURIHandler *handler) { - GstDrmSrc *src = GST_DRM_SRC (handler); - return src->uri; + GstDrmSrc *src = GST_DRM_SRC (handler); + return src->uri; } /** * This function does the following: @@ -540,46 +918,50 @@ static const gchar * gst_drm_src_uri_get_uri (GstURIHandler *handler) */ static gboolean gst_drm_src_uri_set_uri (GstURIHandler *handler, const gchar * uri) { - gchar *protocol, *location; - gboolean ret; - GstDrmSrc *src = GST_DRM_SRC (handler); - // 1. Checks the protocol - protocol = gst_uri_get_protocol (uri); - if (strcmp (protocol, "file") != 0) - { - g_free (protocol); - return FALSE; - } - g_free (protocol); - if (g_str_has_prefix (uri, "file://localhost/")) - { - char *tmp; - tmp = g_strconcat ("file://", uri + 16, NULL); - location = gst_uri_get_location (tmp); - g_free (tmp); - } - else if (strcmp (uri, "file://") == 0) - { - gst_drm_src_set_location (src, NULL); - return TRUE; - } - else - { - location = gst_uri_get_location (uri); - } - if (!location) - return FALSE; - // 2. Checks the whether it is absolute or not - if (!g_path_is_absolute (location)) - { - g_free (location); - return FALSE; - } - // 3 sets the location - ret = gst_drm_src_set_location (src, location); - g_free (location); - return ret; + gchar *location, *hostname = NULL; + gboolean ret = FALSE; + GstDrmSrc *src = GST_DRM_SRC (handler); + GError *error = NULL; + + if (strcmp (uri, "file://") == 0) { + /* Special case for "file://" as this is used by some applications + * to test with gst_element_make_from_uri if there's an element + * that supports the URI protocol. */ + gst_drm_src_set_location (src, NULL); + return TRUE; + } + + location = g_filename_from_uri (uri, &hostname, &error); + + if (!location || error) { + if (error) { + GST_WARNING_OBJECT (src, "Invalid URI '%s' for filesrc: %s", uri, + error->message); + g_error_free (error); + } else { + GST_WARNING_OBJECT (src, "Invalid URI '%s' for filesrc", uri); + } + goto beach; + } + + if ((hostname) && (strcmp (hostname, "localhost"))) { + /* Only 'localhost' is permitted */ + GST_WARNING_OBJECT (src, "Invalid hostname '%s' for filesrc", hostname); + goto beach; + } + + ret = gst_drm_src_set_location (src, location); + +beach: + if (location) + g_free (location); + if (hostname) + g_free (hostname); + + return ret; } + + /** * This function does the following: * 1. Assignes the function pointer for URI related stuff @@ -591,12 +973,13 @@ static gboolean gst_drm_src_uri_set_uri (GstURIHandler *handler, const gchar * u */ static void gst_drm_src_uri_handler_init (gpointer g_iface, gpointer iface_data) { - GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; - // 1. Assigning the function pointer for URI related stuff - iface->get_type = gst_drm_src_uri_get_type; - iface->get_protocols = gst_drm_src_uri_get_protocols; - iface->get_uri = gst_drm_src_uri_get_uri; - iface->set_uri = gst_drm_src_uri_set_uri; + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + /* Assigning the function pointer for URI related stuff */ + iface->get_type = gst_drm_src_uri_get_type; + iface->get_protocols = gst_drm_src_uri_get_protocols; + iface->get_uri = gst_drm_src_uri_get_uri; + iface->set_uri = gst_drm_src_uri_set_uri; } /** * This function does the following: @@ -608,7 +991,7 @@ static void gst_drm_src_uri_handler_init (gpointer g_iface, gpointer iface_data) */ static gboolean plugin_init(GstPlugin* i_pPlugin) { - return gst_element_register(i_pPlugin, "drmsrc", GST_RANK_NONE, GST_TYPE_DRM_SRC);; + return gst_element_register(i_pPlugin, "drmsrc", GST_RANK_NONE, GST_TYPE_DRM_SRC); } /** * This function does the following: @@ -616,12 +999,12 @@ static gboolean plugin_init(GstPlugin* i_pPlugin) * */ GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "drmsrc", - "Plugin to read data from standad/DRM File", - plugin_init, - VERSION, - "LGPL", - "Samsung Electronics Co", - "http://www.samsung.com/") + GST_VERSION_MINOR, + "drmsrc", + "Plugin to read data from standad/DRM File", + plugin_init, + VERSION, + "LGPL", + "Samsung Electronics Co", + "http://www.samsung.com/") diff --git a/drmsrc/src/gstdrmsrc.h b/drmsrc/src/gstdrmsrc.h index e47802e..3390b5c 100755 --- a/drmsrc/src/gstdrmsrc.h +++ b/drmsrc/src/gstdrmsrc.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -48,30 +49,6 @@ #define O_BINARY (0) #endif -#define ENABLE_PROFILING_INFO -#ifdef ENABLE_PROFILING_INFO -#define PROFILE_FUNC_BEGIN\ - char *newname; \ - newname=__FUNCTION__; \ - gst_ta_accum_item_begin(newname,FALSE,__FILE__,__LINE__); - -#define PROFILE_FUNC_END \ - gst_ta_accum_item_end(newname,FALSE,__FILE__,__LINE__); - -#define PROFILE_BLOCK_BEGIN(name) gst_ta_accum_item_begin(name,FALSE,__FILE__,__LINE__); -#define PROFILE_BLOCK_END(name) gst_ta_accum_item_end(name,FALSE,__FILE__,__LINE__); -#define PROFILE_INIT gst_ta_init(); -#define PROFILE_SHOW_RESULT gst_ta_accum_show_result(0);//MMTA_SHOW_FILE); -#define PROFILE_CLEAR_DATA gst_ta_release(); -#else -#define PROFILE_FUNC_BEGIN -#define PROFILE_FUNC_END -#define PROFILE_BLOCK_BEGIN(name) -#define PROFILE_BLOCK_END(name) -#define PROFILE_INIT -#define PROFILE_SHOW_RESULT -#define PROFILE_CLEAR_DATA -#endif G_BEGIN_DECLS #define GST_TYPE_DRM_SRC (gst_drm_src_get_type()) @@ -90,8 +67,17 @@ struct _GstDrmSrc gchar *uri; gint fd; guint64 read_position; - gboolean seekable; + gboolean seekable; gboolean is_regular; + gboolean is_drm; // flag indicating drm file + DRM_DECRYPT_HANDLE hfile; + gboolean is_playready; + gboolean is_oma; + gboolean event_posted; + gboolean isopen; +#ifdef CONTROL_PAGECACHE + guint64 accum; +#endif }; struct _GstDrmSrcClass diff --git a/encodebin/src/gstencodebin.c b/encodebin/src/gstencodebin.c old mode 100644 new mode 100755 index 9539702..3113f09 --- a/encodebin/src/gstencodebin.c +++ b/encodebin/src/gstencodebin.c @@ -54,36 +54,56 @@ {\ g_object_set(G_OBJECT(x_queue), \ "max-size-bytes", (guint)x_byte, \ - "max-size-buffers", (guint)x_buffer, \ + "max-size-buffers", (guint)x_buffer, \ "max-size-time", (guint64)(x_time*GST_SECOND), \ NULL); \ GST_INFO("Set to [%s], max [%d] byte, max [%d] buffer, max [%d] time(sec) ", GST_OBJECT_NAME(x_queue), x_byte, x_buffer, x_time);\ } #else -#define ENCODER_QUEUE_SET(x_queue, x_byte, x_buffer, x_time) +#define ENCODER_QUEUE_SET(x_queue, x_byte, x_buffer, x_time) #endif -#define _GST_PAD_LINK_UNREF( srcpad, sinkpad, if_fail_goto )\ +#define _GST_PAD_LINK_UNREF(srcpad, sinkpad, if_fail_goto)\ {\ - GstPadLinkReturn ret = _GST_PAD_LINK( srcpad, sinkpad );\ - gst_object_unref( srcpad ); srcpad = NULL;\ - gst_object_unref( sinkpad ); sinkpad = NULL;\ - if( ret != GST_PAD_LINK_OK) goto if_fail_goto;\ + GstPadLinkReturn ret = _GST_PAD_LINK(srcpad, sinkpad);\ + if(ret != GST_PAD_LINK_OK) { \ + GstObject *src_parent = gst_pad_get_parent(srcpad);\ + GstObject *sink_parent = gst_pad_get_parent(sinkpad);\ + char *src_name = NULL;\ + char *sink_name = NULL;\ + g_object_get((GObject *)src_parent, "name", &src_name, NULL);\ + g_object_get((GObject *)sink_parent, "name", &sink_name, NULL);\ + GST_ERROR("src[%s] - sink[%s] link failed", src_name, sink_name);\ + gst_object_unref(src_parent); src_parent = NULL;\ + gst_object_unref(sink_parent); sink_parent = NULL;\ + if (src_name) {\ + free(src_name); src_name = NULL;\ + }\ + if (sink_name) {\ + free(sink_name); sink_name = NULL;\ + }\ + gst_object_unref(srcpad); srcpad = NULL;\ + gst_object_unref(sinkpad); sinkpad = NULL;\ + goto if_fail_goto;\ + }\ + gst_object_unref(srcpad); srcpad = NULL;\ + gst_object_unref(sinkpad); sinkpad = NULL;\ } -#define _GST_PAD_UNLINK_UNREF( srcpad, sinkpad)\ +#define _GST_PAD_UNLINK_UNREF(srcpad, sinkpad)\ {\ - gst_pad_unlink( srcpad, sinkpad );\ - gst_object_unref( srcpad ); srcpad = NULL;\ - gst_object_unref( sinkpad ); sinkpad = NULL;\ + gst_pad_unlink(srcpad, sinkpad);\ + gst_object_unref(srcpad); srcpad = NULL;\ + gst_object_unref(sinkpad); sinkpad = NULL;\ } -#define DEFAULT_PROP_PROFILE 0 -#define DEFAULT_PROP_HIGH_SPEED 0 -#define DEFAULT_PROP_VENC_NAME "ffenc_h263" -#define DEFAULT_PROP_AENC_NAME "secenc_amr" -#define DEFAULT_PROP_IENC_NAME "jpegenc" -#define DEFAULT_PROP_MUX_NAME "ffmux_3gp" +#define DEFAULT_PROP_PROFILE 0 +#define DEFAULT_PROP_HIGH_SPEED 0 +#define DEFAULT_PROP_VENC_NAME "ffenc_h263" +#define DEFAULT_PROP_AENC_NAME "secenc_amr" +#define DEFAULT_PROP_IENC_NAME "jpegenc" +#define DEFAULT_PROP_MUX_NAME "ffmux_3gp" +#define DEFAULT_PROP_VCONV_NAME "ffmpegcolorspace" /* props */ enum @@ -96,8 +116,9 @@ enum //elements name PROP_VENC_NAME, PROP_AENC_NAME, - PROP_IENC_NAME, - PROP_MUX_NAME, + PROP_IENC_NAME, + PROP_MUX_NAME, + PROP_VCONV_NAME, //caps PROP_VCAPS, PROP_ACAPS, @@ -108,13 +129,14 @@ enum PROP_AUTO_COLORSPACE, PROP_BLOCK, PROP_PAUSE, - PROP_VENC_QUEUE, + PROP_VENC_QUEUE, PROP_AENC_QUEUE, //elements pointer PROP_VIDEO_ENC, PROP_AUDIO_ENC, PROP_IMAGE_ENC, PROP_MUX, + PROP_VIDEO_CONV, //options PROP_USE_VIDEO_TOGGLE, }; @@ -124,9 +146,9 @@ enum enum { SIGNAL_STREAM_BLOCK, - SIGNAL_STREAM_UNBLOCK, + SIGNAL_STREAM_UNBLOCK, SIGNAL_STREAM_PAUSE, - SIGNAL_STREAM_RESUME, + SIGNAL_STREAM_RESUME, LAST_SIGNAL }; #endif @@ -135,7 +157,8 @@ typedef enum { ENCODEBIN_ELEMENT_VENC, ENCODEBIN_ELEMENT_AENC, ENCODEBIN_ELEMENT_IENC, - ENCODEBIN_ELEMENT_MUX + ENCODEBIN_ELEMENT_MUX, + ENCODEBIN_ELEMENT_VIDEO_CONV }GstEncodeBinElement; typedef enum { @@ -388,7 +411,7 @@ GST_STATIC_CAPS ( \ "height = (int) [ 1, 2147483647 ]," \ "framerate = (fraction) [ 0/1, 2147483647/1 ]," \ "format = (fourcc) AYUV " \ -) +) static GstStaticPadTemplate encoder_bin_src_template = @@ -533,16 +556,16 @@ queue_overun_cb (GstElement * queue, GstEncodeBin *encodebin) GstClockTime now = gst_util_get_timestamp (); - g_object_get(G_OBJECT(queue), "current-level-bytes", &queue_size, + g_object_get(G_OBJECT(queue), "current-level-bytes", &queue_size, "current-level-buffers", &queue_bufnum, // "current-level-time", &queue_time, NULL); GST_ELEMENT_WARNING (encodebin, STREAM, TOO_LAZY, - ("[%" GST_TIME_FORMAT "][%s], [%u b], [%u]", - GST_TIME_ARGS(now), GST_OBJECT_NAME(queue), queue_size, queue_bufnum), (NULL)); + ("[%" GST_TIME_FORMAT "][%s], [%u b], [%u]", + GST_TIME_ARGS(now), GST_OBJECT_NAME(queue), queue_size, queue_bufnum), (NULL)); #else GST_ELEMENT_WARNING (encodebin, STREAM, TOO_LAZY, - ("%s overrun", GST_OBJECT_NAME(queue)), (NULL)); + ("%s overrun", GST_OBJECT_NAME(queue)), (NULL)); #endif } @@ -570,10 +593,13 @@ gst_encode_bin_get_property (GObject * object, break; case PROP_IENC_NAME: g_value_set_string (value, encodebin->ienc_name); - break; + break; case PROP_MUX_NAME: g_value_set_string (value, encodebin->mux_name); - break; + break; + case PROP_VCONV_NAME: + g_value_set_string (value, encodebin->vconv_name); + break; //caps case PROP_VCAPS: gst_value_set_caps (value, encodebin->vcaps); @@ -604,25 +630,25 @@ gst_encode_bin_get_property (GObject * object, // g_value_set_boolean (value, encodebin->use_venc_queue); if((encodebin->video_encode_queue == NULL) && (encodebin->profile == GST_ENCODE_BIN_PROFILE_AV)) { encodebin->video_encode_queue = gst_element_factory_make ("queue", "video_encode_queue"); - if(encodebin->video_encode_queue != NULL) + if(encodebin->video_encode_queue != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->video_encode_queue); } - g_value_set_object (value, encodebin->video_encode_queue); - break; + g_value_set_object (value, encodebin->video_encode_queue); + break; case PROP_AENC_QUEUE: // g_value_set_boolean (value, encodebin->use_aenc_queue); if((encodebin->audio_encode_queue == NULL) && (encodebin->profile <= GST_ENCODE_BIN_PROFILE_AUDIO)) { encodebin->audio_encode_queue = gst_element_factory_make ("queue", "audio_encode_queue"); - if(encodebin->audio_encode_queue != NULL) + if(encodebin->audio_encode_queue != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->audio_encode_queue); } - g_value_set_object (value, encodebin->audio_encode_queue); - break; + g_value_set_object (value, encodebin->audio_encode_queue); + break; //elements pointer case PROP_VIDEO_ENC: if((encodebin->video_encode == NULL) && (encodebin->profile == GST_ENCODE_BIN_PROFILE_AV)) { - encodebin->video_encode = gst_element_factory_make (encodebin->venc_name, "video_encode"); - if(encodebin->video_encode != NULL) + encodebin->video_encode = gst_element_factory_make (encodebin->venc_name, NULL); + if(encodebin->video_encode != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->video_encode); } g_value_set_object (value, encodebin->video_encode); @@ -630,7 +656,7 @@ gst_encode_bin_get_property (GObject * object, case PROP_AUDIO_ENC: if(encodebin->audio_encode == NULL && (encodebin->profile <= GST_ENCODE_BIN_PROFILE_AUDIO)) { encodebin->audio_encode = gst_element_factory_make (encodebin->aenc_name, "audio_encode"); - if(encodebin->audio_encode != NULL) + if(encodebin->audio_encode != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->audio_encode); } g_value_set_object (value, encodebin->audio_encode); @@ -638,19 +664,27 @@ gst_encode_bin_get_property (GObject * object, case PROP_IMAGE_ENC: if(encodebin->image_encode == NULL && (encodebin->profile == GST_ENCODE_BIN_PROFILE_IMAGE)) { encodebin->image_encode = gst_element_factory_make (encodebin->ienc_name, "image_encode"); - if(encodebin->image_encode != NULL) + if(encodebin->image_encode != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->image_encode); } g_value_set_object (value, encodebin->image_encode); - break; + break; case PROP_MUX: if(encodebin->mux == NULL && (encodebin->profile <= GST_ENCODE_BIN_PROFILE_AUDIO)) { encodebin->mux = gst_element_factory_make (encodebin->mux_name, "mux"); - if(encodebin->mux != NULL) + if(encodebin->mux != NULL) gst_bin_add(GST_BIN(encodebin), encodebin->mux); } g_value_set_object (value, encodebin->mux); break; + case PROP_VIDEO_CONV: + if(encodebin->color_space == NULL && (encodebin->profile != GST_ENCODE_BIN_PROFILE_AUDIO)) { + encodebin->color_space = gst_element_factory_make (encodebin->vconv_name, "video_convert"); + if(encodebin->color_space != NULL) + gst_bin_add(GST_BIN(encodebin), encodebin->color_space); + } + g_value_set_object (value, encodebin->color_space); + break; case PROP_USE_VIDEO_TOGGLE: g_value_set_boolean( value, encodebin->use_video_toggle ); break; @@ -676,7 +710,7 @@ gst_encode_bin_set_property (GObject * object, if(encodebin->profile != newprofile) { gst_encode_bin_change_profile(encodebin, newprofile); encodebin->profile = newprofile; - } + } */ break; case PROP_HIGH_SPEED: @@ -691,17 +725,17 @@ gst_encode_bin_set_property (GObject * object, new_name = g_value_get_string (value); if(encodebin->video_encode == NULL) { - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VENC, new_name)) + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VENC, new_name)) encodebin->venc_name = g_strdup (new_name); } else { if(strcmp (encodebin->venc_name, new_name)) { gst_encode_bin_remove_element(encodebin, encodebin->video_encode); - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VENC, new_name)) - encodebin->venc_name = g_strdup (new_name); + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VENC, new_name)) + encodebin->venc_name = g_strdup (new_name); } } break; - } + } case PROP_AENC_NAME: { const gchar *new_name; if(encodebin->profile > GST_ENCODE_BIN_PROFILE_AUDIO) { @@ -711,13 +745,13 @@ gst_encode_bin_set_property (GObject * object, new_name = g_value_get_string (value); if(encodebin->audio_encode == NULL) { - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_AENC, new_name)) + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_AENC, new_name)) encodebin->aenc_name = g_strdup (new_name); } else { if(strcmp (encodebin->aenc_name, new_name)) { gst_encode_bin_remove_element(encodebin, encodebin->audio_encode); - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_AENC, new_name)) - encodebin->aenc_name = g_strdup (new_name); + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_AENC, new_name)) + encodebin->aenc_name = g_strdup (new_name); } } break; @@ -731,16 +765,16 @@ gst_encode_bin_set_property (GObject * object, new_name = g_value_get_string (value); if(encodebin->image_encode == NULL) { - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_IENC, new_name)) + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_IENC, new_name)) encodebin->ienc_name = g_strdup (new_name); } else { if(strcmp (encodebin->ienc_name, new_name)) { gst_encode_bin_remove_element(encodebin, encodebin->image_encode); - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_IENC, new_name)) - encodebin->ienc_name = g_strdup (new_name); + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_IENC, new_name)) + encodebin->ienc_name = g_strdup (new_name); } } - break; + break; } case PROP_MUX_NAME: { const gchar *new_name; @@ -751,16 +785,36 @@ gst_encode_bin_set_property (GObject * object, new_name = g_value_get_string (value); if(encodebin->mux == NULL) { - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_MUX, new_name)) + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_MUX, new_name)) encodebin->mux_name = g_strdup (new_name); } else { if(strcmp (encodebin->mux_name, new_name)) { gst_encode_bin_remove_element(encodebin, encodebin->mux); - if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_MUX, new_name)) - encodebin->mux_name = g_strdup (new_name); + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_MUX, new_name)) + encodebin->mux_name = g_strdup (new_name); + } + } + break; + } + case PROP_VCONV_NAME: { + const gchar *new_name; + if (encodebin->profile == GST_ENCODE_BIN_PROFILE_AUDIO) { + GST_WARNING_OBJECT(encodebin, "Profile isn't match"); + break; + } + new_name = g_value_get_string(value); + + if (encodebin->color_space == NULL) { + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VIDEO_CONV, new_name)) + encodebin->vconv_name = g_strdup (new_name); + } else { + if(strcmp (encodebin->vconv_name, new_name)) { + gst_encode_bin_remove_element(encodebin, encodebin->color_space); + if(gst_encode_bin_add_element_by_name(encodebin, ENCODEBIN_ELEMENT_VIDEO_CONV, new_name)) + encodebin->vconv_name = g_strdup (new_name); } } - break; + break; } //caps case PROP_VCAPS: { @@ -828,23 +882,23 @@ gst_encode_bin_set_property (GObject * object, gboolean newval = g_value_get_boolean (value); if(encodebin->block != newval) { if(!gst_encode_bin_block(encodebin, newval)) { -#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE +#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE if(newval) { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_BLOCK], 0, FALSE); } else { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_UNBLOCK], 0, FALSE); } -#endif +#endif break; - } + } } -#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE +#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE if(newval) { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_BLOCK], 0, TRUE); } else { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_UNBLOCK], 0, TRUE); } -#endif +#endif break; } case PROP_PAUSE: { @@ -853,13 +907,13 @@ gst_encode_bin_set_property (GObject * object, if(!gst_encode_bin_pause(encodebin, newval)) break; } -#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE +#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE if(newval) { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_PAUSE], 0, TRUE); } else { g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_RESUME], 0, TRUE); } -#endif +#endif break; } case PROP_VENC_QUEUE: @@ -879,7 +933,7 @@ gst_encode_bin_set_property (GObject * object, } break; } - break; + break; case PROP_AENC_QUEUE: // encodebin->use_aenc_queue = g_value_get_boolean (value); { @@ -897,8 +951,8 @@ gst_encode_bin_set_property (GObject * object, } break; } - break; - case PROP_VIDEO_ENC: + break; + case PROP_VIDEO_ENC: { GstElement *newelement = g_value_get_object (value); if(encodebin->profile > GST_ENCODE_BIN_PROFILE_AV) { @@ -914,7 +968,7 @@ gst_encode_bin_set_property (GObject * object, } break; } - case PROP_AUDIO_ENC: + case PROP_AUDIO_ENC: { GstElement *newelement = g_value_get_object (value); if(encodebin->profile > GST_ENCODE_BIN_PROFILE_AUDIO) { @@ -960,6 +1014,21 @@ gst_encode_bin_set_property (GObject * object, } break; } + case PROP_VIDEO_CONV: { + GstElement *newelement = g_value_get_object (value); + if(encodebin->profile == GST_ENCODE_BIN_PROFILE_AUDIO) { + GST_WARNING_OBJECT(encodebin, "Profile isn't match, change profile first!"); + break; + } + if(newelement != NULL) { + gst_encode_bin_remove_element(encodebin, encodebin->color_space); + encodebin->color_space = newelement; + gst_object_ref (encodebin->color_space); + gst_object_sink (GST_OBJECT_CAST (encodebin->color_space)); + gst_bin_add(GST_BIN(encodebin), encodebin->color_space); + } + break; + } case PROP_USE_VIDEO_TOGGLE: encodebin->use_video_toggle = g_value_get_boolean( value ); break; @@ -987,12 +1056,12 @@ gst_encode_bin_request_new_pad (GstElement * element, g_return_val_if_fail (GST_IS_ENCODE_BIN (element), NULL); encodebin = GST_ENCODE_BIN (element); - + /* FIXME */ if (templ == gst_element_class_get_pad_template (klass, "audio")) { if (encodebin->profile <= GST_ENCODE_BIN_PROFILE_AUDIO) { gst_encode_bin_init_audio_elements(element, NULL); //?? - + if(encodebin->audio_sinkpad == NULL) { pad = gst_element_get_static_pad (encodebin->audio_queue, "sink"); @@ -1002,10 +1071,10 @@ gst_encode_bin_request_new_pad (GstElement * element, } else { - GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: audio pad is aleady existed, return existing audio pad\n"); + GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: audio pad is aleady existed, return existing audio pad\n"); return encodebin->audio_sinkpad; } - + gst_element_add_pad (element, encodebin->audio_sinkpad); gst_pad_set_setcaps_function (encodebin->audio_sinkpad, GST_DEBUG_FUNCPTR (gst_encode_bin_audsink_set_caps)); @@ -1019,16 +1088,16 @@ gst_encode_bin_request_new_pad (GstElement * element, if(encodebin->video_sinkpad == NULL) { pad = gst_element_get_static_pad (encodebin->video_queue, "sink"); - encodebin->video_sinkpad = gst_ghost_pad_new ("video", pad); + encodebin->video_sinkpad = gst_ghost_pad_new ("video", pad); gst_object_unref(pad); pad = NULL; } else { - GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: video pad is aleady existed, return existing video pad\n"); + GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: video pad is aleady existed, return existing video pad\n"); return encodebin->video_sinkpad; } - + gst_element_add_pad (element, encodebin->video_sinkpad); gst_pad_set_setcaps_function (encodebin->video_sinkpad, GST_DEBUG_FUNCPTR (gst_encode_bin_vidsink_set_caps)); @@ -1041,14 +1110,14 @@ gst_encode_bin_request_new_pad (GstElement * element, pad = gst_element_get_static_pad (encodebin->image_queue, "sink"); encodebin->image_sinkpad = gst_ghost_pad_new ("image", pad); gst_object_unref(pad); - pad = NULL; + pad = NULL; } else { - GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: image pad is aleady existed, return existing image pad\n"); + GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: image pad is aleady existed, return existing image pad\n"); return encodebin->image_sinkpad; } - + gst_element_add_pad (element, encodebin->image_sinkpad); gst_pad_set_setcaps_function (encodebin->image_sinkpad, GST_DEBUG_FUNCPTR (gst_encode_bin_imgsink_set_caps)); @@ -1058,33 +1127,32 @@ gst_encode_bin_request_new_pad (GstElement * element, } else { if (encodebin->profile == GST_ENCODE_BIN_PROFILE_IMAGE) { gst_encode_bin_init_image_elements(element, NULL); //?? - + if(encodebin->image_sinkpad == NULL) { pad = gst_element_get_static_pad (encodebin->image_queue, "sink"); encodebin->image_sinkpad = gst_ghost_pad_new ("image", pad); gst_object_unref(pad); - pad = NULL; + pad = NULL; } else { - GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: image pad is aleady existed, return existing image pad\n"); + GST_WARNING_OBJECT (GST_IS_ENCODE_BIN (element), "encodebin: image pad is aleady existed, return existing image pad\n"); return encodebin->image_sinkpad; } - + gst_element_add_pad (element, encodebin->image_sinkpad); gst_pad_set_setcaps_function (encodebin->image_sinkpad, GST_DEBUG_FUNCPTR (gst_encode_bin_imgsink_set_caps)); return encodebin->image_sinkpad; } else - return NULL; - } + return NULL; + } } static void gst_encode_bin_class_init (GstEncodeBinClass *klass) { - GObjectClass *gobject_klass; GstElementClass *gstelement_klass; GstBinClass *gstbin_klass; @@ -1092,12 +1160,12 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) gobject_klass = (GObjectClass *) klass; gstelement_klass = (GstElementClass *) klass; gstbin_klass = (GstBinClass *) klass; - + parent_class = g_type_class_peek_parent (klass); gobject_klass->get_property = gst_encode_bin_get_property; gobject_klass->set_property = gst_encode_bin_set_property; - gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_encode_bin_dispose); + gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_encode_bin_dispose); gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_encode_bin_finalize); @@ -1107,7 +1175,7 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_HIGH_SPEED, - g_param_spec_int ("high-speed-fps", "high speed rec. fps", "framerate for high speed recording", 0, G_MAXINT, + g_param_spec_int ("high-speed-fps", "high speed rec. fps", "framerate for high speed recording", 0, G_MAXINT, DEFAULT_PROP_HIGH_SPEED, G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_VENC_NAME, @@ -1126,6 +1194,10 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) g_param_spec_string ("mux-name", "muxer name", "the name of muxer to use", DEFAULT_PROP_MUX_NAME, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VCONV_NAME, + g_param_spec_string ("vconv-name", "Video converter name", "the name of video color converter to use", + DEFAULT_PROP_VCONV_NAME, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VCAPS, g_param_spec_boxed ("vcaps", "caps for video","caps for video recording", GST_TYPE_CAPS, G_PARAM_READWRITE)); @@ -1161,11 +1233,11 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) #if 0 g_object_class_install_property (gobject_klass, PROP_VENC_QUEUE, g_param_spec_boolean ("use-venc-queue", "use queue between venc and mux", - "add queue between venc and mux(only for custom optimization)", FALSE, G_PARAM_READWRITE)); + "add queue between venc and mux(only for custom optimization)", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_AENC_QUEUE, g_param_spec_boolean ("use-aenc-queue", "use queue between aenc and mux", - "add queue between aenc and mux(only for custom optimization)", FALSE, G_PARAM_READWRITE)); + "add queue between aenc and mux(only for custom optimization)", FALSE, G_PARAM_READWRITE)); #else g_object_class_install_property (gobject_klass, PROP_VENC_QUEUE, g_param_spec_object ("use-venc-queue", "Video Encoder queue", @@ -1198,6 +1270,11 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) "the muxer element to use", GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VIDEO_CONV, + g_param_spec_object ("video-convert", "Video converter", + "the video converter element to use", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_USE_VIDEO_TOGGLE, g_param_spec_boolean ("use-video-toggle", "Use video toggle", "Use video toggle while AV recording", TRUE, G_PARAM_READWRITE)); @@ -1205,23 +1282,23 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) #ifdef GST_ENCODE_BIN_SIGNAL_ENABLE gst_encode_bin_signals[SIGNAL_STREAM_BLOCK] = g_signal_new ("stream-block", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_block), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_block), NULL, NULL, gst_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); gst_encode_bin_signals[SIGNAL_STREAM_UNBLOCK] = g_signal_new ("stream-unblock", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_unblock), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_unblock), NULL, NULL, gst_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); gst_encode_bin_signals[SIGNAL_STREAM_PAUSE] = g_signal_new ("stream-pause", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_pause), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_pause), NULL, NULL, gst_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); gst_encode_bin_signals[SIGNAL_STREAM_RESUME] = g_signal_new ("stream-resume", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_resume), - NULL, NULL, gst_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstEncodeBinClass, stream_resume), + NULL, NULL, gst_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); #endif gst_element_class_add_pad_template (gstelement_klass, @@ -1237,7 +1314,7 @@ gst_encode_bin_class_init (GstEncodeBinClass *klass) gstelement_klass->request_new_pad = GST_DEBUG_FUNCPTR (gst_encode_bin_request_new_pad); - gstelement_klass->release_pad = + gstelement_klass->release_pad = GST_DEBUG_FUNCPTR (gst_encode_bin_release_pad); gstelement_klass->change_state = GST_DEBUG_FUNCPTR (gst_encode_bin_change_state); @@ -1248,17 +1325,17 @@ gst_encode_bin_init (GstEncodeBin *encodebin) { encodebin->mutex = g_mutex_new(); - if(encodebin->srcpad == NULL) { + if (encodebin->srcpad == NULL) { encodebin->srcpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC); gst_element_add_pad (GST_ELEMENT(encodebin), encodebin->srcpad); - } + } encodebin->video_sinkpad = NULL; encodebin->audio_sinkpad = NULL; encodebin->image_sinkpad = NULL; encodebin->mux_audio_sinkpad = NULL; encodebin->mux_video_sinkpad = NULL; - + encodebin->profile = DEFAULT_PROP_PROFILE; encodebin->fps = 0; encodebin->high_speed_fps = DEFAULT_PROP_HIGH_SPEED; @@ -1268,37 +1345,38 @@ gst_encode_bin_init (GstEncodeBin *encodebin) encodebin->auto_audio_resample = TRUE; encodebin->auto_color_space = TRUE; encodebin->block = FALSE; - encodebin->pause= FALSE; + encodebin->pause= FALSE; encodebin->use_video_toggle = TRUE; - encodebin->use_venc_queue= FALSE; - encodebin->use_aenc_queue= FALSE; + encodebin->use_venc_queue= FALSE; + encodebin->use_aenc_queue= FALSE; encodebin->venc_name = g_strdup(DEFAULT_PROP_VENC_NAME); encodebin->aenc_name = g_strdup(DEFAULT_PROP_AENC_NAME); - encodebin->ienc_name = g_strdup(DEFAULT_PROP_IENC_NAME); - encodebin->mux_name = g_strdup(DEFAULT_PROP_MUX_NAME); + encodebin->ienc_name = g_strdup(DEFAULT_PROP_IENC_NAME); + encodebin->mux_name = g_strdup(DEFAULT_PROP_MUX_NAME); + encodebin->vconv_name = g_strdup(DEFAULT_PROP_VCONV_NAME); encodebin->vcaps = gst_caps_new_any (); encodebin->acaps = gst_caps_new_any (); encodebin->icaps = gst_caps_new_any (); encodebin->audio_queue = NULL; - encodebin->video_queue = NULL; - encodebin->video_encode_queue = NULL; - encodebin->image_queue = NULL; + encodebin->video_queue = NULL; + encodebin->video_encode_queue = NULL; + encodebin->image_queue = NULL; encodebin->audio_encode = NULL; encodebin->video_encode = NULL; - encodebin->image_encode = NULL; + encodebin->image_encode = NULL; - encodebin->vcapsfilter = NULL; - encodebin->acapsfilter = NULL; - encodebin->icapsfilter = NULL; + encodebin->vcapsfilter = NULL; + encodebin->acapsfilter = NULL; + encodebin->icapsfilter = NULL; - encodebin->video_toggle = NULL; + encodebin->video_toggle = NULL; encodebin->image_toggle = NULL; - encodebin->color_space = NULL; - encodebin->audio_conv = NULL; + encodebin->color_space = NULL; + encodebin->audio_conv = NULL; encodebin->audio_sample = NULL; encodebin->mux = NULL; @@ -1310,11 +1388,11 @@ gst_encode_bin_init (GstEncodeBin *encodebin) encodebin->vsink_hs_probeid = 0; encodebin->asink_probeid = 0; encodebin->veque_sig_id = 0; - encodebin->aeque_sig_id = 0; + encodebin->aeque_sig_id = 0; } -static void -gst_encode_bin_dispose (GObject * object) +static void +gst_encode_bin_dispose (GObject * object) { GstEncodeBin *encodebin = GST_ENCODE_BIN (object); @@ -1322,7 +1400,7 @@ gst_encode_bin_dispose (GObject * object) encodebin->venc_name = NULL; g_free(encodebin->aenc_name); - encodebin->aenc_name = NULL; + encodebin->aenc_name = NULL; g_free(encodebin->ienc_name); encodebin->ienc_name = NULL; @@ -1330,22 +1408,20 @@ gst_encode_bin_dispose (GObject * object) g_free(encodebin->mux_name); encodebin->mux_name = NULL; + g_free(encodebin->vconv_name); + encodebin->vconv_name = NULL; gst_caps_replace (&encodebin->vcaps, NULL); gst_caps_replace (&encodebin->acaps, NULL); gst_caps_replace (&encodebin->icaps, NULL); - if(encodebin->srcpad != NULL) - { - gst_element_remove_pad (GST_ELEMENT(encodebin), encodebin->srcpad); + if (encodebin->srcpad != NULL) { + gst_element_remove_pad(GST_ELEMENT(encodebin), encodebin->srcpad); encodebin->srcpad = NULL; } - G_OBJECT_CLASS (parent_class)->dispose (object); - - encodebin->video_sinkpad = NULL; encodebin->audio_sinkpad = NULL; encodebin->image_sinkpad = NULL; @@ -1353,34 +1429,33 @@ gst_encode_bin_dispose (GObject * object) encodebin->mux_video_sinkpad = NULL; encodebin->audio_queue = NULL; - encodebin->video_queue = NULL; - encodebin->image_queue = NULL; + encodebin->video_queue = NULL; + encodebin->image_queue = NULL; encodebin->audio_encode = NULL; encodebin->video_encode = NULL; - encodebin->video_encode_queue = NULL; - encodebin->image_encode = NULL; + encodebin->video_encode_queue = NULL; + encodebin->image_encode = NULL; - encodebin->vcapsfilter = NULL; - encodebin->acapsfilter = NULL; - encodebin->icapsfilter = NULL; + encodebin->vcapsfilter = NULL; + encodebin->acapsfilter = NULL; + encodebin->icapsfilter = NULL; - encodebin->video_toggle = NULL; + encodebin->video_toggle = NULL; encodebin->image_toggle = NULL; - encodebin->color_space = NULL; - encodebin->audio_conv = NULL; + encodebin->color_space = NULL; + encodebin->audio_conv = NULL; encodebin->audio_sample = NULL; - - if(encodebin->mux && GST_IS_ELEMENT(encodebin->mux)) - { - int remain_count= 0 ; + + if (encodebin->mux && GST_IS_ELEMENT(encodebin->mux)) { + int remain_count= 0; remain_count = GST_OBJECT_REFCOUNT_VALUE(encodebin->mux); - while(remain_count) - { + while (remain_count) { gst_object_unref(encodebin->mux); remain_count--; - } + } } + encodebin->mux = NULL; } @@ -1427,8 +1502,8 @@ gst_encode_bin_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_PAUSED_TO_READY: break; case GST_STATE_CHANGE_READY_TO_NULL: - gst_encode_bin_unlink_elements(encode_bin); - break; + gst_encode_bin_unlink_elements(encode_bin); + break; default: break; } @@ -1452,9 +1527,9 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) { GstEncodeBin *encodebin = GST_ENCODE_BIN (element); GstPad *muxpad = NULL; - + if(!pad_compare_name(pad, "video")) { -#if 0 +#if 0 gst_encode_bin_remove_element(encodebin, encodebin->video_queue); encodebin->video_queue = NULL; gst_encode_bin_remove_element(encodebin, encodebin->video_toggle); @@ -1464,29 +1539,29 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) gst_encode_bin_remove_element(encodebin, encodebin->vcapsfilter); encodebin->vcapsfilter = NULL; gst_encode_bin_remove_element(encodebin, encodebin->video_encode_queue); - encodebin->video_encode_queue = NULL; + encodebin->video_encode_queue = NULL; gst_encode_bin_remove_element(encodebin, encodebin->video_encode); encodebin->video_encode = NULL; - gst_element_release_request_pad(encodebin->mux, encodebin->mux_video_sinkpad); + gst_element_release_request_pad(encodebin->mux, encodebin->mux_video_sinkpad); encodebin->mux_video_sinkpad = NULL; if(encodebin->mux_audio_sinkpad == NULL) { gst_encode_bin_remove_element(encodebin, encodebin->mux); encodebin->mux = NULL; } - else + else { encodebin->mux_audio_sinkpad = NULL; } -#endif +#endif if(encodebin->mux_video_sinkpad != NULL) - { + { gst_element_release_request_pad(encodebin->mux, encodebin->mux_video_sinkpad); encodebin->mux_video_sinkpad = NULL; } - + gst_pad_set_active (pad, FALSE); //?? gst_element_remove_pad(element, pad); encodebin->video_sinkpad = NULL; @@ -1501,7 +1576,7 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) gst_encode_bin_remove_element(encodebin, encodebin->acapsfilter); encodebin->acapsfilter = NULL; gst_encode_bin_remove_element(encodebin, encodebin->audio_encode_queue); - encodebin->audio_encode_queue = NULL; + encodebin->audio_encode_queue = NULL; gst_encode_bin_remove_element(encodebin, encodebin->audio_encode); encodebin->audio_encode = NULL; @@ -1513,11 +1588,11 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) gst_encode_bin_remove_element(encodebin, encodebin->mux); encodebin->mux = NULL; } - else + else { encodebin->mux_video_sinkpad = NULL; } -#endif +#endif if(encodebin->mux_audio_sinkpad != NULL) { gst_element_release_request_pad(encodebin->mux, encodebin->mux_audio_sinkpad); @@ -1525,7 +1600,7 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) encodebin->mux_audio_sinkpad = NULL; } - gst_pad_set_active (pad, FALSE); //?? + gst_pad_set_active (pad, FALSE); //?? gst_element_remove_pad(element, pad); encodebin->audio_sinkpad = NULL; } else { @@ -1540,8 +1615,8 @@ gst_encode_bin_release_pad (GstElement * element, GstPad * pad) encodebin->icapsfilter = NULL; gst_encode_bin_remove_element(encodebin, encodebin->image_encode); encodebin->image_encode = NULL; -#endif - gst_pad_set_active (pad, FALSE); //?? +#endif + gst_pad_set_active (pad, FALSE); //?? gst_element_remove_pad(element, pad); encodebin->image_sinkpad = NULL; } @@ -1559,60 +1634,74 @@ pad_compare_name (GstPad * pad1, const gchar * name) return result; } -static gboolean +static gboolean gst_encode_bin_add_element_by_name (GstEncodeBin *encodebin, GstEncodeBinElement type, const gchar *name) { switch(type) { - case ENCODEBIN_ELEMENT_VENC : - encodebin->video_encode = gst_element_factory_make (name, "video_encode"); + case ENCODEBIN_ELEMENT_VENC: + encodebin->video_encode = gst_element_factory_make (name, NULL); if(encodebin->video_encode != NULL) { gst_bin_add(GST_BIN(encodebin), encodebin->video_encode); g_free(encodebin->venc_name); + encodebin->venc_name = NULL; } else { goto element_make_fail; } break; - case ENCODEBIN_ELEMENT_AENC : - encodebin->audio_encode = gst_element_factory_make (name, "audio_encode"); + case ENCODEBIN_ELEMENT_AENC: + encodebin->audio_encode = gst_element_factory_make (name, "audio_encode"); if(encodebin->audio_encode != NULL) { gst_bin_add(GST_BIN(encodebin), encodebin->audio_encode); g_free(encodebin->aenc_name); + encodebin->aenc_name = NULL; } else { goto element_make_fail; - } + } break; - case ENCODEBIN_ELEMENT_IENC : - encodebin->image_encode = gst_element_factory_make (name, "image_encode"); + case ENCODEBIN_ELEMENT_IENC: + encodebin->image_encode = gst_element_factory_make (name, "image_encode"); if(encodebin->image_encode != NULL) { gst_bin_add(GST_BIN(encodebin), encodebin->image_encode); g_free(encodebin->ienc_name); + encodebin->ienc_name = NULL; } else { goto element_make_fail; - } + } break; - case ENCODEBIN_ELEMENT_MUX : - encodebin->mux = gst_element_factory_make (name, "mux"); + case ENCODEBIN_ELEMENT_MUX: + encodebin->mux = gst_element_factory_make (name, "mux"); if(encodebin->mux != NULL) { gst_bin_add(GST_BIN(encodebin), encodebin->mux); g_free(encodebin->mux_name); + encodebin->mux_name = NULL; } else { goto element_make_fail; - } + } break; - default : + case ENCODEBIN_ELEMENT_VIDEO_CONV: + encodebin->color_space = gst_element_factory_make(name, "video_convert"); + if (encodebin->color_space != NULL) { + gst_bin_add(GST_BIN(encodebin), encodebin->color_space); + g_free(encodebin->vconv_name); + encodebin->vconv_name = NULL; + } else { + goto element_make_fail; + } + break; + default: GST_WARNING_OBJECT(encodebin, "Invalid element type = %d", type); - break; + break; } return TRUE; - + element_make_fail: GST_WARNING_OBJECT(encodebin, "no such element factory \"%s\"!", name); return FALSE; } #if 0 //disable unused function -static gboolean +static gboolean gst_encode_bin_change_profile(GstEncodeBin *encodebin, gboolean newprofile) { @@ -1658,7 +1747,7 @@ gst_encode_bin_change_profile(GstEncodeBin *encodebin, gboolean newprofile) } -static void +static void gst_encode_bin_replace_element (GstEncodeBin *encodebin, gint type, GstElement * newelement) { if(newelement == NULL) { @@ -1679,7 +1768,7 @@ gst_encode_bin_replace_element (GstEncodeBin *encodebin, gint type, GstElement * gst_object_ref (encodebin->audio_encode); gst_object_sink (GST_OBJECT_CAST (encodebin->audio_encode)); gst_bin_add(GST_BIN(encodebin), encodebin->audio_encode); - break; + break; case PROP_IMAGE_ENC: gst_encode_bin_remove_element(encodebin, encodebin->image_encode); encodebin->image_encode = newelement; @@ -1701,11 +1790,11 @@ gst_encode_bin_replace_element (GstEncodeBin *encodebin, gint type, GstElement * } } -static gboolean +static gboolean gst_encode_bin_replace_element_by_name(GstEncodeBin *encodebin, GstEncodeBinElement type, const gchar *name) { GstPad *sink1, *sink2, *src, *peersink1, *peersink2, *peersrc; - + switch(type) { case ENCODEBIN_ELEMENT_VENC: if(encodebin->video_encode == NULL) { @@ -1731,7 +1820,7 @@ gst_encode_bin_replace_element_by_name(GstEncodeBin *encodebin, GstEncodeBinElem } } } - + if(gst_encode_bin_remove_element(encodebin, encodebin->video_encode)) { if(encodebin->video_encode = gst_element_factory_make (name, "video_encode") != NULL) { gst_bin_add(GST_BIN(encodebin), encodebin->video_encode); @@ -1740,7 +1829,7 @@ gst_encode_bin_replace_element_by_name(GstEncodeBin *encodebin, GstEncodeBinElem goto link_fail; } } - + if(peersrc != NULL) { if(!gst_pad_link(gst_element_get_pad(encodebin->video_encode, "src"), peersrc)) { goto link_fail; @@ -1748,13 +1837,13 @@ gst_encode_bin_replace_element_by_name(GstEncodeBin *encodebin, GstEncodeBinElem } } else { GST_ERROR_OBJECT(encodebin, "gst_encode_bin_replace_element_by_name() new element[%d] make fail\n", type); - return FALSE; + return FALSE; } } else { GST_ERROR_OBJECT(encodebin, "gst_encode_bin_replace_element_by_name() old element[%d] remove fail\n", type); return FALSE; - } - } + } + } break; case ENCODEBIN_ELEMENT_AENC: break; @@ -1769,38 +1858,38 @@ gst_encode_bin_replace_element_by_name(GstEncodeBin *encodebin, GstEncodeBinElem gst_object_unref(sink1); gst_object_unref(sink2); gst_object_unref(src); - gst_object_unref(peersink1); - gst_object_unref(peersink2); - gst_object_unref(peersrc); + gst_object_unref(peersink1); + gst_object_unref(peersink2); + gst_object_unref(peersrc); return TRUE; unlink_fail: gst_object_unref(sink1); gst_object_unref(sink2); gst_object_unref(src); - gst_object_unref(peersink1); - gst_object_unref(peersink2); - gst_object_unref(peersrc); + gst_object_unref(peersink1); + gst_object_unref(peersink2); + gst_object_unref(peersrc); GST_ERROR_OBJECT(encodebin, "gst_encode_bin_replace_element_by_name() old element[%d] unlink fail\n", type); return FALSE; - + link_fail: gst_object_unref(sink1); gst_object_unref(sink2); gst_object_unref(src); - gst_object_unref(peersink1); - gst_object_unref(peersink2); - gst_object_unref(peersrc); + gst_object_unref(peersink1); + gst_object_unref(peersink2); + gst_object_unref(peersrc); GST_ERROR_OBJECT(encodebin, "gst_encode_bin_replace_element_by_name() new element[%d] link fail\n", type); - return FALSE; + return FALSE; } -static gboolean +static gboolean gst_encode_bin_replace_element_by_object(GstEncodeBin *encodebin, GstEncodeBinElement type, GstElement * element) { GstPad *sink1, *sink2, *src, *peersink1, *peersink2, *peersrc; - + switch(type) case ENCODEBIN_ELEMENT_VENC: if(encodebin->video_encode == NULL) { @@ -1819,15 +1908,17 @@ gst_encode_bin_replace_element_by_object(GstEncodeBin *encodebin, GstEncodeBinEl } #endif //disable unused function -static gboolean +static gboolean gst_encode_bin_remove_element (GstEncodeBin *encodebin, GstElement * element) { GstObject *parent; gchar *ename = NULL; - GST_DEBUG_OBJECT (encodebin, "gst_encode_bin_remove_element"); + GST_INFO_OBJECT (encodebin, "gst_encode_bin_remove_element"); - if(element == NULL) + if (element == NULL) { + GST_INFO_OBJECT (encodebin, "element is already NULL"); return TRUE; + } gst_element_set_state (element, GST_STATE_NULL); parent = gst_element_get_parent (element); @@ -1839,37 +1930,46 @@ gst_encode_bin_remove_element (GstEncodeBin *encodebin, GstElement * element) GST_ERROR_OBJECT (encodebin, "gst_encode_bin_remove_element() [%s] remove fail", ename); g_free (ename); return FALSE; - } else - gst_object_unref (parent); + } else { + gst_object_unref(parent); + } } else { gst_object_unref(element); } + return TRUE; } -static gboolean +static gboolean gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? { GstPad *srcpad = NULL, *sinkpad = NULL; switch(encodebin->profile) { case GST_ENCODE_BIN_PROFILE_AV : - if(!gst_caps_is_any(encodebin->vcaps)) - { + if (!gst_caps_is_any(encodebin->vcaps)) { + gchar *caps_str = NULL; + caps_str = gst_caps_to_string(encodebin->vcaps); + if (caps_str) { + GST_INFO_OBJECT(encodebin, "vconv caps [%s]", caps_str); + g_free(caps_str); + caps_str = NULL; + } + g_object_set(encodebin->vcapsfilter, "caps", encodebin->vcaps, NULL); } - + if (encodebin->auto_color_space) { if(encodebin->color_space == NULL) { - encodebin->color_space = gst_element_factory_make ("ffmpegcolorspace","color_space"); - gst_bin_add (GST_BIN (encodebin), encodebin->color_space); + encodebin->color_space = gst_element_factory_make (encodebin->vconv_name, "video_convert"); + gst_bin_add (GST_BIN (encodebin), encodebin->color_space); } - - srcpad = gst_element_get_static_pad(encodebin->video_queue, "src"); + + srcpad = gst_element_get_static_pad(encodebin->video_queue, "src"); if( encodebin->video_toggle ) { sinkpad = gst_element_get_static_pad(encodebin->video_toggle, "sink"); _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); - + srcpad = gst_element_get_static_pad(encodebin->video_toggle, "src"); } sinkpad = gst_element_get_static_pad(encodebin->color_space, "sink"); @@ -1877,100 +1977,100 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->color_space, "src"); sinkpad = gst_element_get_static_pad(encodebin->vcapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); srcpad = gst_element_get_static_pad(encodebin->vcapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); #if 0 if(encodebin->use_venc_queue) { if(encodebin->video_encode_queue == NULL) { encodebin->video_encode_queue = gst_element_factory_make ("queue","video_encode_queue"); gst_bin_add (GST_BIN (encodebin), encodebin->video_encode_queue); - + ENCODER_QUEUE_SET(encodebin->video_encode_queue, 0, 0, VIDEO_ENC_QUE_TIME); - encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", + encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", G_CALLBACK(queue_overun_cb), encodebin); - - } + + } srcpad = gst_element_get_static_pad(encodebin->video_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } #else if(encodebin->video_encode_queue) { ENCODER_QUEUE_SET(encodebin->video_encode_queue, 0, 0, VIDEO_ENC_QUE_TIME); - encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", + encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", G_CALLBACK(queue_overun_cb), encodebin); #if 0 - g_object_set(G_OBJECT(encodebin->video_queue), - "max-size-bytes", (guint)0, - "max-size-buffers", (guint)1, - "max-size-time", (guint64)0, - NULL); + g_object_set(G_OBJECT(encodebin->video_queue), + "max-size-bytes", (guint)0, + "max-size-buffers", (guint)1, + "max-size-time", (guint64)0, + NULL); #endif srcpad = gst_element_get_static_pad(encodebin->video_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } #endif - + } else { srcpad = gst_element_get_static_pad(encodebin->video_queue, "src"); if( encodebin->video_toggle ) { sinkpad = gst_element_get_static_pad(encodebin->video_toggle, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); srcpad = gst_element_get_static_pad(encodebin->video_toggle, "src"); } sinkpad = gst_element_get_static_pad(encodebin->vcapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); srcpad = gst_element_get_static_pad(encodebin->vcapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); #if 0 if(encodebin->use_venc_queue) { if(encodebin->video_encode_queue == NULL) { encodebin->video_encode_queue = gst_element_factory_make ("queue","video_encode_queue"); gst_bin_add (GST_BIN (encodebin), encodebin->video_encode_queue); - + ENCODER_QUEUE_SET(encodebin->video_encode_queue, 0, 0, VIDEO_ENC_QUE_TIME); - encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); } - + srcpad = gst_element_get_static_pad(encodebin->video_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode_queue, "sink"); _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); - - } + + } #else if(encodebin->video_encode_queue) { ENCODER_QUEUE_SET(encodebin->video_encode_queue, 0, 0, VIDEO_ENC_QUE_TIME); - encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", + encodebin->veque_sig_id = g_signal_connect( G_OBJECT(encodebin->video_encode_queue), "overrun", G_CALLBACK(queue_overun_cb), encodebin); -#if 0 - g_object_set(G_OBJECT(encodebin->video_queue), - "max-size-bytes", (guint)0, - "max-size-buffers", (guint)1, - "max-size-time", (guint64)0, - NULL); +#if 0 + g_object_set(G_OBJECT(encodebin->video_queue), + "max-size-bytes", (guint)0, + "max-size-buffers", (guint)1, + "max-size-time", (guint64)0, + NULL); #endif srcpad = gst_element_get_static_pad(encodebin->video_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->video_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } -#endif - +#endif + } // gst_element_get_request_pad (encodebin->mux, "video_%d"); @@ -1979,45 +2079,45 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? { srcpad = gst_element_get_static_pad(encodebin->video_encode_queue, "src"); sinkpad = encodebin->mux_video_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_VIDEO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } #else if(encodebin->video_encode_queue) { srcpad = gst_element_get_static_pad(encodebin->video_encode_queue, "src"); sinkpad = encodebin->mux_video_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_VIDEO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } #endif else { srcpad = gst_element_get_static_pad(encodebin->video_encode, "src"); sinkpad = encodebin->mux_video_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_VIDEO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, video_link_fail); } srcpad = gst_element_get_static_pad(encodebin->mux, "src"); if(gst_ghost_pad_get_target(GST_GHOST_PAD (encodebin->srcpad)) != srcpad) gst_ghost_pad_set_target(GST_GHOST_PAD (encodebin->srcpad), srcpad); gst_object_unref(srcpad); - srcpad = NULL; - + srcpad = NULL; + /* For pause/resume control */ // encodebin->vsink_probeid = gst_pad_add_data_probe (gst_element_get_static_pad (encodebin->video_queue, "sink"), sinkpad = gst_element_get_static_pad (encodebin->video_queue, "sink"); encodebin->vsink_probeid = gst_pad_add_buffer_probe (sinkpad, G_CALLBACK (gst_encode_bin_video_probe), encodebin); gst_object_unref(sinkpad); sinkpad = NULL; - + if(encodebin->high_speed_fps > DEFAULT_PROP_HIGH_SPEED) { // encodebin->vsink_hs_probeid = gst_pad_add_data_probe (gst_element_get_static_pad (encodebin->video_encode, "sink"), sinkpad = gst_element_get_static_pad (encodebin->video_encode, "sink"); encodebin->vsink_hs_probeid = gst_pad_add_buffer_probe (sinkpad, G_CALLBACK (gst_encode_bin_video_probe_hs), encodebin); gst_object_unref(sinkpad); - sinkpad = NULL; - } - + sinkpad = NULL; + } + if(encodebin->audio_queue == NULL) { GST_WARNING_OBJECT(encodebin, "Audio pad isn't requested, recording video only mode"); @@ -2025,7 +2125,7 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? } case GST_ENCODE_BIN_PROFILE_AUDIO : if(!gst_caps_is_any(encodebin->acaps)) - { + { g_object_set(encodebin->acapsfilter, "caps", encodebin->acaps, NULL); } if (encodebin->auto_audio_convert ||encodebin->auto_audio_resample) { @@ -2044,42 +2144,42 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->acapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); -#if 0 + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); +#if 0 if(encodebin->use_aenc_queue) { if(encodebin->audio_encode_queue == NULL) { encodebin->audio_encode_queue = gst_element_factory_make ("queue","audio_encode_queue"); gst_bin_add (GST_BIN (encodebin), encodebin->audio_encode_queue); - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); - } + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); + } srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); - } + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + } #else if(encodebin->audio_encode_queue) { - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } #endif - + } else if (!encodebin->auto_audio_resample) { if (encodebin->audio_conv == NULL) { encodebin->audio_conv = gst_element_factory_make ("audioconvert","audio_conv"); gst_bin_add (GST_BIN (encodebin), encodebin->audio_conv); - } + } srcpad = gst_element_get_static_pad(encodebin->audio_queue, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_conv, "sink"); @@ -2091,8 +2191,8 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->acapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); -#if 0 + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); +#if 0 if(encodebin->use_aenc_queue) { if(encodebin->audio_encode_queue == NULL) { @@ -2100,29 +2200,29 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? gst_bin_add (GST_BIN (encodebin), encodebin->audio_encode_queue); ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); } srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); - - } + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + + } #else if(encodebin->audio_encode_queue) { - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } -#endif - +#endif + } else { if(encodebin->audio_sample == NULL) { encodebin->audio_sample = gst_element_factory_make ("audioresample","audio_sample"); @@ -2135,7 +2235,7 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->audio_queue, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_conv, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); srcpad = gst_element_get_static_pad(encodebin->audio_conv, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_sample, "sink"); @@ -2143,89 +2243,89 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->audio_sample, "src"); sinkpad = gst_element_get_static_pad(encodebin->acapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); srcpad = gst_element_get_static_pad(encodebin->acapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); -#if 0 + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); +#if 0 if(encodebin->use_aenc_queue) { if(encodebin->audio_encode_queue == NULL) { encodebin->audio_encode_queue = gst_element_factory_make ("queue","audio_encode_queue"); gst_bin_add (GST_BIN (encodebin), encodebin->audio_encode_queue); - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); - } + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); + } srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); - - } + + } #else if(encodebin->audio_encode_queue) { - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } -#endif - +#endif + } }else { srcpad = gst_element_get_static_pad(encodebin->audio_queue, "src"); sinkpad = gst_element_get_static_pad(encodebin->acapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); srcpad = gst_element_get_static_pad(encodebin->acapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); #if 0 if(encodebin->use_aenc_queue) { if(encodebin->audio_encode_queue == NULL) { encodebin->audio_encode_queue = gst_element_factory_make ("queue","audio_encode_queue"); gst_bin_add (GST_BIN (encodebin), encodebin->audio_encode_queue); - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); - } + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); + } srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); - } + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + } #else if(encodebin->audio_encode_queue) { - + ENCODER_QUEUE_SET(encodebin->audio_encode_queue, 0, 0, AUDIO_ENC_QUE_TIME); - encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", - G_CALLBACK(queue_overun_cb), encodebin); + encodebin->aeque_sig_id = g_signal_connect( G_OBJECT(encodebin->audio_encode_queue), "overrun", + G_CALLBACK(queue_overun_cb), encodebin); srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } -#endif - +#endif + } -#if 0 +#if 0 if(encodebin->use_aenc_queue) { srcpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "src"); sinkpad = encodebin->mux_audio_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_AUDIO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } #else @@ -2234,16 +2334,16 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? { srcpad = gst_element_get_static_pad(encodebin->audio_encode_queue, "src"); sinkpad = encodebin->mux_audio_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_AUDIO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); } #endif else - { + { srcpad = gst_element_get_static_pad(encodebin->audio_encode, "src"); sinkpad = encodebin->mux_audio_sinkpad = gst_encode_bin_get_mux_sink_pad(encodebin->mux, ENCODEBIN_MUX_AUDIO_SINK); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); - + _GST_PAD_LINK_UNREF(srcpad, sinkpad, audio_link_fail); + } srcpad = gst_element_get_static_pad(encodebin->mux, "src"); @@ -2261,10 +2361,10 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? break; case GST_ENCODE_BIN_PROFILE_IMAGE : if(!gst_caps_is_any(encodebin->icaps)) - { + { g_object_set(encodebin->icapsfilter, "caps", encodebin->icaps, NULL); } - + if (encodebin->auto_color_space) { if(encodebin->color_space == NULL) { encodebin->color_space = gst_element_factory_make ("ffmpegcolorspace","color_space"); @@ -2277,30 +2377,30 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? srcpad = gst_element_get_static_pad(encodebin->image_toggle, "src"); sinkpad = gst_element_get_static_pad(encodebin->color_space, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); srcpad = gst_element_get_static_pad(encodebin->color_space, "src"); sinkpad = gst_element_get_static_pad(encodebin->icapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); srcpad = gst_element_get_static_pad(encodebin->icapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->image_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); } else { - + srcpad = gst_element_get_static_pad(encodebin->image_queue, "src"); sinkpad = gst_element_get_static_pad(encodebin->image_toggle, "sink"); _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); srcpad = gst_element_get_static_pad(encodebin->image_toggle, "src"); sinkpad = gst_element_get_static_pad(encodebin->icapsfilter, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); srcpad = gst_element_get_static_pad(encodebin->icapsfilter, "src"); sinkpad = gst_element_get_static_pad(encodebin->image_encode, "sink"); - _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); + _GST_PAD_LINK_UNREF(srcpad, sinkpad, image_link_fail); } srcpad = gst_element_get_static_pad (encodebin->image_encode, "src"); @@ -2314,7 +2414,7 @@ gst_encode_bin_link_elements (GstEncodeBin *encodebin) // need to return ???? return FALSE; break; } -// gst_pad_set_active(encodebin->srcpad, TRUE); +// gst_pad_set_active(encodebin->srcpad, TRUE); return TRUE; video_link_fail: @@ -2322,78 +2422,71 @@ video_link_fail: gst_encode_bin_remove_element(encodebin, encodebin->color_space); GST_WARNING_OBJECT(encodebin, "encodebin link video elements fail"); return FALSE; - + audio_link_fail: - // remove element - gst_encode_bin_remove_element(encodebin, encodebin->audio_conv); - gst_encode_bin_remove_element(encodebin, encodebin->audio_sample); + // remove element + gst_encode_bin_remove_element(encodebin, encodebin->audio_conv); + gst_encode_bin_remove_element(encodebin, encodebin->audio_sample); GST_WARNING_OBJECT(encodebin, "encodebin link audio elements fail"); return FALSE; image_link_fail: - // remove element - gst_encode_bin_remove_element(encodebin, encodebin->color_space); + // remove element + gst_encode_bin_remove_element(encodebin, encodebin->color_space); GST_WARNING_OBJECT(encodebin, "encodebin link image elements fail"); return FALSE; - + } -static gboolean +static gboolean gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) { GstPad *pad = NULL, *muxpad = NULL; - + switch(encodebin->profile) { case GST_ENCODE_BIN_PROFILE_AV : if (encodebin->auto_color_space) { - if( encodebin->video_toggle ) - { - gst_element_unlink_many ( + if (encodebin->video_toggle) { + gst_element_unlink_many( encodebin->video_queue, encodebin->video_toggle, encodebin->color_space, - encodebin->vcapsfilter, + encodebin->vcapsfilter, encodebin->video_encode, - //encodebin->video_encode_queue, + //encodebin->video_encode_queue, NULL); - } - else - { + } else { gst_element_unlink_many( encodebin->video_queue, encodebin->color_space, - encodebin->vcapsfilter, + encodebin->vcapsfilter, encodebin->video_encode, - //encodebin->video_encode_queue, + //encodebin->video_encode_queue, NULL); } - } - else { - if( encodebin->video_toggle ) - { - gst_element_unlink_many ( + } else { + if (encodebin->video_toggle) { + gst_element_unlink_many( encodebin->video_queue, encodebin->video_toggle, - encodebin->vcapsfilter, + encodebin->vcapsfilter, encodebin->video_encode, - //encodebin->video_encode_queue, + //encodebin->video_encode_queue, NULL); - } - else - { + } else { gst_element_unlink_many( encodebin->video_queue, - encodebin->vcapsfilter, + encodebin->vcapsfilter, encodebin->video_encode, - //encodebin->video_encode_queue, + //encodebin->video_encode_queue, NULL); } } if(encodebin->mux_video_sinkpad != NULL) { -#if 0 +#if 0 if(encodebin->use_venc_queue) { gst_element_unlink(encodebin->video_encode, encodebin->video_encode_queue); @@ -2443,9 +2536,9 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) gst_pad_remove_buffer_probe(pad, encodebin->vsink_probeid); encodebin->vsink_probeid = 0; gst_object_unref(pad); - pad = NULL; + pad = NULL; } - + if(encodebin->vsink_hs_probeid) { @@ -2468,22 +2561,22 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) encodebin->audio_sample, encodebin->acapsfilter, encodebin->audio_encode, - NULL); + NULL); } else if (!encodebin->auto_audio_resample) { gst_element_unlink_many ( encodebin->audio_queue, - encodebin->audio_conv, + encodebin->audio_conv, encodebin->acapsfilter, encodebin->audio_encode, - NULL); + NULL); } else { gst_element_unlink_many ( encodebin->audio_queue, encodebin->audio_conv, - encodebin->audio_sample, + encodebin->audio_sample, encodebin->acapsfilter, encodebin->audio_encode, - NULL); + NULL); } } else { @@ -2491,12 +2584,12 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) encodebin->audio_queue, encodebin->acapsfilter, encodebin->audio_encode, - NULL); + NULL); } if(encodebin->mux_audio_sinkpad != NULL) { -#if 0 +#if 0 if(encodebin->use_aenc_queue) { gst_element_unlink(encodebin->audio_encode, encodebin->audio_encode_queue); @@ -2504,12 +2597,12 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) pad = gst_element_get_static_pad (encodebin->audio_encode_queue, "src"); gst_pad_unlink(pad, muxpad); gst_object_unref(pad); - pad = NULL; + pad = NULL; if ( g_signal_handler_is_connected ( encodebin->audio_encode_queue, encodebin->veque_sig_id) ) { g_signal_handler_disconnect ( encodebin->audio_encode_queue, encodebin->veque_sig_id ); - } + } } #else if(encodebin->audio_encode_queue) @@ -2519,12 +2612,12 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) pad = gst_element_get_static_pad (encodebin->audio_encode_queue, "src"); gst_pad_unlink(pad, encodebin->mux_audio_sinkpad); gst_object_unref(pad); - pad = NULL; + pad = NULL; if ( g_signal_handler_is_connected ( encodebin->audio_encode_queue, encodebin->veque_sig_id) ) { g_signal_handler_disconnect ( encodebin->audio_encode_queue, encodebin->veque_sig_id ); - } + } } #endif else @@ -2533,7 +2626,7 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) gst_pad_unlink(pad, encodebin->mux_audio_sinkpad); gst_object_unref(pad); pad = NULL; - } + } gst_element_release_request_pad(encodebin->mux, encodebin->mux_audio_sinkpad); // gst_object_unref(encodebin->mux_audio_sinkpad); //*** @@ -2542,7 +2635,7 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) if(encodebin->asink_probeid) { - pad = gst_element_get_static_pad (encodebin->audio_queue, "sink"); + pad = gst_element_get_static_pad (encodebin->audio_queue, "sink"); gst_pad_remove_buffer_probe(pad, encodebin->asink_probeid); encodebin->asink_probeid =0; gst_object_unref(pad); @@ -2556,17 +2649,17 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) encodebin->image_queue, encodebin->image_toggle, encodebin->color_space, - encodebin->icapsfilter, + encodebin->icapsfilter, encodebin->image_encode, NULL); } else { gst_element_unlink_many ( encodebin->image_queue, - encodebin->image_toggle, - encodebin->icapsfilter, + encodebin->image_toggle, + encodebin->icapsfilter, encodebin->image_encode, - NULL); + NULL); } break; default: @@ -2574,12 +2667,12 @@ gst_encode_bin_unlink_elements (GstEncodeBin *encodebin) return FALSE; break; } - // gst_pad_set_active(encodebin->srcpad, TRUE); + // gst_pad_set_active(encodebin->srcpad, TRUE); return TRUE; } -static gboolean +static gboolean gst_encode_bin_init_video_elements (GstElement *element, gpointer user_data) { GstEncodeBin *encodebin = GST_ENCODE_BIN (element); @@ -2609,18 +2702,18 @@ gst_encode_bin_init_video_elements (GstElement *element, gpointer user_data) if(encodebin->vcapsfilter == NULL) { encodebin->vcapsfilter = gst_element_factory_make ("capsfilter","vcapsfilter"); gst_bin_add (GST_BIN (element), encodebin->vcapsfilter); - } + } #if 0 encodebin->vcaps = gst_caps_new_simple("video/x-raw-yuv", "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'), "width", G_TYPE_INT, 320, "height", G_TYPE_INT, 240, "framerate", GST_TYPE_FRACTION, 30, 1, - NULL); + NULL); #endif if(encodebin->video_encode == NULL) { - encodebin->video_encode = gst_element_factory_make (encodebin->venc_name, "video_encode"); + encodebin->video_encode = gst_element_factory_make (encodebin->venc_name, NULL); gst_bin_add (GST_BIN (element), encodebin->video_encode); } @@ -2638,33 +2731,33 @@ gst_encode_bin_init_video_elements (GstElement *element, gpointer user_data) gst_bin_add (GST_BIN (element), encodebin->mux); } - if (!encodebin->video_encode -// || !encodebin->video_encode_queue - || !encodebin->mux - || !encodebin->video_queue + if (!encodebin->video_encode +// || !encodebin->video_encode_queue + || !encodebin->mux + || !encodebin->video_queue || !encodebin->vcapsfilter || !encodebin->srcpad ) { GST_ERROR_OBJECT(encodebin, "Faild create element \n"); return FALSE; } - + if( encodebin->use_video_toggle && !encodebin->video_toggle ) { GST_ERROR_OBJECT(encodebin, "Faild create video toggle element \n"); return FALSE; } -#if 0 +#if 0 if (encodebin->auto_color_space && (encodebin->color_space == NULL)) { encodebin->color_space = gst_element_factory_make ("ffmpegcolorspace","color_space"); gst_bin_add (GST_BIN (element), encodebin->color_space); - } -#endif + } +#endif return TRUE; } -static gboolean +static gboolean gst_encode_bin_init_audio_elements (GstElement *element, gpointer user_data) { GstEncodeBin *encodebin = GST_ENCODE_BIN (element); @@ -2680,7 +2773,7 @@ gst_encode_bin_init_audio_elements (GstElement *element, gpointer user_data) if(encodebin->acapsfilter == NULL) { encodebin->acapsfilter = gst_element_factory_make ("capsfilter","acapsfilter"); gst_bin_add (GST_BIN (element), encodebin->acapsfilter); - } + } #if 0 encodebin->acaps = gst_caps_new_simple("audio/x-raw-int", "rate", G_TYPE_INT, 8000, @@ -2699,10 +2792,10 @@ encodebin->acaps = gst_caps_new_simple("audio/x-raw-int", gst_bin_add (GST_BIN (element), encodebin->mux); } - if (!encodebin->audio_encode + if (!encodebin->audio_encode || !encodebin->audio_queue || !encodebin->mux - || !encodebin->acapsfilter + || !encodebin->acapsfilter || !encodebin->srcpad ) { GST_ERROR_OBJECT(encodebin, "Faild create element \n"); @@ -2712,18 +2805,18 @@ encodebin->acaps = gst_caps_new_simple("audio/x-raw-int", if (encodebin->auto_audio_convert && (encodebin->audio_conv == NULL)) { encodebin->audio_conv = gst_element_factory_make ("audioconvert","audio_conv"); gst_bin_add (GST_BIN (element), encodebin->audio_conv); - } + } if (encodebin->auto_audio_resample && (encodebin->audio_sample == NULL)) { encodebin->audio_sample = gst_element_factory_make ("audioresample","audio_sample"); gst_bin_add (GST_BIN (element), encodebin->audio_sample); - } + } #endif return TRUE; } -static gboolean +static gboolean gst_encode_bin_init_image_elements (GstElement *element, gpointer user_data) { GstEncodeBin *encodebin = GST_ENCODE_BIN (element); @@ -2744,14 +2837,14 @@ gst_encode_bin_init_image_elements (GstElement *element, gpointer user_data) if(encodebin->icapsfilter == NULL) { encodebin->icapsfilter = gst_element_factory_make ("capsfilter","icapsfilter"); gst_bin_add (GST_BIN (element), encodebin->icapsfilter); - } + } if(encodebin->image_encode == NULL) { encodebin->image_encode = gst_element_factory_make (encodebin->ienc_name, "image_encode"); gst_bin_add (GST_BIN (element), encodebin->image_encode); - } + } - if (!encodebin->image_encode + if (!encodebin->image_encode || !encodebin->image_queue || !encodebin->image_toggle || !encodebin->icapsfilter @@ -2764,21 +2857,21 @@ gst_encode_bin_init_image_elements (GstElement *element, gpointer user_data) if (encodebin->auto_color_space && (encodebin->color_space == NULL)) { encodebin->color_space = gst_element_factory_make ("ffmpegcolorspace","color_space"); gst_bin_add (GST_BIN (element), encodebin->color_space); - } + } #endif return TRUE; } static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) { - + if(value) { //block stream switch(encodebin->profile) { case GST_ENCODE_BIN_PROFILE_AV: if(encodebin->audio_queue == NULL && encodebin->video_queue == NULL) { goto block_fail; } else { - if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->video_queue)), + if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->video_queue)), "empty-buffers") == NULL) { GST_ERROR_OBJECT(encodebin, "The queue element doesn't support 'empty-buffers' property"); goto block_fail; @@ -2788,7 +2881,7 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) g_object_set(encodebin->video_toggle, "block-data", TRUE , NULL); GST_INFO_OBJECT( encodebin, "video_toggle block-data TRUE" ); } - + g_object_set(encodebin->video_queue, "empty-buffers", TRUE , NULL); GST_INFO_OBJECT( encodebin, "video_queue empty-buffers TRUE" ); if(encodebin->audio_queue != NULL) @@ -2800,13 +2893,13 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) break; case GST_ENCODE_BIN_PROFILE_AUDIO: if(encodebin->audio_queue == NULL) { - goto block_fail; + goto block_fail; } else { - if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->audio_queue)), + if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->audio_queue)), "empty-buffers") == NULL) { GST_ERROR_OBJECT(encodebin, "The queue element doesn't support 'empty-buffers' property"); goto block_fail; - } + } g_object_set(encodebin->audio_queue, "empty-buffers", TRUE , NULL); GST_INFO_OBJECT( encodebin, "audio_queue empty-buffers TRUE" ); } @@ -2818,11 +2911,11 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) g_object_set(encodebin->image_toggle, "block_data", TRUE, NULL); GST_INFO_OBJECT( encodebin, "image_toggle block_data TRUE" ); } - break; + break; default: GST_WARNING_OBJECT (encodebin,"Invalid profile number = %d", encodebin->profile); goto block_fail; - break; + break; } } else { //release blocked-stream switch(encodebin->profile) { @@ -2830,19 +2923,19 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) if(encodebin->audio_queue == NULL && encodebin->video_queue == NULL) { goto unblock_fail; } else { - if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->video_queue)), + if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->video_queue)), "empty-buffers") == NULL) { GST_ERROR_OBJECT(encodebin, "The queue element doesn't support 'empty-buffers' property"); goto unblock_fail; - } + } if( encodebin->video_toggle ) { g_object_set(encodebin->video_toggle, "block-data", FALSE , NULL); GST_INFO_OBJECT( encodebin, "video_toggle block-data FALSE" ); } - + if(encodebin->audio_queue != NULL) - { + { g_object_set(encodebin->audio_queue, "empty-buffers", FALSE , NULL); GST_INFO_OBJECT( encodebin, "audio_queue empty-buffers FALSE" ); } @@ -2852,13 +2945,13 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) break; case GST_ENCODE_BIN_PROFILE_AUDIO: if(encodebin->audio_queue == NULL) { - goto unblock_fail; - } else { - if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->audio_queue)), + goto unblock_fail; + } else { + if(g_object_class_find_property(G_OBJECT_GET_CLASS(GST_OBJECT(encodebin->audio_queue)), "empty-buffers") == NULL) { GST_ERROR_OBJECT(encodebin, "The queue element doesn't support 'empty-buffers' property"); goto unblock_fail; - } + } g_object_set(encodebin->audio_queue, "empty-buffers", FALSE , NULL); GST_INFO_OBJECT( encodebin, "audio_queue empty-buffers FALSE" ); } @@ -2866,16 +2959,16 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) case GST_ENCODE_BIN_PROFILE_IMAGE: if(encodebin->image_toggle == NULL) { goto unblock_fail; - } else { + } else { g_object_set(encodebin->image_toggle, "block_data", FALSE, NULL); GST_INFO_OBJECT( encodebin, "image_toggle block_data FALSE" ); } - break; + break; default: GST_WARNING_OBJECT (encodebin,"Invalid profile number = %d", encodebin->profile); goto unblock_fail; - break; - } + break; + } } encodebin->block = value; return TRUE; @@ -2883,17 +2976,17 @@ static gboolean gst_encode_bin_block(GstEncodeBin *encodebin, gboolean value) block_fail: GST_ERROR_OBJECT(encodebin, "encodebin block failed"); return FALSE; - -unblock_fail: - GST_ERROR_OBJECT(encodebin, "encodebin unblock failed"); - return FALSE; + +unblock_fail: + GST_ERROR_OBJECT(encodebin, "encodebin unblock failed"); + return FALSE; } static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) { GstClock *clock = NULL; - if(value) { + if(value) { /* pause stream*/ //Block src of encode bin if (!gst_encode_bin_block(encodebin, TRUE)) @@ -2913,7 +3006,7 @@ static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) base_time = gst_element_get_base_time(GST_ELEMENT(encodebin)); encodebin->paused_time = current_time - base_time; - + GST_INFO_OBJECT (encodebin, "Encodebin is in running-pause at [%"GST_TIME_FORMAT"]." , GST_TIME_ARGS(encodebin->paused_time)); } @@ -2921,15 +3014,15 @@ static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) { encodebin->paused_time = 0; encodebin->total_offset_time = 0; - + GST_WARNING_OBJECT (encodebin, "There is no clock in Encodebin."); } } -#if 0 //def GST_ENCODE_BIN_SIGNAL_ENABLE +#if 0 //def GST_ENCODE_BIN_SIGNAL_ENABLE g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_PAUSE], 0, TRUE); -#endif +#endif } - else { + else { /* release paused-stream*/ if (encodebin->paused_time != 0) { @@ -2941,10 +3034,10 @@ static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) current_time = gst_clock_get_time(clock); base_time = gst_element_get_base_time(GST_ELEMENT(encodebin)); paused_gap = current_time - base_time - encodebin->paused_time; - + encodebin->total_offset_time += paused_gap; encodebin->paused_time = 0; - + GST_INFO_OBJECT (encodebin, "Encodebin now resumes. Offset delay [%"GST_TIME_FORMAT"], Total offset delay [%"GST_TIME_FORMAT"]" , GST_TIME_ARGS(paused_gap) , GST_TIME_ARGS(encodebin->total_offset_time)); } @@ -2952,7 +3045,7 @@ static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) { encodebin->paused_time = 0; encodebin->total_offset_time = 0; - + GST_WARNING_OBJECT (encodebin, "There is no clock in Encodebin."); } } @@ -2965,26 +3058,26 @@ static gboolean gst_encode_bin_pause(GstEncodeBin *encodebin, gboolean value) GST_WARNING_OBJECT (encodebin, "Fail to Unblock Encodebin."); goto resume_fail; } -#if 0 //def GST_ENCODE_BIN_SIGNAL_ENABLE +#if 0 //def GST_ENCODE_BIN_SIGNAL_ENABLE g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_RESUME], 0, TRUE); -#endif +#endif } encodebin->pause = value; return TRUE; pause_fail: GST_WARNING_OBJECT (encodebin, "Fail to pause Encodebin"); -#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE +#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_PAUSE], 0, FALSE); -#endif +#endif return FALSE; -resume_fail: +resume_fail: GST_WARNING_OBJECT (encodebin, "Fail to resume Encodebin"); -#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE +#ifdef GST_ENCODE_BIN_SIGNAL_ENABLE g_signal_emit (G_OBJECT (encodebin), gst_encode_bin_signals[SIGNAL_STREAM_RESUME], 0, FALSE); -#endif - return FALSE; +#endif + return FALSE; } static gboolean @@ -3011,9 +3104,9 @@ gst_encode_bin_release_pipeline (GstElement *element, gst_element_unlink (encodebin->video_queue, encodebin->video_encode); } - gst_pad_unlink (gst_element_get_pad (encodebin->audio_encode, "src"), + gst_pad_unlink (gst_element_get_pad (encodebin->audio_encode, "src"), encodebin->mux_audio_sinkpad); - gst_pad_unlink (gst_element_get_pad (encodebin->video_encode, "src"), + gst_pad_unlink (gst_element_get_pad (encodebin->video_encode, "src"), encodebin->mux_video_sinkpad); gst_bin_remove_many (GST_BIN (element), @@ -3042,7 +3135,7 @@ gst_encode_bin_audsink_set_caps (GstPad * pad, GstCaps * vscaps) // const GValue *codec_data; gint channels, rate; - encodebin = GST_ENCODE_BIN (gst_pad_get_parent (pad)); + encodebin = GST_ENCODE_BIN (gst_pad_get_parent (pad)); structure = gst_caps_get_structure (vscaps, 0); // mimetype = gst_structure_get_name (structure); @@ -3095,11 +3188,11 @@ gst_encode_bin_vidsink_set_caps (GstPad * pad, GstCaps * vscaps) encodebin->fps = gst_value_get_fraction_numerator(fps); - if(encodebin->high_speed_fps > 0) + if(encodebin->high_speed_fps > 0 && encodebin->fps > 0) { encodebin->multiple =(encodebin->high_speed_fps)/(encodebin->fps); } - + { guint32 format; @@ -3169,15 +3262,15 @@ static gboolean gst_encode_bin_imgsink_set_caps (GstPad * pad, GstCaps * vscaps) break; } } - gst_object_unref (encodebin); + gst_object_unref (encodebin); return TRUE; - + refuse_caps: { GST_WARNING_OBJECT (encodebin, "refused caps %" GST_PTR_FORMAT, vscaps); gst_object_unref (encodebin); return FALSE; - } + } } @@ -3189,7 +3282,7 @@ gst_encode_bin_video_probe(GstPad *pad, GstBuffer *buffer, GstEncodeBin *encodeb GST_WARNING_OBJECT (encodebin, "encodebin is Null."); return TRUE; } - + //Adjusting timestamp of video source GST_BUFFER_TIMESTAMP(buffer) -= encodebin->total_offset_time; @@ -3204,7 +3297,7 @@ gst_encode_bin_video_probe_hs(GstPad *pad, GstBuffer *buffer, GstEncodeBin *enco GST_WARNING_OBJECT (encodebin, "encodebin is Null."); return TRUE; } - + GST_BUFFER_TIMESTAMP(buffer) *= encodebin->multiple; return TRUE; } @@ -3217,7 +3310,7 @@ gst_encode_bin_audio_probe(GstPad *pad, GstBuffer *buffer, GstEncodeBin *encodeb GST_WARNING_OBJECT (encodebin, "encodebin is Null."); return TRUE; } - + //Adjusting timestamp of video source GST_BUFFER_TIMESTAMP(buffer) -= encodebin->total_offset_time; @@ -3274,5 +3367,5 @@ plugin_init (GstPlugin * plugin) GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "encodebin", - "EXT encoder bin", + "EXT encoder bin", plugin_init, VERSION, "LGPL", "Samsung Electronics Co", "http://www.samsung.com/") diff --git a/encodebin/src/gstencodebin.h b/encodebin/src/gstencodebin.h index 64c3a64..136fc92 100644 --- a/encodebin/src/gstencodebin.h +++ b/encodebin/src/gstencodebin.h @@ -69,72 +69,72 @@ struct _GstEncodeBin GMutex *mutex; /* pads */ - GstPad *srcpad; - GstPad *video_sinkpad; - GstPad *audio_sinkpad; - GstPad *image_sinkpad; - GstPad *mux_audio_sinkpad; - GstPad *mux_video_sinkpad; + GstPad *srcpad; + GstPad *video_sinkpad; + GstPad *audio_sinkpad; + GstPad *image_sinkpad; + GstPad *mux_audio_sinkpad; + GstPad *mux_video_sinkpad; /* sinkpads, video first */ - GSList *sinkpads; + GSList *sinkpads; /* video restricted to 1 pad */ - guint video_pads, audio_pads; - - gint profile; - gint fps; - gint high_speed_fps; - gint multiple; - gchar *venc_name; - gchar *aenc_name; - gchar *ienc_name; - gchar *mux_name; - - - GstCaps *vcaps; - GstCaps *acaps; - GstCaps *icaps; - - gboolean auto_audio_convert; - gboolean auto_audio_resample; - gboolean auto_color_space; - gboolean block; - gboolean pause; - gboolean use_video_toggle; - gboolean use_venc_queue; - gboolean use_aenc_queue; - + guint video_pads, audio_pads; + + gint profile; + gint fps; + gint high_speed_fps; + gint multiple; + gchar *venc_name; + gchar *aenc_name; + gchar *ienc_name; + gchar *mux_name; + gchar *vconv_name; + + GstCaps *vcaps; + GstCaps *acaps; + GstCaps *icaps; + + gboolean auto_audio_convert; + gboolean auto_audio_resample; + gboolean auto_color_space; + gboolean block; + gboolean pause; + gboolean use_video_toggle; + gboolean use_venc_queue; + gboolean use_aenc_queue; + GstElement *audio_queue; - GstElement *video_queue; - GstElement *video_encode_queue; - GstElement *audio_encode_queue; - GstElement *image_queue; - + GstElement *video_queue; + GstElement *video_encode_queue; + GstElement *audio_encode_queue; + GstElement *image_queue; + GstElement *audio_encode; GstElement *video_encode; - GstElement *image_encode; + GstElement *image_encode; + + GstElement *vcapsfilter; + GstElement *acapsfilter; + GstElement *icapsfilter; - GstElement *vcapsfilter; - GstElement *acapsfilter; - GstElement *icapsfilter; - GstElement *video_toggle; - GstElement *image_toggle; - GstElement *color_space; - GstElement *audio_conv; + GstElement *image_toggle; + GstElement *color_space; + GstElement *audio_conv; GstElement *audio_sample; - + GstElement *mux; /* pause/resume variables */ - GstClockTime paused_time; /* pipeline time when pausing */ - GstClockTime total_offset_time; /* delayed time which is due to pause */ - gulong vsink_probeid; - gulong vsink_hs_probeid; - gulong asink_probeid; - gulong veque_sig_id; - gulong aeque_sig_id; + GstClockTime paused_time; /* pipeline time when pausing */ + GstClockTime total_offset_time; /* delayed time which is due to pause */ + gulong vsink_probeid; + gulong vsink_hs_probeid; + gulong asink_probeid; + gulong veque_sig_id; + gulong aeque_sig_id; }; struct _GstEncodeBinClass diff --git a/evasimagesink/src/Makefile.am b/evasimagesink/src/Makefile.am index 8521916..8a88d14 100644 --- a/evasimagesink/src/Makefile.am +++ b/evasimagesink/src/Makefile.am @@ -17,8 +17,8 @@ plugin_LTLIBRARIES = libgstevasimagesink.la libgstevasimagesink_la_SOURCES = gstevasimagesink.c gstevasimagesink.h # compiler and linker flags used to compile this plugin, set in configure.ac -libgstevasimagesink_la_CFLAGS = $(GST_CFLAGS) $(GST_VIDEO_CFLAGS) $(EFL_CFLAGS) $(MMTA_CFLAGS) -libgstevasimagesink_la_LIBADD = $(GST_LIBS) $(GST_VIDEO_LIBS) $(EFL_LIBS) $(MMTA_LIBS) +libgstevasimagesink_la_CFLAGS = $(GST_CFLAGS) $(GST_VIDEO_CFLAGS) $(EFL_CFLAGS) $(NATIVE_BUFFER_CFLAGS) $(TBM_CFLAGS) +libgstevasimagesink_la_LIBADD = $(GST_LIBS) $(GST_VIDEO_LIBS) $(EFL_LIBS) $(NATIVE_BUFFER_LIBS) $(TBM_LIBS) libgstevasimagesink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstevasimagesink_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/evasimagesink/src/gstevasimagesink.c b/evasimagesink/src/gstevasimagesink.c old mode 100644 new mode 100755 index f9f9d05..3e8504b --- a/evasimagesink/src/gstevasimagesink.c +++ b/evasimagesink/src/gstevasimagesink.c @@ -42,6 +42,7 @@ #define CAP_WIDTH "width" #define CAP_HEIGHT "height" +//#define DUMP_IMG GST_DEBUG_CATEGORY_STATIC (gst_evas_image_sink_debug); #define GST_CAT_DEFAULT gst_evas_image_sink_debug @@ -58,10 +59,137 @@ enum PROP_0, PROP_EVAS_OBJECT, PROP_EVAS_OBJECT_SHOW, +#ifdef USE_TBM_SURFACE + PROP_ROTATE_ANGLE, + PROP_DISPLAY_GEOMETRY_METHOD, + PROP_ENABLE_FLUSH_BUFFER, + PROP_FLIP +#endif +}; + +enum +{ + UPDATE_FALSE, + UPDATE_TRUE }; #define COLOR_DEPTH 4 #define GL_X11_ENGINE "gl_x11" +#define DO_RENDER_FROM_FIMC 1 +#define SIZE_FOR_UPDATE_VISIBILITY sizeof(gchar) + +#ifdef USE_TBM_SURFACE +#define MAX_ECOREPIPE_BUFFER_CNT 4 +#define DEBUGLOG_DEFAULT_COUNT 8 +#define SIZE_FOR_NATIVE_INDEX sizeof(gint) + +/* max channel count *********************************************************/ +#define SCMN_IMGB_MAX_PLANE (4) + +/* image buffer definition *************************************************** + + +------------------------------------------+ --- + | | ^ + | a[], p[] | | + | +---------------------------+ --- | | + | | | ^ | | + | |<---------- w[] ---------->| | | | + | | | | | | + | | | | + | | | h[] | e[] + | | | | + | | | | | | + | | | | | | + | | | v | | + | +---------------------------+ --- | | + | | v + +------------------------------------------+ --- + + |<----------------- s[] ------------------>| +*/ + +typedef struct +{ + /* width of each image plane */ + int w[SCMN_IMGB_MAX_PLANE]; + /* height of each image plane */ + int h[SCMN_IMGB_MAX_PLANE]; + /* stride of each image plane */ + int s[SCMN_IMGB_MAX_PLANE]; + /* elevation of each image plane */ + int e[SCMN_IMGB_MAX_PLANE]; + /* user space address of each image plane */ + void *a[SCMN_IMGB_MAX_PLANE]; + /* physical address of each image plane, if needs */ + void *p[SCMN_IMGB_MAX_PLANE]; + /* color space type of image */ + int cs; + /* left postion, if needs */ + int x; + /* top position, if needs */ + int y; + /* to align memory */ + int __dummy2; + /* arbitrary data */ + int data[16]; + /* dma buf fd */ + int fd[SCMN_IMGB_MAX_PLANE]; + /* buffer share method */ + int buf_share_method; + /* Y plane size in case of ST12 */ + int y_size; + /* UV plane size in case of ST12 */ + int uv_size; + /* Tizen buffer object */ + void *bo[SCMN_IMGB_MAX_PLANE]; + /* JPEG data */ + void *jpeg_data; + /* JPEG size */ + int jpeg_size; + /* TZ memory buffer */ + int tz_enable; +} SCMN_IMGB; +#endif + +#define EVASIMAGESINK_SET_EVAS_OBJECT_EVENT_CALLBACK( x_evas_image_object, x_usr_data ) \ +do \ +{ \ + if (x_evas_image_object) { \ + GST_LOG("object callback add"); \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_DEL, evas_callback_del_event, x_usr_data); \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_RESIZE, evas_callback_resize_event, x_usr_data); \ + } \ +}while(0) + +#define EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK( x_evas_image_object ) \ +do \ +{ \ + if (x_evas_image_object) { \ + GST_LOG("object callback del"); \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_DEL, evas_callback_del_event); \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_RESIZE, evas_callback_resize_event); \ + } \ +}while(0) + +#ifdef USE_TBM_SURFACE +#define EVASIMAGESINK_SET_EVAS_EVENT_CALLBACK( x_evas, x_usr_data ) \ +do \ +{ \ + if (x_evas) { \ + GST_DEBUG("callback add... evas_callback_render_pre.. evas : %p esink : %p", x_evas, x_usr_data); \ + evas_event_callback_add (x_evas, EVAS_CALLBACK_RENDER_PRE, evas_callback_render_pre, x_usr_data); \ + } \ +}while(0) + +#define EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK( x_evas ) \ +do \ +{ \ + if (x_evas) { \ + GST_DEBUG("callback del... evas_callback_render_pre"); \ + evas_event_callback_del (x_evas, EVAS_CALLBACK_RENDER_PRE, evas_callback_render_pre); \ + } \ +}while(0) +#endif GMutex *instance_lock; guint instance_lock_count; @@ -83,6 +211,14 @@ is_evas_image_object (Evas_Object *obj) return FALSE; } +#ifdef USE_TBM_SURFACE +gint +gst_evas_image_sink_ref_count (GstBuffer * buf) +{ + return GST_OBJECT_REFCOUNT_VALUE(GST_BUFFER_CAST(buf)); +} +#endif + /* the capabilities of the inputs. * * BGRx format @@ -90,8 +226,15 @@ is_evas_image_object (Evas_Object *obj) static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, +#ifdef USE_FIMCC GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx)); - +#endif +#ifdef USE_TBM_SURFACE + GST_STATIC_CAPS(GST_VIDEO_CAPS_YUV ("I420") ";" + GST_VIDEO_CAPS_YUV ("NV12") ";" + GST_VIDEO_CAPS_YUV ("SN12")) +); +#endif GST_BOILERPLATE (GstEvasImageSink, gst_evas_image_sink, GstVideoSink, GST_TYPE_VIDEO_SINK); static void gst_evas_image_sink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); @@ -100,8 +243,20 @@ static gboolean gst_evas_image_sink_set_caps (GstBaseSink *base_sink, GstCaps *c static GstFlowReturn gst_evas_image_sink_show_frame (GstVideoSink *video_sink, GstBuffer *buf); static gboolean gst_evas_image_sink_event (GstBaseSink *sink, GstEvent *event); static GstStateChangeReturn gst_evas_image_sink_change_state (GstElement *element, GstStateChange transition); -static void evas_image_sink_cb_del_eo (void *data, Evas *e, Evas_Object *obj, void *event_info); -static void evas_image_sink_cb_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +static void evas_callback_del_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +static void evas_callback_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +#ifdef USE_TBM_SURFACE +static void evas_callback_render_pre (void *data, Evas *e, void *event_info); +static GstFlowReturn gst_esink_epipe_reset(GstEvasImageSink *esink); +static gboolean gst_esink_make_flush_buffer(GstEvasImageSink *esink); +static void _release_flush_buffer(GstEvasImageSink *esink); +static void gst_evas_image_sink_update_geometry (GstEvasImageSink *esink, GstVideoRectangle *result); +static void gst_evas_image_sink_apply_geometry (GstEvasImageSink *esink); +#endif +#ifdef DUMP_IMG +int util_write_rawdata (const char *file, const void *data, unsigned int size); +int g_cnt = 0; +#endif static void gst_evas_image_sink_base_init (gpointer gclass) @@ -112,7 +267,7 @@ gst_evas_image_sink_base_init (gpointer gclass) "EvasImageSink", "VideoSink", "Video sink element for evas image object", - "Wonguk Jeong "); + "Samsung Electronics "); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_factory)); @@ -138,7 +293,20 @@ gst_evas_image_sink_class_init (GstEvasImageSinkClass *klass) g_param_spec_pointer ("evas-object", "Destination Evas Object", "Destination evas image object", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_EVAS_OBJECT_SHOW, g_param_spec_boolean ("visible", "Show Evas Object", "When disabled, evas object does not show", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - +#ifdef USE_TBM_SURFACE + g_object_class_install_property(gobject_class, PROP_ROTATE_ANGLE, + g_param_spec_int("rotate", "Rotate angle", "Rotate angle of display output", DEGREE_0, DEGREE_NUM, DEGREE_0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(gobject_class, PROP_DISPLAY_GEOMETRY_METHOD, + g_param_spec_int("display-geometry-method", "Display geometry method", + "Geometrical method for display", DISP_GEO_METHOD_LETTER_BOX, DISP_GEO_METHOD_NUM, DISP_GEO_METHOD_LETTER_BOX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ENABLE_FLUSH_BUFFER, + g_param_spec_boolean("enable-flush-buffer", "Enable flush buffer mechanism", + "Enable flush buffer mechanism when state change(PAUSED_TO_READY)", + TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(gobject_class, PROP_FLIP, + g_param_spec_int("flip", "Display flip", + "Flip for display", FLIP_NONE, FLIP_NUM, FLIP_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif gstvideosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_evas_image_sink_show_frame); gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_evas_image_sink_set_caps); gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_evas_image_sink_event); @@ -148,16 +316,39 @@ gst_evas_image_sink_class_init (GstEvasImageSinkClass *klass) static void gst_evas_image_sink_fini (gpointer data, GObject *obj) { - GST_INFO ("enter"); + GST_DEBUG ("[ENTER]"); GstEvasImageSink *esink = GST_EVASIMAGESINK (obj); if (!esink) { return; } +#ifdef USE_FIMCC if (esink->oldbuf) { gst_buffer_unref (esink->oldbuf); } +#endif + if (esink->eo) { +#ifdef USE_TBM_SURFACE + EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK (evas_object_evas_get(esink->eo)); + GST_DEBUG("unset EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK esink : %p, esink->eo : %x", esink, esink->eo); +#endif + EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK (esink->eo); + GST_DEBUG("unset EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK esink : %p, esink->eo : %x", esink, esink->eo); + evas_object_image_data_set(esink->eo, NULL); + } +#ifdef USE_TBM_SURFACE + if (esink->display_buffer_lock) { + g_mutex_free (esink->display_buffer_lock); + esink->display_buffer_lock = NULL; + } + if (esink->flow_lock) { + g_mutex_free (esink->flow_lock); + esink->flow_lock = NULL; + } + esink->eo = NULL; + esink->epipe = NULL; +#endif g_mutex_lock (instance_lock); instance_lock_count--; g_mutex_unlock (instance_lock); @@ -165,29 +356,121 @@ gst_evas_image_sink_fini (gpointer data, GObject *obj) g_mutex_free (instance_lock); instance_lock = NULL; } + + GST_DEBUG ("[LEAVE]"); +} + +#ifdef USE_TBM_SURFACE +static void +gst_evas_image_sink_reset (GstEvasImageSink *esink) +{ + int i = 0; + tbm_bo_handle bo_handle; + + GST_DEBUG("gst_evas_image_sink_reset start"); + + g_mutex_lock(esink->display_buffer_lock); + + for(i=0; idisplaying_buffer[i].tbm_surf) + { + tbm_surface_destroy(esink->displaying_buffer[i].tbm_surf); + esink->displaying_buffer[i].tbm_surf = NULL; + } + if(esink->displaying_buffer[i].buffer) + { + if(esink->displaying_buffer[i].ref_count) + { + if(esink->displaying_buffer[i].bo) { + bo_handle = tbm_bo_map(esink->displaying_buffer[i].bo, TBM_DEVICE_CPU, TBM_OPTION_READ|TBM_OPTION_WRITE); + if (!bo_handle.ptr) { + GST_WARNING("failed to map bo [%p]", esink->displaying_buffer[i].bo); + } + else + tbm_bo_unmap(esink->displaying_buffer[i].bo); + } + else + GST_WARNING("there is no bo information. so skip to map bo"); + + GST_WARNING("[reset] unreffing gst %p", esink->displaying_buffer[i].buffer); + + while(esink->displaying_buffer[i].ref_count) + { + GST_WARNING("index[%d]'s buffer ref count=%d",i,gst_evas_image_sink_ref_count (esink->displaying_buffer[i].buffer)); + esink->displaying_buffer[i].ref_count--; + gst_buffer_unref(esink->displaying_buffer[i].buffer); + } + } + esink->displaying_buffer[i].buffer = NULL; + } + if(esink->displaying_buffer[i].bo) + esink->displaying_buffer[i].bo = NULL; + } + esink->prev_buf = NULL; + esink->prev_index = -1; + esink->cur_index = -1; + + esink->eo_size.x = esink->eo_size.y = + esink->eo_size.w = esink->eo_size.h = 0; + esink->use_ratio = FALSE; + esink->sent_buffer_cnt = 0; + + g_mutex_unlock(esink->display_buffer_lock); } +#endif +#ifdef USE_FIMCC static void evas_image_sink_cb_pipe (void *data, void *buffer, unsigned int nbyte) +#endif +#ifdef USE_TBM_SURFACE +static void +evas_image_sink_cb_pipe (void *data, int *buffer_index, unsigned int nbyte) +#endif { - GstBuffer *buf; GstEvasImageSink *esink = data; +#ifdef USE_FIMCC + GstBuffer *buf; void *img_data; - - if (!data || !buffer) { +#endif + GST_DEBUG ("[ENTER]"); + if (!esink || !esink->eo) { + GST_WARNING ("esink : %p, or eo is NULL returning", esink); return; } - if (nbyte != sizeof (GstBuffer *)) { + GST_LOG("esink : %p, esink->eo : %x", esink, esink->eo); + if (nbyte == SIZE_FOR_UPDATE_VISIBILITY) { + if(!esink->object_show) { + evas_object_hide(esink->eo); + GST_INFO ("object hide.."); + } else { + evas_object_show(esink->eo); + GST_INFO ("object show.."); + } + GST_DEBUG ("[LEAVE]"); return; } - if (!esink->eo) { +#ifdef USE_FIMCC + if (!buffer || nbyte != sizeof (GstBuffer *)) { + GST_WARNING ("buffer %p, nbyte : %d, sizeof(GstBuffer *) : %d", buffer, nbyte, sizeof (GstBuffer *)); return; } +#endif +#ifdef USE_TBM_SURFACE + int index = 0; + index = *buffer_index; + if ((index<0 || index>=NATIVE_BUFFER_NUM) || nbyte != SIZE_FOR_NATIVE_INDEX) { + GST_WARNING ("index : %d, nbyte : %d", index, nbyte); + return; + } +#endif if (GST_STATE(esink) < GST_STATE_PAUSED) { GST_WARNING ("WRONG-STATE(%d) for rendering, skip this frame", GST_STATE(esink)); return; } +#ifdef USE_FIMCC memcpy (&buf, buffer, sizeof (GstBuffer *)); if (!buf) { GST_ERROR ("There is no buffer"); @@ -195,16 +478,142 @@ evas_image_sink_cb_pipe (void *data, void *buffer, unsigned int nbyte) } if (esink->present_data_addr == -1) { /* if present_data_addr is -1, we don't use this member variable */ - } else if (esink->present_data_addr != GST_BUFFER_DATA (buf)) { - GST_WARNING ("skip rendering this buffer, present_data_addr:%x, GST_BUFFER_DATA(buf):%x", esink->present_data_addr,GST_BUFFER_DATA(buf)); + } else if (esink->present_data_addr != DO_RENDER_FROM_FIMC) { + GST_WARNING ("skip rendering this buffer, present_data_addr:%d, DO_RENDER_FROM_FIMC:%d", esink->present_data_addr, DO_RENDER_FROM_FIMC); return; } +#endif +#ifdef USE_TBM_SURFACE + g_mutex_lock(esink->display_buffer_lock); + if(esink->tbm_surface_format == TBM_FORMAT_NV12) { + if (!esink->displaying_buffer[index].tbm_surf) { + GST_ERROR("the index's nbuffer was already NULL, so return"); + g_mutex_unlock(esink->display_buffer_lock); + return; + } + GST_LOG("received (bo %p, gst %p) index num : %d", esink->displaying_buffer[index].bo, esink->displaying_buffer[index].buffer, index); + } else if(esink->tbm_surface_format == TBM_FORMAT_YUV420) { + GST_LOG("received (bo %p) index num : %d", esink->displaying_buffer[index].bo, index); + } - MMTA_ACUM_ITEM_BEGIN("eavsimagesink _cb_pipe total", FALSE); + Evas_Native_Surface surf; + surf.type = EVAS_NATIVE_SURFACE_TBM; + surf.version = EVAS_NATIVE_SURFACE_VERSION; + surf.data.tizen.buffer = esink->displaying_buffer[index].tbm_surf; + surf.data.tizen.rot = esink->rotate_angle; + surf.data.tizen.flip = esink->flip; + GST_LOG("received (bo %p, gst %p) index num : %d", esink->displaying_buffer[index].bo, esink->displaying_buffer[index].buffer, index); + + GstVideoRectangle result = {0}; + + evas_object_geometry_get(esink->eo, &esink->eo_size.x, &esink->eo_size.y, &esink->eo_size.w, &esink->eo_size.h); + if (!esink->eo_size.w || !esink->eo_size.h) { + GST_ERROR ("there is no information for evas object size"); + g_mutex_unlock(esink->display_buffer_lock); + return; + } + gst_evas_image_sink_update_geometry(esink, &result); + if(!result.w || !result.h) + { + GST_ERROR("no information about geometry (%d, %d)", result.w, result.h); + g_mutex_unlock(esink->display_buffer_lock); + return; + } + + if(esink->use_ratio) + { + surf.data.tizen.ratio = (float) esink->w / esink->h; + GST_LOG("set ratio for letter mode"); + } + evas_object_size_hint_align_set(esink->eo, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(esink->eo, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + if ( !esink->is_evas_object_size_set && esink->w > 0 && esink->h > 0) { + evas_object_image_size_set(esink->eo, esink->w, esink->h); + esink->is_evas_object_size_set = TRUE; + } +#ifdef DUMP_IMG + int ret = 0; + char file_name[128]; + char *dump_data; + guint size = esink->w*esink->h + (esink->w/2*esink->h/2)*2; //this size is for I420 format + tbm_bo_handle vaddr_dump; + if(g_cnt<20) + { + GST_WARNING ("DUMP IMG_%2.2d : buffer size(%d)", g_cnt, size); + sprintf(file_name, "DUMP_IMG_%2.2d.dump", g_cnt++); + + dump_data = g_malloc(size); + vaddr_dump = tbm_bo_map(esink->displaying_buffer[index].bo, TBM_DEVICE_CPU, TBM_OPTION_READ|TBM_OPTION_WRITE); + if (vaddr_dump.ptr) { + tbm_bo_unmap(esink->displaying_buffer[index].bo); + } + memcpy (dump_data, vaddr_dump.ptr, size); + + ret = util_write_rawdata(file_name, dump_data, size); + if (ret) { + GST_ERROR_OBJECT (esink, "util_write_rawdata() failed"); + } + g_free(dump_data); + } +#endif + evas_object_image_native_surface_set(esink->eo, &surf); + GST_DEBUG("native surface set finish"); + + if(result.x || result.y) + GST_LOG("coordinate x, y (%d, %d) for locating video to center", result.x, result.y); + evas_object_image_fill_set(esink->eo, result.x, result.y, result.w, result.h); + + evas_object_image_pixels_dirty_set(esink->eo, EINA_TRUE); + GST_DEBUG_OBJECT (esink, "GEO_METHOD : src(%dx%d), dst(%dx%d), dst_x(%d), dst_y(%d), rotate(%d), flip(%d)", + esink->w, esink->h, esink->eo_size.w, esink->eo_size.h, esink->eo_size.x, esink->eo_size.y, esink->rotate_angle, esink->flip); + + /* unref previous buffer */ + if(esink->tbm_surface_format == TBM_FORMAT_NV12) { + if(esink->prev_buf && esink->displaying_buffer[esink->prev_index].ref_count) + { + GST_DEBUG("before index %d's ref_count =%d, gst_buf %p",esink->prev_index,esink->displaying_buffer[esink->prev_index].ref_count, esink->prev_buf); + esink->displaying_buffer[esink->prev_index].ref_count--; + GST_DEBUG("after index %d's ref_count =%d, gst_buf %p",esink->prev_index,esink->displaying_buffer[esink->prev_index].ref_count, esink->prev_buf); + /* Print debug log for 8 frame */ + if(esink->debuglog_cnt_ecoreCbPipe > 0) + { + GST_WARNING("(%d) ecore_cb_pipe unref index[%d] .. gst_buf %p", DEBUGLOG_DEFAULT_COUNT-(esink->debuglog_cnt_ecoreCbPipe), esink->prev_index, esink->prev_buf); + esink->debuglog_cnt_ecoreCbPipe--; + } + if (esink->sent_buffer_cnt == MAX_ECOREPIPE_BUFFER_CNT) + GST_WARNING("sent buffer cnt 4->3 so skip will be stop"); + + esink->sent_buffer_cnt--; + GST_DEBUG("prev gst_buffer %p's unref Start!!", esink->prev_buf); + gst_buffer_unref(esink->prev_buf); + GST_DEBUG("prev gst_buffer %p's unref End!!", esink->prev_buf); + } else { + GST_DEBUG("ref_count=%d unref prev gst_buffer %p", esink->displaying_buffer[esink->prev_index].ref_count,esink->prev_buf); + } + + GST_DEBUG("Current gst_buf %p and index=%d is overwrited to Prev gst_buf %p & index %d", + esink->displaying_buffer[index].buffer, index, esink->prev_buf, esink->prev_index ); + esink->prev_buf = esink->displaying_buffer[index].buffer; + esink->prev_index = index; + }else if(esink->tbm_surface_format == TBM_FORMAT_YUV420) { + /* Print debug log for 8 frame */ + if(esink->debuglog_cnt_ecoreCbPipe > 0) + { + GST_WARNING("(%d) ecore_cb_pipe set native surface [%d] tbm_surf[%p]", + DEBUGLOG_DEFAULT_COUNT-(esink->debuglog_cnt_ecoreCbPipe), index, esink->displaying_buffer[index].tbm_surf); + esink->debuglog_cnt_ecoreCbPipe--; + } + if (esink->sent_buffer_cnt == MAX_ECOREPIPE_BUFFER_CNT) + GST_WARNING("sent buffer cnt 4->3 so skip will be stop"); + + esink->sent_buffer_cnt--; + } +#endif +#ifdef USE_FIMCC if ( !esink->is_evas_object_size_set && esink->w > 0 && esink->h > 0) { evas_object_image_size_set (esink->eo, esink->w, esink->h); - GST_DEBUG("evas_object_image_size_set(), width(%d),height(%d)",esink->w,esink->h); + GST_DEBUG("evas_object_image_size_set(), width(%d),height(%d)", esink->w, esink->h); esink->is_evas_object_size_set = TRUE; } if (esink->gl_zerocopy) { @@ -213,14 +622,14 @@ evas_image_sink_cb_pipe (void *data, void *buffer, unsigned int nbyte) GST_WARNING ("Cannot get image data from evas object or cannot get gstbuffer data"); evas_object_image_data_set(esink->eo, img_data); } else { - GST_DEBUG ("img_data(%x), GST_BUFFER_DATA(buf):%x, esink->w(%d),esink->h(%d)",img_data,GST_BUFFER_DATA(buf),esink->w,esink->h); - __ta__("evasimagesink memcpy in _cb_pipe", memcpy (img_data, GST_BUFFER_DATA (buf), esink->w * esink->h * COLOR_DEPTH);); + GST_DEBUG ("img_data(%x), GST_BUFFER_DATA(buf):%x, esink->w(%d),esink->h(%d), esink->eo(%x)",img_data,GST_BUFFER_DATA(buf),esink->w,esink->h,esink->eo); + memcpy (img_data, GST_BUFFER_DATA (buf), esink->w * esink->h * COLOR_DEPTH); evas_object_image_pixels_dirty_set (esink->eo, 1); evas_object_image_data_set(esink->eo, img_data); } gst_buffer_unref (buf); } else { - GST_DEBUG ("GST_BUFFER_DATA(buf):%x",GST_BUFFER_DATA(buf)); + GST_DEBUG ("GST_BUFFER_DATA(buf):%x, esink->eo(%x)",GST_BUFFER_DATA(buf),esink->eo); evas_object_image_data_set (esink->eo, GST_BUFFER_DATA (buf)); evas_object_image_pixels_dirty_set (esink->eo, 1); if (esink->oldbuf) { @@ -228,22 +637,170 @@ evas_image_sink_cb_pipe (void *data, void *buffer, unsigned int nbyte) } esink->oldbuf = buf; } +#endif +#ifdef USE_TBM_SURFACE + g_mutex_unlock(esink->display_buffer_lock); +#endif + GST_DEBUG ("[LEAVE]"); +} +#ifdef USE_TBM_SURFACE +static void +gst_evas_image_sink_update_geometry (GstEvasImageSink *esink, GstVideoRectangle *result) +{ + if (!esink || !esink->eo) { + GST_WARNING("there is no esink"); + return; + } - MMTA_ACUM_ITEM_END("eavsimagesink _cb_pipe total", FALSE); + result->x = 0; + result->y = 0; + + switch (esink->display_geometry_method) + { + case DISP_GEO_METHOD_LETTER_BOX: // 0 + /* set black padding for letter box mode */ + GST_DEBUG("letter box mode"); + esink->use_ratio = TRUE; + result->w = esink->eo_size.w; + result->h = esink->eo_size.h; + break; + case DISP_GEO_METHOD_ORIGIN_SIZE: // 1 + GST_DEBUG("origin size mode"); + esink->use_ratio = FALSE; + /* set coordinate for each case */ + result->x = (esink->eo_size.w-esink->w) / 2; + result->y = (esink->eo_size.h-esink->h) / 2; + result->w = esink->w; + result->h = esink->h; + break; + case DISP_GEO_METHOD_FULL_SCREEN: // 2 + GST_DEBUG("full screen mode"); + esink->use_ratio = FALSE; + result->w = esink->eo_size.w; + result->h = esink->eo_size.h; + break; + case DISP_GEO_METHOD_CROPPED_FULL_SCREEN: // 3 + GST_DEBUG("cropped full screen mode"); + esink->use_ratio = FALSE; + /* compare evas object's ratio with video's */ + if((esink->eo_size.w/esink->eo_size.h) > (esink->w/esink->h)) + { + result->w = esink->eo_size.w; + result->h = esink->eo_size.w * esink->h / esink->w; + result->y = -(result->h-esink->eo_size.h) / 2; + } + else + { + result->w = esink->eo_size.h * esink->w / esink->h; + result->h = esink->eo_size.h; + result->x = -(result->w-esink->eo_size.w) / 2; + } + break; + case DISP_GEO_METHOD_ORIGIN_SIZE_OR_LETTER_BOX: // 4 + GST_DEBUG("origin size or letter box mode"); + /* if video size is smaller than evas object's, it will be set to origin size mode */ + if((esink->eo_size.w > esink->w) && (esink->eo_size.h > esink->h)) + { + GST_DEBUG("origin size mode"); + esink->use_ratio = FALSE; + /* set coordinate for each case */ + result->x = (esink->eo_size.w-esink->w) / 2; + result->y = (esink->eo_size.h-esink->h) / 2; + result->w = esink->w; + result->h = esink->h; + } + else + { + GST_DEBUG("letter box mode"); + esink->use_ratio = TRUE; + result->w = esink->eo_size.w; + result->h = esink->eo_size.h; + } + break; + default: + GST_WARNING("unsupported mode."); + break; + } + GST_DEBUG("geometry result [%d, %d, %d, %d]", result->x, result->y, result->w, result->h); } +static void +gst_evas_image_sink_apply_geometry (GstEvasImageSink *esink) +{ + if (!esink || !esink->eo) { + GST_WARNING("there is no esink"); + return; + } + + Evas_Native_Surface *surf = evas_object_image_native_surface_get(esink->eo); + GstVideoRectangle result = {0}; + + if(surf) + { + GST_DEBUG("native surface exists"); + surf->data.tizen.rot = esink->rotate_angle; + surf->data.tizen.flip = esink->flip; + evas_object_image_native_surface_set(esink->eo, surf); + + gst_evas_image_sink_update_geometry(esink, &result); + + if(esink->use_ratio) + { + surf->data.tizen.ratio = (float) esink->w / esink->h; + GST_LOG("set ratio for letter mode"); + } + + if(result.x || result.y) + GST_LOG("coordinate x, y (%d, %d) for locating video to center", result.x, result.y); + + evas_object_image_fill_set(esink->eo, result.x, result.y, result.w, result.h); + } + else + GST_WARNING("there is no surf"); +} +#endif static void gst_evas_image_sink_init (GstEvasImageSink *esink, GstEvasImageSinkClass *gclass) { - GST_INFO ("enter"); + GST_DEBUG ("[ENTER]"); esink->eo = NULL; esink->epipe = NULL; esink->object_show = FALSE; - esink->gl_zerocopy = FALSE; + esink->update_visibility = UPDATE_FALSE; esink->is_evas_object_size_set = FALSE; +#ifdef USE_FIMCC + esink->gl_zerocopy = FALSE; esink->present_data_addr = -1; - +#endif +#ifdef USE_TBM_SURFACE + esink->display_buffer_lock = g_mutex_new (); + esink->flow_lock = g_mutex_new (); + int i = 0; + for (i=0; idisplaying_buffer[i].tbm_surf = NULL; + esink->displaying_buffer[i].buffer = NULL; + esink->displaying_buffer[i].bo = NULL; + esink->displaying_buffer[i].ref_count = 0; + } + esink->prev_buf = NULL; + esink->prev_index = -1; + esink->cur_index = -1; + esink->enable_flush_buffer = TRUE; + esink->need_flush = FALSE; + esink->display_geometry_method = DISP_GEO_METHOD_LETTER_BOX; + esink->flip = FLIP_NONE; + esink->eo_size.x = esink->eo_size.y = + esink->eo_size.w = esink->eo_size.h = 0; + esink->use_ratio = FALSE; + esink->sent_buffer_cnt = 0; + esink->debuglog_cnt_showFrame = DEBUGLOG_DEFAULT_COUNT; + esink->debuglog_cnt_ecoreCbPipe = DEBUGLOG_DEFAULT_COUNT; + + esink->src_buf_idx = 0; + esink->is_buffer_allocated = FALSE; +#endif if(!instance_lock) { instance_lock = g_mutex_new(); } @@ -252,42 +809,229 @@ gst_evas_image_sink_init (GstEvasImageSink *esink, GstEvasImageSinkClass *gclass g_mutex_unlock (instance_lock); g_object_weak_ref (G_OBJECT (esink), gst_evas_image_sink_fini, NULL); + + GST_DEBUG ("[LEAVE]"); } static void -evas_image_sink_cb_del_eo (void *data, Evas *e, Evas_Object *obj, void *event_info) +evas_callback_del_event (void *data, Evas *e, Evas_Object *obj, void *event_info) { + GST_DEBUG ("[ENTER]"); + GstEvasImageSink *esink = data; if (!esink) { return; } - - evas_object_event_callback_del (esink->eo, EVAS_CALLBACK_RESIZE, evas_image_sink_cb_resize_event); +#ifdef USE_FIMCC if (esink->oldbuf) { gst_buffer_unref (esink->oldbuf); esink->oldbuf = NULL; + } +#endif + if (esink->eo) { +#ifdef USE_TBM_SURFACE + EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK (evas_object_evas_get(esink->eo)); + GST_DEBUG("unset EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK esink : %p, esink->eo : %x", esink, esink->eo); +#endif + EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK (esink->eo); + GST_DEBUG("unset EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK esink : %p, esink->eo : %x", esink, esink->eo); + + evas_object_image_data_set(esink->eo, NULL); esink->eo = NULL; } + GST_DEBUG ("[LEAVE]"); } static void -evas_image_sink_cb_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info) +evas_callback_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info) { - int w = 0; - int h = 0; + int x, y, w, h; + x = y = w = h = 0; + + GST_DEBUG ("[ENTER]"); + GstEvasImageSink *esink = data; - if (!esink) { + if (!esink || !esink->eo) { return; } - evas_object_geometry_get(esink->eo, NULL, NULL, &w, &h); + evas_object_geometry_get(esink->eo, &x, &y, &w, &h); if (!w || !h) { - GST_WARNING ("evas object size (w:%d,h:%d) was not set",w,h); + GST_WARNING ("evas object size (w:%d,h:%d) was not set", w, h); } else { +#ifdef USE_TBM_SURFACE + esink->eo_size.x = x; + esink->eo_size.y = y; + esink->eo_size.w = w; + esink->eo_size.h = h; + GST_WARNING ("resize (x:%d, y:%d, w:%d, h:%d)", x, y, w, h); + gst_evas_image_sink_apply_geometry(esink); +#endif +#ifdef USE_FIMCC evas_object_image_fill_set(esink->eo, 0, 0, w, h); - GST_DEBUG ("evas object fill set (w:%d,h:%d)",w,h); + GST_DEBUG ("evas object fill set (w:%d,h:%d)", w, h); +#endif } + GST_DEBUG ("[LEAVE]"); } +#ifdef USE_TBM_SURFACE +static void +evas_callback_render_pre (void *data, Evas *e, void *event_info) +{ + GstEvasImageSink *esink = data; + if (!esink || !esink->eo) { + GST_WARNING("there is no esink info.... esink : %p, or eo is NULL returning", esink); + return; + } + if(esink->need_flush && esink->flush_buffer) + { + g_mutex_lock(esink->display_buffer_lock); + Evas_Native_Surface surf; + GstVideoRectangle result = {0}; + + evas_object_geometry_get(esink->eo, &esink->eo_size.x, &esink->eo_size.y, &esink->eo_size.w, &esink->eo_size.h); + if (!esink->eo_size.w || !esink->eo_size.h) { + GST_ERROR ("there is no information for evas object size"); + return; + } + gst_evas_image_sink_update_geometry(esink, &result); + if(!result.w || !result.h) + { + GST_ERROR("no information about geometry (%d, %d)", result.w, result.h); + return; + } + + if(esink->use_ratio) + { + surf.data.tizen.ratio = (float) esink->w / esink->h; + GST_LOG("set ratio for letter mode"); + } + evas_object_size_hint_align_set(esink->eo, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(esink->eo, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + if ( !esink->is_evas_object_size_set && esink->w > 0 && esink->h > 0) { + evas_object_image_size_set(esink->eo, esink->w, esink->h); + esink->is_evas_object_size_set = TRUE; + } + + if(result.x || result.y) + GST_LOG("coordinate x, y (%d, %d) for locating video to center", result.x, result.y); + + evas_object_image_fill_set(esink->eo, result.x, result.y, result.w, result.h); + + surf.type = EVAS_NATIVE_SURFACE_TBM; + surf.version = EVAS_NATIVE_SURFACE_VERSION; + surf.data.tizen.buffer = esink->flush_buffer->tbm_surf; + surf.data.tizen.rot = esink->rotate_angle; + surf.data.tizen.flip = esink->flip; + GST_DEBUG("use esink->flush buffer->tbm_surf (%p), rotate(%d), flip(%d)", + esink->flush_buffer->tbm_surf, esink->rotate_angle, esink->flip); + evas_object_image_native_surface_set(esink->eo, &surf); + g_mutex_unlock(esink->display_buffer_lock); + esink->need_flush = FALSE; + } + evas_object_image_pixels_dirty_set (esink->eo, EINA_TRUE); + GST_LOG("dirty set finish"); +} + +static gboolean +gst_evas_image_sink_release_source_buffer(GstEvasImageSink *esink) +{ + int i = 0; + tbm_bo_handle bo_handle; + g_mutex_lock(esink->display_buffer_lock); + + for(i = 0; i < NATIVE_BUFFER_NUM; i++) { + GST_WARNING("[reset] reset gst %p", esink->displaying_buffer[i].buffer); + esink->displaying_buffer[i].bo = NULL; + esink->displaying_buffer[i].tbm_surf = NULL; + esink->displaying_buffer[i].ref_count = 0; + } + + for(i = 0; i < SOURCE_BUFFER_NUM; i++) { + if(esink->src_buffer_info[i].bo) { + tbm_bo_unmap(esink->src_buffer_info[i].bo); + esink->src_buffer_info[i].bo = NULL; + } + + if (esink->src_buffer_info[i].tbm_surf) { + tbm_surface_destroy(esink->src_buffer_info[i].tbm_surf); + esink->src_buffer_info[i].tbm_surf = NULL; + } + } + + esink->is_buffer_allocated = FALSE; + esink->src_buf_idx = 0; + esink->prev_buf = NULL; + esink->prev_index = -1; + esink->cur_index = -1; + + esink->eo_size.x = esink->eo_size.y = + esink->eo_size.w = esink->eo_size.h = 0; + esink->use_ratio = FALSE; + esink->sent_buffer_cnt = 0; + + g_mutex_unlock(esink->display_buffer_lock); + + return TRUE; +} + +static gboolean +gst_evas_image_sink_allocate_source_buffer(GstEvasImageSink *esink) +{ + int size = 0; + int idx = 0; + tbm_bo bo; + tbm_bo_handle vaddr; + GstFlowReturn ret=GST_FLOW_OK; + + if (esink == NULL) { + GST_ERROR("handle is NULL"); + return FALSE; + } + + for(idx=0; idx < SOURCE_BUFFER_NUM; idx++) { + if(!esink->src_buffer_info[idx].tbm_surf) { + /* create tbm surface */ + esink->src_buffer_info[idx].tbm_surf = tbm_surface_create(esink->w, esink->h, esink->tbm_surface_format); + } + if(!esink->src_buffer_info[idx].tbm_surf) + { + GST_ERROR("tbm_surf is NULL!!"); + goto ALLOC_FAILED; + } + + /* get bo and size */ + bo = tbm_surface_internal_get_bo(esink->src_buffer_info[idx].tbm_surf, 0); + size = tbm_bo_size(bo); + if(!bo || !size) + { + GST_ERROR("bo(%p), size(%d)", bo, size); + goto ALLOC_FAILED; + } + esink->src_buffer_info[idx].bo = bo; + + vaddr = tbm_bo_map(bo, TBM_DEVICE_CPU, TBM_OPTION_READ|TBM_OPTION_WRITE); + if (!vaddr.ptr) { + GST_WARNING_OBJECT(esink, "get vaddr failed pointer = %p", vaddr.ptr); + tbm_bo_unmap(bo); + goto ALLOC_FAILED; + } else { + memset (vaddr.ptr, 0x0, size); + GST_LOG_OBJECT (esink, "tbm_bo_map(VADDR) finished, bo(%p), vaddr(%p)", bo, vaddr.ptr); + } + + esink->src_buffer_info[idx].usr_addr = vaddr.ptr; + + GST_WARNING_OBJECT(esink, "src buffer index:%d , tbm surface : %p", idx, esink->src_buffer_info[idx].tbm_surf); + } + + return TRUE; + +ALLOC_FAILED: + gst_evas_image_sink_release_source_buffer(esink); + return FALSE; +} +#endif static int evas_image_sink_get_size_from_caps (GstCaps *caps, int *w, int *h) @@ -306,19 +1050,23 @@ evas_image_sink_get_size_from_caps (GstCaps *caps, int *w, int *h) r = gst_structure_get_int (s, CAP_WIDTH, &width); if (r == FALSE) { + GST_DEBUG("fail to get width from caps"); return -1; } r = gst_structure_get_int (s, CAP_HEIGHT, &height); if (r == FALSE) { + GST_DEBUG("fail to get height from caps"); return -1; } *w = width; *h = height; + GST_DEBUG ("size w(%d), h(%d)", width, height); + return 0; } - +#ifdef USE_FIMCC static gboolean is_zerocopy_supported (Evas *e) { @@ -378,14 +1126,15 @@ evas_image_sink_event_parse_data (GstEvasImageSink *esink, GstEvent *event) return 0; } - +#endif static gboolean gst_evas_image_sink_event (GstBaseSink *sink, GstEvent *event) { +#ifdef USE_FIMCC GstEvasImageSink *esink = GST_EVASIMAGESINK (sink); GstMessage *msg; gchar *str; - +#endif switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: GST_DEBUG ("GST_EVENT_FLUSH_START"); @@ -396,6 +1145,7 @@ gst_evas_image_sink_event (GstBaseSink *sink, GstEvent *event) case GST_EVENT_EOS: GST_DEBUG ("GST_EVENT_EOS"); break; +#ifdef USE_FIMCC case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: if(!evas_image_sink_event_parse_data(esink, event)) { GST_DEBUG ("GST_EVENT_CUSTOM_DOWNSTREAM_OOB, present_data_addr:%x",esink->present_data_addr); @@ -403,6 +1153,7 @@ gst_evas_image_sink_event (GstBaseSink *sink, GstEvent *event) GST_ERROR ("evas_image_sink_event_parse_data() failed"); } break; +#endif default: break; } @@ -418,21 +1169,37 @@ gst_evas_image_sink_change_state (GstElement *element, GstStateChange transition { GstStateChangeReturn ret_state = GST_STATE_CHANGE_SUCCESS; GstEvasImageSink *esink = NULL; + GstFlowReturn ret=GST_FLOW_OK; esink = GST_EVASIMAGESINK(element); - int ret = 0; if(!esink) { GST_ERROR("can not get evasimagesink from element"); + return GST_STATE_CHANGE_FAILURE; } + switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: - GST_INFO ("*** STATE_CHANGE_NULL_TO_READY ***"); + GST_WARNING ("*** STATE_CHANGE_NULL_TO_READY ***"); +#ifdef USE_TBM_SURFACE + g_mutex_lock (esink->flow_lock); + if (!is_evas_image_object (esink->eo)) { + GST_ERROR_OBJECT (esink, "There is no evas image object.."); + g_mutex_unlock (esink->flow_lock); + return GST_STATE_CHANGE_FAILURE; + } + g_mutex_unlock (esink->flow_lock); +#endif break; case GST_STATE_CHANGE_READY_TO_PAUSED: - GST_INFO ("*** STATE_CHANGE_READY_TO_PAUSED ***"); + GST_WARNING ("*** STATE_CHANGE_READY_TO_PAUSED ***"); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - GST_INFO ("*** STATE_CHANGE_PAUSED_TO_PLAYING ***"); +#ifdef USE_TBM_SURFACE + /* Print debug log for 8 frame */ + esink->debuglog_cnt_showFrame = DEBUGLOG_DEFAULT_COUNT; + esink->debuglog_cnt_ecoreCbPipe = DEBUGLOG_DEFAULT_COUNT; +#endif + GST_WARNING ("*** STATE_CHANGE_PAUSED_TO_PLAYING ***"); break; default: break; @@ -442,16 +1209,49 @@ gst_evas_image_sink_change_state (GstElement *element, GstStateChange transition switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - GST_INFO ("*** STATE_CHANGE_PLAYING_TO_PAUSED ***"); + GST_WARNING ("*** STATE_CHANGE_PLAYING_TO_PAUSED ***"); break; case GST_STATE_CHANGE_PAUSED_TO_READY: - GST_INFO ("*** STATE_CHANGE_PAUSED_TO_READY ***"); + GST_WARNING ("*** STATE_CHANGE_PAUSED_TO_READY ***"); +#ifdef USE_TBM_SURFACE + Eina_Bool r; + /* flush buffer, we will copy last buffer to keep image data and reset buffer list */ + GST_WARNING("esink->enable_flush_buffer : %d", esink->enable_flush_buffer); + if(esink->enable_flush_buffer && esink->tbm_surface_format == TBM_FORMAT_NV12) + { + if (gst_esink_make_flush_buffer(esink) == FALSE) { + ret = gst_esink_epipe_reset(esink); + if(ret) { + GST_ERROR_OBJECT(esink, "evas epipe reset ret=%d, need to check",ret); + return GST_STATE_CHANGE_FAILURE; + } + gst_evas_image_sink_reset(esink); + } + } + else { + ret = gst_esink_epipe_reset(esink); + if(ret) { + GST_ERROR_OBJECT(esink, "evas epipe reset ret=%d, need to check",ret); + return GST_STATE_CHANGE_FAILURE; + } + if(esink->tbm_surface_format == TBM_FORMAT_NV12) { + gst_evas_image_sink_reset(esink); + } else if(esink->tbm_surface_format == TBM_FORMAT_YUV420) { + gst_evas_image_sink_release_source_buffer(esink); + } + } +#endif break; case GST_STATE_CHANGE_READY_TO_NULL: - GST_INFO ("*** STATE_CHANGE_READY_TO_NULL ***"); - evas_object_event_callback_del(esink->eo, EVAS_CALLBACK_DEL, evas_image_sink_cb_del_eo); - evas_object_event_callback_del(esink->eo, EVAS_CALLBACK_RESIZE, evas_image_sink_cb_resize_event); + GST_WARNING ("*** STATE_CHANGE_READY_TO_NULL ***"); +#ifdef USE_TBM_SURFACE + if(esink->flush_buffer) + _release_flush_buffer(esink); + EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK (evas_object_evas_get(esink->eo)); +#endif + EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK (esink->eo); if (esink->epipe) { + GST_DEBUG("ecore-pipe will delete"); ecore_pipe_del (esink->epipe); esink->epipe = NULL; } @@ -476,24 +1276,39 @@ gst_evas_image_sink_set_property (GObject *object, guint prop_id, const GValue * eo = g_value_get_pointer (value); if (is_evas_image_object (eo)) { if (eo != esink->eo) { - /* delete evas object callbacks registrated on a former evas image object */ - evas_object_event_callback_del (esink->eo, EVAS_CALLBACK_DEL, evas_image_sink_cb_del_eo); - evas_object_event_callback_del (esink->eo, EVAS_CALLBACK_RESIZE, evas_image_sink_cb_resize_event); - + Eina_Bool r; +#ifdef USE_TBM_SURFACE + EVASIMAGESINK_UNSET_EVAS_EVENT_CALLBACK (evas_object_evas_get(esink->eo)); +#endif + /* delete evas object callbacks registrated on a previous evas image object */ + EVASIMAGESINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK (esink->eo); esink->eo = eo; /* add evas object callbacks on a new evas image object */ - evas_object_event_callback_add (esink->eo, EVAS_CALLBACK_DEL, evas_image_sink_cb_del_eo, esink); - evas_object_event_callback_add (esink->eo, EVAS_CALLBACK_RESIZE, evas_image_sink_cb_resize_event, esink); - + EVASIMAGESINK_SET_EVAS_OBJECT_EVENT_CALLBACK (esink->eo, esink); +#ifdef USE_TBM_SURFACE + GST_WARNING("register render callback [esink : %p, esink->eo : %x]", esink, esink->eo); + EVASIMAGESINK_SET_EVAS_EVENT_CALLBACK (evas_object_evas_get(esink->eo), esink); + evas_object_geometry_get(esink->eo, &esink->eo_size.x, &esink->eo_size.y, &esink->eo_size.w, &esink->eo_size.h); + GST_WARNING ("evas object size (x:%d, y:%d, w:%d, h:%d)", esink->eo_size.x, esink->eo_size.y, esink->eo_size.w, esink->eo_size.h); +#endif +#ifdef USE_FIMCC esink->gl_zerocopy = is_zerocopy_supported (evas_object_evas_get (eo)); if (esink->gl_zerocopy) { evas_object_image_content_hint_set (esink->eo, EVAS_IMAGE_CONTENT_HINT_DYNAMIC); GST_DEBUG("Enable gl zerocopy"); } - GST_DEBUG("Evas Image Object(%x) is set",esink->eo); - evas_object_show(esink->eo); +#endif + GST_DEBUG("Evas Image Object(%x) is set", esink->eo); + esink->is_evas_object_size_set = FALSE; esink->object_show = TRUE; + esink->update_visibility = UPDATE_TRUE; + if (esink->epipe) { + r = ecore_pipe_write (esink->epipe, &esink->update_visibility, SIZE_FOR_UPDATE_VISIBILITY); + if (r == EINA_FALSE) { + GST_WARNING ("Failed to ecore_pipe_write() for updating visibility\n"); + } + } } } else { GST_ERROR ("Cannot set evas-object property: value is not an evas image object"); @@ -501,20 +1316,87 @@ gst_evas_image_sink_set_property (GObject *object, guint prop_id, const GValue * break; case PROP_EVAS_OBJECT_SHOW: + { + Eina_Bool r; esink->object_show = g_value_get_boolean (value); if( !is_evas_image_object(esink->eo) ) { GST_WARNING ("Cannot apply visible(show-object) property: cannot get an evas object\n"); break; } - if(!esink->object_show) { - evas_object_hide(esink->eo); - GST_INFO ("object hide.."); - } else { - evas_object_show(esink->eo); - GST_INFO ("object show.."); + esink->update_visibility = UPDATE_TRUE; + GST_LOG("esink->update_visibility : %d", esink->update_visibility); + if (esink->epipe) { + r = ecore_pipe_write (esink->epipe, &esink->update_visibility, SIZE_FOR_UPDATE_VISIBILITY); + if (r == EINA_FALSE) { + GST_WARNING ("Failed to ecore_pipe_write() for updating visibility)\n"); + } + } + break; + } +#ifdef USE_TBM_SURFACE + case PROP_ROTATE_ANGLE: + { + int rotate = 0; + rotate = g_value_get_int (value); + switch(rotate) + { + case DEGREE_0: + esink->rotate_angle = 0; + break; + case DEGREE_90: + esink->rotate_angle = 90; + break; + case DEGREE_180: + esink->rotate_angle = 180; + break; + case DEGREE_270: + esink->rotate_angle = 270; + break; + default: + break; } + GST_INFO("update rotate_angle : %d", esink->rotate_angle); break; + } + case PROP_DISPLAY_GEOMETRY_METHOD: + { + Eina_Bool r; + guint geometry = g_value_get_int (value); + if (esink->display_geometry_method != geometry) { + esink->display_geometry_method = geometry; + GST_INFO_OBJECT (esink, "Overlay geometry method update, display_geometry_method(%d)", esink->display_geometry_method); + } + g_mutex_lock(esink->display_buffer_lock); + + if(esink->cur_index!=-1 && esink->epipe) + { + GST_WARNING("apply property esink->cur_index =%d",esink->cur_index); + esink->displaying_buffer[esink->cur_index].ref_count++; + gst_buffer_ref(esink->displaying_buffer[esink->cur_index].buffer); + esink->sent_buffer_cnt++; + r = ecore_pipe_write (esink->epipe, &esink->cur_index, SIZE_FOR_NATIVE_INDEX); + + if (r == EINA_FALSE) { + GST_WARNING("ecore_pipe_write fail"); + esink->displaying_buffer[esink->cur_index].ref_count--; + gst_buffer_unref(esink->displaying_buffer[esink->cur_index].buffer); + esink->sent_buffer_cnt--; + } + } + g_mutex_unlock(esink->display_buffer_lock); + break; + } + case PROP_ENABLE_FLUSH_BUFFER: + esink->enable_flush_buffer = g_value_get_boolean(value); + break; + case PROP_FLIP: + { + esink->flip = g_value_get_int(value); + GST_INFO("update flip : %d", esink->flip); + break; + } +#endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -535,6 +1417,20 @@ gst_evas_image_sink_get_property (GObject *object, guint prop_id, GValue *value, case PROP_EVAS_OBJECT_SHOW: g_value_set_boolean (value, esink->object_show); break; +#ifdef USE_TBM_SURFACE + case PROP_ROTATE_ANGLE: + g_value_set_int (value, esink->rotate_angle); + break; + case PROP_DISPLAY_GEOMETRY_METHOD: + g_value_set_int (value, esink->display_geometry_method); + break; + case PROP_ENABLE_FLUSH_BUFFER: + g_value_set_boolean(value, esink->enable_flush_buffer); + break; + case PROP_FLIP: + g_value_set_int(value, esink->flip); + break; +#endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -547,6 +1443,8 @@ gst_evas_image_sink_set_caps (GstBaseSink *base_sink, GstCaps *caps) int r; int w, h; GstEvasImageSink *esink = GST_EVASIMAGESINK (base_sink); + GstStructure *structure = NULL; + guint32 format = 0; esink->is_evas_object_size_set = FALSE; r = evas_image_sink_get_size_from_caps (caps, &w, &h); @@ -555,6 +1453,29 @@ gst_evas_image_sink_set_caps (GstBaseSink *base_sink, GstCaps *caps) esink->h = h; GST_DEBUG ("set size w(%d), h(%d)", w, h); } + + structure = gst_caps_get_structure (caps, 0); + if (!structure) { + return FALSE; + } + + if (!gst_structure_get_fourcc (structure, "format", &format)) { + GST_DEBUG ("format(dst) is not set...it may be rgb series"); + } + + GST_DEBUG_OBJECT(esink, "source color format is %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (format)); + +#ifdef USE_TBM_SURFACE + if (format == GST_MAKE_FOURCC('S','N','1','2') || format == GST_MAKE_FOURCC('N','V','1','2') ) { + esink->tbm_surface_format = TBM_FORMAT_NV12; + } else if (format == GST_MAKE_FOURCC('I','4','2','0')){ + esink->tbm_surface_format = TBM_FORMAT_YUV420; + } else { + GST_ERROR("cannot parse fourcc format from caps."); + return FALSE; + } +#endif + return TRUE; } @@ -564,35 +1485,485 @@ gst_evas_image_sink_show_frame (GstVideoSink *video_sink, GstBuffer *buf) GstEvasImageSink *esink = GST_EVASIMAGESINK (video_sink); Eina_Bool r; + GST_LOG("[ENTER] show frame"); +#ifdef USE_TBM_SURFACE + if (!gst_evas_image_sink_ref_count (buf)) + { + GST_WARNING("ref count is 0.. skip show frame"); + return GST_FLOW_OK; + } +#endif g_mutex_lock (instance_lock); +#ifdef USE_FIMCC if (esink->present_data_addr == -1) { /* if present_data_addr is -1, we don't use this member variable */ - } else if (esink->present_data_addr != GST_BUFFER_DATA (buf)) { - GST_WARNING ("skip rendering this buffer, present_data_addr:%x, GST_BUFFER_DATA(buf):%x", esink->present_data_addr,GST_BUFFER_DATA(buf)); + } else if (esink->present_data_addr != DO_RENDER_FROM_FIMC) { + GST_WARNING ("skip rendering this buffer, present_data_addr:%d, DO_RENDER_FROM_FIMC:%d", esink->present_data_addr, DO_RENDER_FROM_FIMC); g_mutex_unlock (instance_lock); - return; + return GST_FLOW_OK; } +#endif if (!esink->epipe) { - esink->epipe = ecore_pipe_add (evas_image_sink_cb_pipe, esink); + esink->epipe = ecore_pipe_add ((Ecore_Pipe_Cb)evas_image_sink_cb_pipe, esink); if (!esink->epipe) { GST_ERROR ("ecore-pipe create failed"); + g_mutex_unlock (instance_lock); return GST_FLOW_ERROR; } + GST_DEBUG("ecore-pipe create success"); } - if (esink->object_show) { +#ifdef USE_TBM_SURFACE + int i = 0; + int index = -1; + SCMN_IMGB *scmn_imgb = NULL; + gboolean exist_bo = FALSE; + + GST_LOG ("BUF: gst_buf= %p ts= %" GST_TIME_FORMAT " size= %lu, mallocdata= %p", + buf, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf), GST_BUFFER_MALLOCDATA(buf)); + + if(esink->tbm_surface_format == TBM_FORMAT_NV12) { + /* get received buffer informations */ + scmn_imgb = (SCMN_IMGB *)GST_BUFFER_MALLOCDATA(buf); + if (!scmn_imgb) { + GST_WARNING("scmn_imgb is NULL. Skip..." ); + g_mutex_unlock (instance_lock); + return GST_FLOW_OK; + } + if (scmn_imgb->buf_share_method == BUF_SHARE_METHOD_TIZEN_BUFFER) + { + /* check whether bo is new or not */ + for(i=0; idisplaying_buffer[i].bo==scmn_imgb->bo[0]) + { + GST_DEBUG("it is already saved bo %p (index num : %d)", esink->displaying_buffer[i].bo, i); + index = i; + exist_bo = TRUE; + break; + } + else + exist_bo = FALSE; + } + /* keep bo */ + if(!exist_bo) + { + /* find empty buffer space for indexing */ + for(i=0; idisplaying_buffer[i].bo) + { + index = i; + break; + } + } + if(index!=-1) + { + /* keep informations */ + esink->displaying_buffer[index].buffer = buf; + esink->displaying_buffer[index].bo = scmn_imgb->bo[0]; + GST_WARNING("TBM bo %p / gst_buf %p", esink->displaying_buffer[index].bo, esink->displaying_buffer[index].buffer); + + /* create new tbm surface */ + esink->displaying_buffer[index].tbm_surf = tbm_surface_internal_create_with_bos(esink->w, esink->h, + TBM_FORMAT_NV12, esink->displaying_buffer[index].bo, TBM_BO_DEFAULT); + if(!esink->displaying_buffer[index].tbm_surf) + { + GST_WARNING("there is no tbm surface.. bo : %p, gst_buf : %p", esink->displaying_buffer[index].bo, esink->displaying_buffer[index].buffer); + g_mutex_unlock (instance_lock); + return GST_FLOW_OK; + } + + GST_WARNING("create tbm surface : %p", esink->displaying_buffer[index].tbm_surf); + } + } + /* because it has same bo, use existing tbm surface */ + else + { + if(index!=-1) + { + esink->displaying_buffer[index].buffer = buf; + GST_DEBUG("existing tbm surface %p.. bo %p, gst_buf %p", esink->displaying_buffer[index].tbm_surf, esink->displaying_buffer[index].bo, esink->displaying_buffer[index].buffer); + exist_bo = FALSE; + } + } + + /* if it couldn't find proper index */ + if(index == -1) + GST_WARNING("all spaces are using!!!"); + else + GST_DEBUG("selected buffer index %d", index); + + } + else if (scmn_imgb->buf_share_method == BUF_SHARE_METHOD_FLUSH_BUFFER) + { + /* flush buffer, we will copy last buffer to keep image data and reset buffer list */ + GST_WARNING_OBJECT(esink, "FLUSH_BUFFER: gst_buf= %p ts= %" GST_TIME_FORMAT " size= %lu, mallocdata= %p", + buf, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf), GST_BUFFER_MALLOCDATA(buf)); + gst_buffer_ref (buf); + if (gst_esink_make_flush_buffer(esink)) + GST_WARNING("made flush buffer"); + gst_buffer_unref (buf); + } + else + { + GST_ERROR("it is not TBM buffer.. %d", scmn_imgb->buf_share_method); + } + } else if(esink->tbm_surface_format == TBM_FORMAT_YUV420) { + static int skip_count_i420=0; + if(esink->sent_buffer_cnt >= MAX_ECOREPIPE_BUFFER_CNT) { + if(!(skip_count_i420++%20)) { + GST_WARNING("[%d]EA buffer was already sent to ecore pipe, and %d frame skipped", esink->sent_buffer_cnt,skip_count_i420); + } + g_mutex_unlock (instance_lock); + return GST_FLOW_OK; + } + + if(!esink->is_buffer_allocated) { + /* Allocate TBM buffer for non-zero copy case */ + if(!gst_evas_image_sink_allocate_source_buffer(esink)) { + GST_ERROR_OBJECT(esink, "Buffer allocation failed"); + g_mutex_unlock (instance_lock); + return GST_FLOW_ERROR; + } + esink->is_buffer_allocated = TRUE; + } + + skip_count_i420 = 0; //for skip log in I420 + + /* check whether bo is new or not */ + for(i=0; idisplaying_buffer[i].bo == esink->src_buffer_info[esink->src_buf_idx].bo) { + GST_DEBUG_OBJECT(esink, "it is already saved bo %p (index num : %d)", esink->displaying_buffer[i].bo, i); + index = i; + exist_bo = TRUE; + break; + } + else + exist_bo = FALSE; + } + + /* keep bo */ + if(!exist_bo) { + /* find empty buffer space for indexing */ + for(i=0; idisplaying_buffer[i].bo) { + index = i; + break; + } + } + + if(index!=-1) { + /* keep informations */ + esink->displaying_buffer[index].bo = esink->src_buffer_info[esink->src_buf_idx].bo; + esink->displaying_buffer[index].tbm_surf = esink->src_buffer_info[esink->src_buf_idx].tbm_surf; + + GST_DEBUG_OBJECT(esink, "gst_buf %p, TBM bo %p.. gst_buf vaddr %p .. src data size(%lu)", + buf, esink->displaying_buffer[index].bo, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf)); + + memcpy(esink->src_buffer_info[esink->src_buf_idx].usr_addr, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf)); + } + } else { + /* because it has same bo, use existing tbm surface */ + if(index!=-1) { + memcpy(esink->src_buffer_info[esink->src_buf_idx].usr_addr, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf)); + + GST_DEBUG_OBJECT(esink, "existing tbm surface %p.. gst_buf %p, bo %p, gst_buf vaddr %p", + esink->displaying_buffer[index].tbm_surf, buf, esink->displaying_buffer[index].bo, GST_BUFFER_DATA(buf)); + exist_bo = FALSE; + } + } + + /* if it couldn't find proper index */ + if(index == -1) + GST_WARNING_OBJECT(esink, "all spaces are being used!!!"); + else + GST_DEBUG_OBJECT(esink, "selected buffer index %d", index); + + } else { + GST_ERROR_OBJECT(esink, "unsupported color format"); + g_mutex_unlock (instance_lock); + return GST_FLOW_ERROR; + } +#endif + if (esink->object_show && index != -1) { +#ifdef USE_FIMCC gst_buffer_ref (buf); - __ta__("evasimagesink ecore_pipe_write", r = ecore_pipe_write (esink->epipe, &buf, sizeof (GstBuffer *));); + r = ecore_pipe_write (esink->epipe, &buf, sizeof (GstBuffer *)); if (r == EINA_FALSE) { + //add error handling + GST_WARNING("ecore_pipe_write fail"); gst_buffer_unref (buf); } - GST_DEBUG ("after ecore_pipe_write()"); +#endif +#ifdef USE_TBM_SURFACE + int old_curidx=esink->cur_index ; + static int skip_count=0; + g_mutex_lock(esink->display_buffer_lock); + + if(esink->tbm_surface_format == TBM_FORMAT_NV12 && scmn_imgb->buf_share_method != BUF_SHARE_METHOD_FLUSH_BUFFER) { + if (esink->sent_buffer_cnt < MAX_ECOREPIPE_BUFFER_CNT) { + GST_LOG("[show_frame] before refcount : %d .. gst_buf : %p", gst_evas_image_sink_ref_count (buf), buf); + gst_buffer_ref (buf); + esink->displaying_buffer[index].ref_count++; + esink->cur_index = index; + GST_LOG("index %d set refcount increase as %d", index,esink->displaying_buffer[index].ref_count); + GST_LOG("[show_frame] after refcount : %d .. gst_buf : %p", gst_evas_image_sink_ref_count (buf), buf); + + /* Print debug log for 8 frame */ + if(esink->debuglog_cnt_showFrame > 0) + { + GST_WARNING("(%d) ecore_pipe_write index[%d] gst_buf : %p", DEBUGLOG_DEFAULT_COUNT-(esink->debuglog_cnt_showFrame), + esink->cur_index, esink->displaying_buffer[esink->cur_index].buffer); + esink->debuglog_cnt_showFrame--; + } + + esink->sent_buffer_cnt++; + skip_count =0 ; + + r = ecore_pipe_write (esink->epipe, &esink->cur_index , SIZE_FOR_NATIVE_INDEX); + + if (r == EINA_FALSE) { + GST_LOG("[show_frame] before refcount : %d .. gst_buf : %p", gst_evas_image_sink_ref_count (buf), buf); + esink->cur_index = old_curidx; + if(esink->displaying_buffer[index].ref_count) + { + esink->displaying_buffer[index].ref_count--; + esink->sent_buffer_cnt--; + gst_buffer_unref(buf); + GST_ERROR("finish unreffing"); + } + } + } else { + /* If buffer count which is sent to ecore pipe, is upper 3, Print Error log */ + if(!(skip_count++%20)) { + GST_WARNING("[%d]EA buffer was already sent to ecore pipe, and %d frame skipped", esink->sent_buffer_cnt,skip_count); + } + } + } else if (esink->tbm_surface_format == TBM_FORMAT_YUV420) { + esink->cur_index = index; + GST_LOG("index %d", index); + GST_LOG("[show_frame] gst_buf vaddr %p", esink->src_buffer_info[esink->src_buf_idx].usr_addr); + + /* Print debug log for 8 frame */ + if(esink->debuglog_cnt_showFrame > 0) + { + GST_WARNING("(%d) ecore_pipe_write : index[%d], bo[%p], tbm_surf[%p], gst_buf[%p], gst_buf vaddr [%p]", DEBUGLOG_DEFAULT_COUNT-(esink->debuglog_cnt_showFrame), + esink->cur_index, esink->displaying_buffer[index].bo, esink->displaying_buffer[index].tbm_surf, buf, esink->src_buffer_info[esink->src_buf_idx].usr_addr); + esink->debuglog_cnt_showFrame--; + } + + esink->sent_buffer_cnt++; + + esink->src_buf_idx++; + r = ecore_pipe_write (esink->epipe, &esink->cur_index , SIZE_FOR_NATIVE_INDEX); + + if (r == EINA_FALSE) { + esink->cur_index = old_curidx; + esink->sent_buffer_cnt--; + esink->src_buf_idx--; + GST_ERROR("ecore_pipe_write is failed. index[%d], bo[%p], tbm_surf[%p], gst_buf vaddr [%p]", + index, esink->displaying_buffer[index].bo, esink->displaying_buffer[index].tbm_surf, esink->src_buffer_info[esink->src_buf_idx].usr_addr); + } + esink->src_buf_idx = esink->src_buf_idx % SOURCE_BUFFER_NUM; + } + + g_mutex_unlock(esink->display_buffer_lock); +#endif + GST_DEBUG ("ecore_pipe_write() was called with gst_buf= %p.. gst_buf Vaddr=%p, mallocdata= %p", buf, GST_BUFFER_DATA(buf), GST_BUFFER_MALLOCDATA(buf)); } else { - GST_DEBUG ("skip ecore_pipe_write()"); + GST_WARNING ("skip ecore_pipe_write(). becuase of esink->object_show(%d) index(%d)", esink->object_show, index); } g_mutex_unlock (instance_lock); + GST_DEBUG ("Leave"); return GST_FLOW_OK; } +#ifdef USE_TBM_SURFACE +static GstFlowReturn +gst_esink_epipe_reset(GstEvasImageSink *esink) +{ + if (esink == NULL) { + GST_ERROR("handle is NULL"); + return GST_FLOW_ERROR; + } + + if (esink->epipe) { + GST_DEBUG("ecore-pipe will delete"); + ecore_pipe_del (esink->epipe); + esink->epipe = NULL; + } + + if (!esink->epipe) { + esink->epipe = ecore_pipe_add ((Ecore_Pipe_Cb)evas_image_sink_cb_pipe, esink); + if (!esink->epipe) { + GST_ERROR ("ecore-pipe create failed"); + return GST_FLOW_ERROR; + } + GST_DEBUG("ecore-pipe create success"); + } + + return GST_FLOW_OK; +} + + +static gboolean gst_esink_make_flush_buffer(GstEvasImageSink *esink) +{ + GstEvasImageFlushBuffer *flush_buffer = NULL; + GstEvasImageDisplayingBuffer *display_buffer = NULL; + int size = 0; + tbm_bo bo; + tbm_bo_handle vaddr_src; + tbm_bo_handle vaddr_dst; + GstFlowReturn ret=GST_FLOW_OK; + int src_size = 0; + + if (esink == NULL) { + GST_ERROR("handle is NULL"); + return FALSE; + } + + if (esink->cur_index == -1) { + GST_WARNING_OBJECT(esink, "there is no remained buffer"); + return FALSE; + } + + if(esink->flush_buffer) + _release_flush_buffer(esink); + + /* malloc buffer */ + flush_buffer = (GstEvasImageFlushBuffer *)malloc(sizeof(GstEvasImageFlushBuffer)); + if (flush_buffer == NULL) { + GST_ERROR_OBJECT(esink, "GstEvasImageFlushBuffer alloc failed"); + return FALSE; + } + + memset(flush_buffer, 0x0, sizeof(GstEvasImageFlushBuffer)); + + display_buffer = &(esink->displaying_buffer[esink->cur_index]); + GST_WARNING_OBJECT(esink, "cur_index [%d]", + esink->cur_index); + if(!display_buffer || !display_buffer->bo) + { + GST_WARNING("display_buffer(%p) or bo (%p) is NULL!!", display_buffer, display_buffer->bo); + goto FLUSH_BUFFER_FAILED; + } + + /* create tbm surface */ + flush_buffer->tbm_surf = tbm_surface_create(esink->w, esink->h, TBM_FORMAT_NV12); + + if(!flush_buffer->tbm_surf) + { + GST_ERROR("tbm_surf is NULL!!"); + goto FLUSH_BUFFER_FAILED; + } + + /* get bo and size */ + bo = tbm_surface_internal_get_bo(flush_buffer->tbm_surf, 0); + size = tbm_bo_size(bo); + if(!bo || !size) + { + GST_ERROR("bo(%p), size(%d)", bo, size); + goto FLUSH_BUFFER_FAILED; + } + flush_buffer->bo = bo; + + src_size = display_buffer->buffer->size; + + vaddr_src = tbm_bo_map(display_buffer->bo, TBM_DEVICE_CPU, TBM_OPTION_READ|TBM_OPTION_WRITE); + vaddr_dst = tbm_bo_map(bo, TBM_DEVICE_CPU, TBM_OPTION_READ|TBM_OPTION_WRITE); + if (!vaddr_src.ptr || !vaddr_dst.ptr) { + GST_WARNING_OBJECT(esink, "get vaddr failed src %p, dst %p", vaddr_src.ptr, vaddr_dst.ptr); + if (vaddr_src.ptr) { + tbm_bo_unmap(display_buffer->bo); + } + if (vaddr_dst.ptr) { + tbm_bo_unmap(bo); + } + goto FLUSH_BUFFER_FAILED; + } else { + memset (vaddr_dst.ptr, 0x0, size); + GST_WARNING_OBJECT (esink, "tbm_bo_map(VADDR) finished, bo(%p), vaddr(%p)", bo, vaddr_dst.ptr); + } + + /* copy buffer */ + memcpy(vaddr_dst.ptr, vaddr_src.ptr, src_size); + + tbm_bo_unmap(display_buffer->bo); + tbm_bo_unmap(bo); + + GST_WARNING_OBJECT(esink, "copy done.. tbm surface : %p src_size : %d", flush_buffer->tbm_surf, src_size); + + esink->flush_buffer = flush_buffer; + + /* initialize buffer list */ + if (esink->object_show) + esink->need_flush = TRUE; + + ret = gst_esink_epipe_reset(esink); + if(ret) { + GST_ERROR_OBJECT(esink, "evas epipe reset ret=%d, need to check",ret); + return FALSE; + } else { + GST_ERROR_OBJECT(esink, "evas epipe reset success"); + } + + gst_evas_image_sink_reset(esink); + + + return TRUE; + +FLUSH_BUFFER_FAILED: + if (flush_buffer) { + if(flush_buffer->tbm_surf) + { + tbm_surface_destroy(flush_buffer->tbm_surf); + flush_buffer->tbm_surf = NULL; + } + + free(flush_buffer); + flush_buffer = NULL; + } + return FALSE; +} + + static void _release_flush_buffer(GstEvasImageSink *esink) +{ + if (esink == NULL || + esink->flush_buffer == NULL) { + GST_WARNING("handle is NULL"); + return; + } + + GST_WARNING_OBJECT(esink, "release FLUSH BUFFER start"); + if (esink->flush_buffer->bo) { + esink->flush_buffer->bo = NULL; + } + if(esink->flush_buffer->tbm_surf) { + tbm_surface_destroy(esink->flush_buffer->tbm_surf); + esink->flush_buffer->tbm_surf = NULL; + } + + GST_WARNING_OBJECT(esink, "release FLUSH BUFFER done"); + + free(esink->flush_buffer); + esink->flush_buffer = NULL; + + return; +} +#endif +#ifdef DUMP_IMG +int util_write_rawdata(const char *file, const void *data, unsigned int size) +{ + FILE *fp; + fp = fopen(file, "wb"); + if (fp == NULL) + return -1; + fwrite((char*)data, sizeof(char), size, fp); + fclose(fp); + return 0; +} +#endif + static gboolean evas_image_sink_init (GstPlugin *evasimagesink) { diff --git a/evasimagesink/src/gstevasimagesink.h b/evasimagesink/src/gstevasimagesink.h old mode 100644 new mode 100755 index 1e2e19b..1633044 --- a/evasimagesink/src/gstevasimagesink.h +++ b/evasimagesink/src/gstevasimagesink.h @@ -24,12 +24,17 @@ #ifndef __GST_EVASIMAGESINK_H__ #define __GST_EVASIMAGESINK_H__ +//#define USE_TBM_SURFACE +#define USE_FIMCC + #include #include #include #include -#include - +#ifdef USE_TBM_SURFACE +#include +#include +#endif G_BEGIN_DECLS /* #defines don't like whitespacey bits */ @@ -46,7 +51,71 @@ G_BEGIN_DECLS typedef struct _GstEvasImageSink GstEvasImageSink; typedef struct _GstEvasImageSinkClass GstEvasImageSinkClass; +#ifdef USE_TBM_SURFACE +typedef struct _GstEvasImageDisplayingBuffer GstEvasImageDisplayingBuffer; +typedef struct _GstEvasImageFlushBuffer GstEvasImageFlushBuffer; +typedef enum { + BUF_SHARE_METHOD_NONE = -1, + BUF_SHARE_METHOD_PADDR = 0, + BUF_SHARE_METHOD_FD, + BUF_SHARE_METHOD_TIZEN_BUFFER, + BUF_SHARE_METHOD_FLUSH_BUFFER +} buf_share_method_t; + +enum { + DEGREE_0 = 0, + DEGREE_90, + DEGREE_180, + DEGREE_270, + DEGREE_NUM, +}; + +enum { + DISP_GEO_METHOD_LETTER_BOX = 0, + DISP_GEO_METHOD_ORIGIN_SIZE, + DISP_GEO_METHOD_FULL_SCREEN, + DISP_GEO_METHOD_CROPPED_FULL_SCREEN, + DISP_GEO_METHOD_ORIGIN_SIZE_OR_LETTER_BOX, + DISP_GEO_METHOD_CUSTOM_DST_ROI, + DISP_GEO_METHOD_NUM, +}; + +enum { + FLIP_NONE = 0, + FLIP_HORIZONTAL, + FLIP_VERTICAL, + FLIP_BOTH, + FLIP_NUM +}; +struct buffer_info { + uint64_t usr_addr; + uint64_t size; + void *bo; + tbm_surface_h tbm_surf; +}; + +/* _GstEvasDisplayingBuffer + * + * buffer : manage ref count by got index through comparison + * bo : compare with buffer from codec for getting index + * n_buffer : compare with buffer from evas for getting index + * ref_count : decide whether it unref buffer or not in gst_evas_image_sink_fini/reset + */ +struct _GstEvasImageDisplayingBuffer { + GstBuffer *buffer; + void *bo; + tbm_surface_h tbm_surf; + int ref_count; +}; + +struct _GstEvasImageFlushBuffer { + void *bo; + tbm_surface_h tbm_surf; +}; +#define NATIVE_BUFFER_NUM 20 +#define SOURCE_BUFFER_NUM 8 +#endif struct _GstEvasImageSink { GstVideoSink element; @@ -56,12 +125,37 @@ struct _GstEvasImageSink Evas_Coord w; Evas_Coord h; gboolean object_show; + gchar update_visibility; gboolean gl_zerocopy; GstBuffer *oldbuf; gboolean is_evas_object_size_set; guint present_data_addr; +#ifdef USE_TBM_SURFACE + GMutex *display_buffer_lock; + GMutex *flow_lock; + GstEvasImageDisplayingBuffer displaying_buffer[NATIVE_BUFFER_NUM]; + GstVideoRectangle eo_size; + gboolean use_ratio; + guint rotate_angle; + guint display_geometry_method; + guint flip; + GstBuffer *prev_buf; + gint prev_index; + gint cur_index; + gboolean need_flush; + gboolean enable_flush_buffer; + GstEvasImageFlushBuffer *flush_buffer; + gint sent_buffer_cnt; + gint debuglog_cnt_showFrame; + gint debuglog_cnt_ecoreCbPipe; + + tbm_format tbm_surface_format; + struct buffer_info src_buffer_info[NATIVE_BUFFER_NUM]; + guint src_buf_idx; + gboolean is_buffer_allocated; +#endif }; struct _GstEvasImageSinkClass diff --git a/evaspixmapsink/Makefile.am b/evaspixmapsink/Makefile.am new file mode 100644 index 0000000..4caa9d5 --- /dev/null +++ b/evaspixmapsink/Makefile.am @@ -0,0 +1,14 @@ +plugin_LTLIBRARIES = libgstevaspixmapsink.la + +libgstevaspixmapsink_la_SOURCES = evaspixmapsink.c +libgstevaspixmapsink_la_CFLAGS = $(GST_CFLAGS) $(X_CFLAGS) $(EFL_CFLAGS) \ + $(XFIXES_CFLAGS) $(DRI2PROTO_CFLAGS) $(DRI2_CFLAGS) $(X11_CFLAGS) $(XDAMAGE_CFLAGS) $(XV_CFLAGS) $(TBM_CFLAGS) $(DRM_CFLAGS) $(DRM_DEVEL_CFLAGS) +libgstevaspixmapsink_la_LIBADD = \ + $(GST_LIBS) -lgstvideo-$(GST_MAJORMINOR) -lgstinterfaces-$(GST_MAJORMINOR) \ + $(X_LIBS) $(XVIDEO_LIBS) $(XSHM_LIBS) $(LIBM) \ + $(EFL_LIBS) \ + $(XFIXES_LIBS) $(DRI2PROTO_LIBS) $(DRI2_LIBS) $(X11_LIBS) $(XDAMAGE_LIBS) $(XV_LIBS) $(TBM_LIBS) $(DRM_LIBS) $(DRM_DEVEL_LIBS) +libgstevaspixmapsink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstxvimagesink_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = evaspixmapsink.h diff --git a/evaspixmapsink/evaspixmapsink.c b/evaspixmapsink/evaspixmapsink.c new file mode 100755 index 0000000..3e25891 --- /dev/null +++ b/evaspixmapsink/evaspixmapsink.c @@ -0,0 +1,4921 @@ +/* + * EvasPixmapSink + * + * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Sangchul Lee + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Our interfaces */ +#include +#include +#include +/* Helper functions */ +#include + +/* Object header */ +#include "evaspixmapsink.h" + +#include + +/* Samsung extension headers */ +/* For xv extension header for buffer transfer (output) */ +#include "xv_types.h" + +/* headers for drm */ +#include +#include +#include +#include +#include +#include +#include + +/* max channel count *********************************************************/ +#define SCMN_IMGB_MAX_PLANE (4) + +/* image buffer definition *************************************************** + + +------------------------------------------+ --- + | | ^ + | a[], p[] | | + | +---------------------------+ --- | | + | | | ^ | | + | |<---------- w[] ---------->| | | | + | | | | | | + | | | | + | | | h[] | e[] + | | | | + | | | | | | + | | | | | | + | | | v | | + | +---------------------------+ --- | | + | | v + +------------------------------------------+ --- + + |<----------------- s[] ------------------>| +*/ + +typedef struct +{ + /* width of each image plane */ + int w[SCMN_IMGB_MAX_PLANE]; + /* height of each image plane */ + int h[SCMN_IMGB_MAX_PLANE]; + /* stride of each image plane */ + int s[SCMN_IMGB_MAX_PLANE]; + /* elevation of each image plane */ + int e[SCMN_IMGB_MAX_PLANE]; + /* user space address of each image plane */ + void *a[SCMN_IMGB_MAX_PLANE]; + /* physical address of each image plane, if needs */ + void *p[SCMN_IMGB_MAX_PLANE]; + /* color space type of image */ + int cs; + /* left postion, if needs */ + int x; + /* top position, if needs */ + int y; + /* to align memory */ + int __dummy2; + /* arbitrary data */ + int data[16]; + /* dma buf fd */ + int dma_buf_fd[SCMN_IMGB_MAX_PLANE]; + /* buffer share method */ + int buf_share_method; + /* Y plane size in case of ST12 */ + int y_size; + /* UV plane size in case of ST12 */ + int uv_size; + /* Tizen buffer object */ + void *bo[SCMN_IMGB_MAX_PLANE]; + /* JPEG data */ + void *jpeg_data; + /* JPEG size */ + int jpeg_size; + /* TZ memory buffer */ + int tz_enable; +} SCMN_IMGB; + +/* Debugging category */ +#include +GST_DEBUG_CATEGORY_STATIC (gst_debug_evaspixmapsink); +#define GST_CAT_DEFAULT gst_debug_evaspixmapsink +GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE); + +enum { + UPDATE_FALSE, + UPDATE_TRUE +}; + +enum { + DEGREE_0, + DEGREE_90, + DEGREE_180, + DEGREE_270, + DEGREE_NUM, +}; + +enum { + DISP_GEO_METHOD_LETTER_BOX = 0, + DISP_GEO_METHOD_ORIGIN_SIZE, + DISP_GEO_METHOD_FULL_SCREEN, + DISP_GEO_METHOD_CROPPED_FULL_SCREEN, + DISP_GEO_METHOD_CUSTOM_ROI, + DISP_GEO_METHOD_NUM, +}; + +enum { + FLIP_NONE = 0, + FLIP_HORIZONTAL, + FLIP_VERTICAL, + FLIP_BOTH, + FLIP_NUM, +}; + +#define DEF_DISPLAY_GEOMETRY_METHOD DISP_GEO_METHOD_LETTER_BOX +#define DEF_DISPLAY_FLIP FLIP_NONE +#define GST_TYPE_EVASPIXMAPSINK_FLIP (gst_evaspixmapsink_flip_get_type()) +#define GST_TYPE_EVASPIXMAPSINK_ROTATE_ANGLE (gst_evaspixmapsink_rotate_angle_get_type()) +#define GST_TYPE_EVASPIXMAPSINK_DISPLAY_GEOMETRY_METHOD (gst_evaspixmapsink_display_geometry_method_get_type()) +#define SIZE_FOR_UPDATE_VISIBILITY sizeof(gchar) +#define EPIPE_REQUEST_LIMIT 20 + +#ifdef DUMP_IMG +int util_write_rawdata (const char *file, const void *data, unsigned int size); +typedef struct _BufferInfo +{ + Display *dpy; + Pixmap pixmap; + int width; + int height; + + /* Dri2 */ + int drm_fd; + tbm_bufmgr bufmgr; + void *virtual; + DRI2Buffer *dri2_buffers; + tbm_bo bo; +} BufferInfo; + +static BufferInfo *g_bufinfo[NUM_OF_PIXMAP]; +void * gst_evaspixmapsink_get_buffer (Display *dpy, Pixmap pixmap, int width, int height, int i); +void gst_evaspixmapsink_free_buffer (int i); +static Bool gst_evaspixmapsink_init_dri2 (BufferInfo *bufinfo); +int g_cnt; +#endif + +/* macro ******************************************************/ +#define EVASPIXMAPSINK_SET_PIXMAP_ID_TO_GEM_INFO( x_evaspixmapsink, x_pixmap_id ) \ +do \ +{ \ + int i = 0; \ + XV_DATA_PTR data = (XV_DATA_PTR)x_evaspixmapsink->evas_pixmap_buf->xvimage->data; \ + if (data->YBuf > 0) { \ + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { \ + if (evaspixmapsink->gem_info[i].gem_name == data->YBuf) { \ + evaspixmapsink->gem_info[i].ref_pixmap = x_pixmap_id; \ + GST_LOG_OBJECT (x_evaspixmapsink,"pixmap(%d) is marked at index(%d) of gem_info(YBuf)->gem_handle(%d)", x_pixmap_id, i, evaspixmapsink->gem_info[i].gem_handle); \ + break; \ + } \ + } \ + } \ + if (data->CbBuf > 0) { \ + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { \ + if (evaspixmapsink->gem_info[i].gem_name == data->CbBuf) { \ + evaspixmapsink->gem_info[i].ref_pixmap = x_pixmap_id; \ + GST_LOG_OBJECT (x_evaspixmapsink,"pixmap(%d) is marked at index(%d) of gem_info(CbBuf)->gem_handle(%d)", x_pixmap_id, i, evaspixmapsink->gem_info[i].gem_handle); \ + break; \ + } \ + } \ + } \ + if (data->CrBuf > 0) { \ + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { \ + if (evaspixmapsink->gem_info[i].gem_name == data->CrBuf) { \ + evaspixmapsink->gem_info[i].ref_pixmap = x_pixmap_id; \ + GST_LOG_OBJECT (x_evaspixmapsink,"pixmap(%d) is marked at index(%d) of gem_info(CrBuf)->gem_handle(%d)", x_pixmap_id, i, evaspixmapsink->gem_info[i].gem_handle); \ + break; \ + } \ + } \ + } \ +}while(0) + +#define EVASPIXMAPSINK_SET_EVAS_OBJECT_EVENT_CALLBACK( x_evas_image_object, x_usr_data ) \ +do \ +{ \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_DEL, evas_callback_del_event, x_usr_data); \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_RESIZE, evas_callback_resize_event, x_usr_data); \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_SHOW, evas_callback_show_event, x_usr_data); \ + evas_object_event_callback_add (x_evas_image_object, EVAS_CALLBACK_HIDE, evas_callback_hide_event, x_usr_data); \ +}while(0) + +#define EVASPIXMAPSINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK( x_evas_image_object ) \ +do \ +{ \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_DEL, evas_callback_del_event); \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_RESIZE, evas_callback_resize_event); \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_SHOW, evas_callback_show_event); \ + evas_object_event_callback_del (x_evas_image_object, EVAS_CALLBACK_HIDE, evas_callback_hide_event); \ +}while(0) + +static GType +gst_evaspixmapsink_flip_get_type(void) +{ + static GType evaspixmapsink_flip_type = 0; + static const GEnumValue flip_type[] = { + { FLIP_NONE, "Flip NONE", "FLIP_NONE"}, + { FLIP_HORIZONTAL, "Flip HORIZONTAL", "FLIP_HORIZONTAL"}, + { FLIP_VERTICAL, "Flip VERTICAL", "FLIP_VERTICAL"}, + { FLIP_BOTH, "Flip BOTH", "FLIP_BOTH"}, + { FLIP_NUM, NULL, NULL}, + }; + + if (!evaspixmapsink_flip_type) { + evaspixmapsink_flip_type = g_enum_register_static("GstEvasPixmapSinkFlipType", flip_type); + } + + return evaspixmapsink_flip_type; +} + +static GType +gst_evaspixmapsink_rotate_angle_get_type(void) +{ + static GType evaspixmapsink_rotate_angle_type = 0; + static const GEnumValue rotate_angle_type[] = { + { 0, "No rotate", "DEGREE_0"}, + { 1, "Rotate 90 degree", "DEGREE_90"}, + { 2, "Rotate 180 degree", "DEGREE_180"}, + { 3, "Rotate 270 degree", "DEGREE_270"}, + { 4, NULL, NULL}, + }; + + if (!evaspixmapsink_rotate_angle_type) { + evaspixmapsink_rotate_angle_type = g_enum_register_static("GstEvasPixmapSinkRotateAngleType", rotate_angle_type); + } + + return evaspixmapsink_rotate_angle_type; +} + +static GType +gst_evaspixmapsink_display_geometry_method_get_type(void) +{ + static GType evaspixmapsink_display_geometry_method_type = 0; + static const GEnumValue display_geometry_method_type[] = { + { 0, "Letter box", "LETTER_BOX"}, + { 1, "Origin size", "ORIGIN_SIZE"}, + { 2, "Full-screen", "FULL_SCREEN"}, + { 3, "Cropped Full-screen", "CROPPED_FULL_SCREEN"}, + { 4, "Explicitely described destination ROI", "CUSTOM_ROI"}, + { 5, NULL, NULL}, + }; + + if (!evaspixmapsink_display_geometry_method_type) { + evaspixmapsink_display_geometry_method_type = g_enum_register_static("GstEvasPixmapSinkDisplayGeometryMethodType", display_geometry_method_type); + } + + return evaspixmapsink_display_geometry_method_type; +} + +typedef struct +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; +} +MotifWmHints, MwmHints; + +#define MWM_HINTS_DECORATIONS (1L << 1) + +static void gst_evaspixmapsink_reset (GstEvasPixmapSink *evaspixmapsink); +static GstBufferClass *evaspixmap_buffer_parent_class = NULL; +static void gst_evaspixmap_buffer_finalize (GstEvasPixmapBuffer *evaspixmapbuf); +static void gst_evaspixmapsink_xcontext_clear (GstEvasPixmapSink *evaspixmapsink); +static void gst_evaspixmapsink_xpixmap_destroy (GstEvasPixmapSink *evaspixmapsink, GstXPixmap *xpixmap); +static void gst_evaspixmapsink_xpixmap_update_geometry (GstEvasPixmapSink *evaspixmapsink, int idx); +static gboolean gst_evaspixmap_buffer_put (GstEvasPixmapSink *evaspixmapsink, GstEvasPixmapBuffer *evaspixmapbuf); +static gboolean gst_evaspixmapsink_xpixmap_link (GstEvasPixmapSink *evaspixmapsink); +static void gst_evaspixmapsink_xpixmap_clear (GstEvasPixmapSink *evaspixmapsink, GstXPixmap *xpixmap); +static gint gst_evaspixmapsink_get_format_from_caps (GstEvasPixmapSink *evaspixmapsink, GstCaps *caps); +static void drm_close_gem(GstEvasPixmapSink *evaspixmapsink, unsigned int gem_handle); +static void drm_fini_close_gem_handle(GstEvasPixmapSink *evaspixmapsink, Pixmap pixmap_id); +static void evas_callback_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +static void evas_callback_del_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +static void evas_callback_show_event (void *data, Evas *e, Evas_Object *obj, void *event_info); +static void evas_callback_hide_event (void *data, Evas *e, Evas_Object *obj, void *event_info); + +/* Default template - initiated with class struct to allow gst-register to work + without X running */ +static GstStaticPadTemplate gst_evaspixmapsink_sink_template_factory = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]; " + "video/x-raw-yuv, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") + ); + +enum +{ + PROP_0, + PROP_CONTRAST, + PROP_BRIGHTNESS, + PROP_HUE, + PROP_SATURATION, + PROP_DISPLAY, + PROP_SYNCHRONOUS, + PROP_PIXEL_ASPECT_RATIO, + PROP_DEVICE, + PROP_DEVICE_NAME, + PROP_DOUBLE_BUFFER, + PROP_AUTOPAINT_COLORKEY, + PROP_COLORKEY, + PROP_PIXMAP_WIDTH, + PROP_PIXMAP_HEIGHT, + PROP_FLIP, + PROP_ROTATE_ANGLE, + PROP_DISPLAY_GEOMETRY_METHOD, + PROP_ZOOM, + PROP_DST_ROI_X, + PROP_DST_ROI_Y, + PROP_DST_ROI_W, + PROP_DST_ROI_H, + PROP_STOP_VIDEO, + PROP_EVAS_OBJECT, + PROP_VISIBLE, + PROP_ORIGIN_SIZE, +}; + +static GstVideoSinkClass *parent_class = NULL; + +/* ============================================================= */ +/* */ +/* Private Methods */ +/* */ +/* ============================================================= */ + +/* evaspixmap buffers */ + +#define GST_TYPE_EVASPIXMAP_BUFFER (gst_evaspixmap_buffer_get_type()) + +#define GST_IS_EVASPIXMAP_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_EVASPIXMAP_BUFFER)) +#define GST_EVASPIXMAP_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_EVASPIXMAP_BUFFER, GstEvasPixmapBuffer)) + +static int log_warn_count = 0; + +Display *g_my_disp; + +static int get_millis_time() +{ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return (tp.tv_sec * 1000) + (tp.tv_nsec / 1000000L); +} + +static void +ecore_pipe_callback_handler (void *data, void *buffer, unsigned int nbyte) +{ + if (!data ) { + GST_WARNING ("data is NULL.."); + return; + } + + GstEvasPixmapSink *evaspixmapsink = (GstEvasPixmapSink*)data; + GST_DEBUG_OBJECT (evaspixmapsink,"[START] Evas_Object(0x%x)", evaspixmapsink->eo); + int i = 0; + guint idx = 0; + GstXPixmap *xpixmap = NULL; + + g_mutex_lock (evaspixmapsink->flow_lock); + + evaspixmapsink->epipe_request_count--; + if (!evaspixmapsink->eo) { + GST_WARNING_OBJECT (evaspixmapsink,"evas object is NULL.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return; + } + + if (!evaspixmapsink->xcontext) { + GST_WARNING_OBJECT (evaspixmapsink,"xcontext is NULL.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return; + } + + /* setting evas object hide and show */ + if (nbyte == SIZE_FOR_UPDATE_VISIBILITY) { + if(!evaspixmapsink->visible) { + evas_object_hide(evaspixmapsink->eo); + GST_INFO_OBJECT (evaspixmapsink, "object hide"); + } else { + if (!evas_object_image_native_surface_get(evaspixmapsink->eo)) { + GST_WARNING_OBJECT (evaspixmapsink, "native surface is not set, skip evas_object_show().."); + } else { + evas_object_show(evaspixmapsink->eo); + GST_INFO_OBJECT (evaspixmapsink, "object show"); + } + } + g_mutex_unlock (evaspixmapsink->flow_lock); + return; + } + + /* mapping evas object with xpixmap */ + if (evaspixmapsink->do_link) { + GST_DEBUG_OBJECT (evaspixmapsink,"do link"); + evas_object_image_size_set(evaspixmapsink->eo, evaspixmapsink->w, evaspixmapsink->h); + if (evaspixmapsink->xpixmap[idx]->pixmap) { + Evas_Native_Surface surf; + surf.version = EVAS_NATIVE_SURFACE_VERSION; + surf.type = EVAS_NATIVE_SURFACE_X11; + surf.data.x11.visual = ecore_x_default_visual_get(ecore_x_display_get(), ecore_x_default_screen_get()); + surf.data.x11.pixmap = evaspixmapsink->xpixmap[idx]->pixmap; + evas_object_image_native_surface_set(evaspixmapsink->eo, &surf); + evaspixmapsink->do_link = FALSE; + } else { + GST_WARNING_OBJECT (evaspixmapsink,"pixmap is NULL.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return; + } + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]->prev_pixmap && evaspixmapsink->xpixmap[i]->prev_gc && evaspixmapsink->prev_damage[i]) { + GST_LOG_OBJECT (evaspixmapsink,"Free pixmap(%d), gc(%x), destroy previous damage(%d)", + evaspixmapsink->xpixmap[i]->prev_pixmap, evaspixmapsink->xpixmap[i]->prev_gc, evaspixmapsink->prev_damage[i]); + g_mutex_lock (evaspixmapsink->x_lock); + XFreePixmap(evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->prev_pixmap); + XFreeGC (evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->prev_gc); + XDamageDestroy(evaspixmapsink->xcontext->disp, evaspixmapsink->prev_damage[i]); + XSync(evaspixmapsink->xcontext->disp, FALSE); + g_mutex_unlock (evaspixmapsink->x_lock); + evaspixmapsink->xpixmap[i]->prev_pixmap = 0; + evaspixmapsink->xpixmap[i]->prev_gc = 0; + evaspixmapsink->prev_damage[i] = NULL; + } + } + if (evaspixmapsink->visible && !evas_object_visible_get(evaspixmapsink->eo)) { + evas_object_show(evaspixmapsink->eo); + GST_WARNING_OBJECT (evaspixmapsink, "object show (lazy)"); + } + + } else { + GST_DEBUG_OBJECT (evaspixmapsink,"update"); + /* update evas image object size */ + if (evaspixmapsink->use_origin_size) { + evas_object_geometry_get(evaspixmapsink->eo, NULL, NULL, &evaspixmapsink->w, &evaspixmapsink->h); + } + + g_mutex_lock (evaspixmapsink->pixmap_ref_lock); + + /* find a oldest damaged pixmap */ + int temp_time = 0; + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->last_updated_idx == i) { + continue; + } else { + xpixmap = evaspixmapsink->xpixmap[i]; + if (xpixmap->ref > 0 && xpixmap->damaged_time) { + if (temp_time == 0) { + temp_time = xpixmap->damaged_time; + idx = i; + } else { + if (temp_time > xpixmap->damaged_time) { + temp_time = xpixmap->damaged_time; + idx = i; + } + } + } + } + } + + xpixmap = evaspixmapsink->xpixmap[idx]; + if (xpixmap->pixmap) { + if (evaspixmapsink->last_updated_idx != idx) { + Evas_Native_Surface surf; + surf.version = EVAS_NATIVE_SURFACE_VERSION; + surf.type = EVAS_NATIVE_SURFACE_X11; + surf.data.x11.visual = ecore_x_default_visual_get(ecore_x_display_get(), ecore_x_default_screen_get()); + surf.data.x11.pixmap = xpixmap->pixmap; + if (evaspixmapsink->eo) { + evas_object_image_native_surface_set(evaspixmapsink->eo, NULL); + } + evas_object_image_native_surface_set(evaspixmapsink->eo, &surf); + GST_LOG_OBJECT (evaspixmapsink,"update, native_surface_set of xpixmap[%d]",idx); + if (evaspixmapsink->last_updated_idx == -1) { + xpixmap->damaged_time = 0; + GST_INFO_OBJECT (evaspixmapsink,"this is the first time to request to update(do not DECREASE ref-count) : pixmap(%d), refcount(%d), damaged_time(%d), idx(%d)", + xpixmap->pixmap, xpixmap->ref, xpixmap->damaged_time, idx); + } else { + xpixmap = evaspixmapsink->xpixmap[evaspixmapsink->last_updated_idx]; + if(xpixmap->ref > 0 && xpixmap->damaged_time > 0) { + xpixmap->ref--; + xpixmap->damaged_time = 0; + GST_INFO_OBJECT (evaspixmapsink,"pixmap ref-count DECREASED : pixmap(%d), refcount(%d), damaged_time(%d), idx(%d)", + xpixmap->pixmap, xpixmap->ref, xpixmap->damaged_time, evaspixmapsink->last_updated_idx); + } + } + evaspixmapsink->last_updated_idx = idx; + } + + evas_object_image_fill_set(evaspixmapsink->eo, 0, 0, evaspixmapsink->w, evaspixmapsink->h); + evas_object_image_data_update_add(evaspixmapsink->eo, 0, 0, evaspixmapsink->w, evaspixmapsink->h); + //GST_LOG_OBJECT (evaspixmapsink,"request to update : pixmap idx(%d), ref(%d)", idx, xpixmap->ref); + } else { + GST_ERROR_OBJECT (evaspixmapsink,"pixmap is NULL.."); + } + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); + } + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); + g_mutex_unlock (evaspixmapsink->flow_lock); +} + +static inline gboolean +is_evas_image_object (Evas_Object *obj) +{ + const char *type; + if (!obj) { + GST_ERROR ("evas image object is NULL.."); + return FALSE; + } + type = evas_object_type_get (obj); + if (!type) { + GST_ERROR ("could not find type of the evas object.."); + return FALSE; + } + if (strcmp (type, "image") == 0) { + return TRUE; + } + return FALSE; +} + +static void +evas_callback_resize_event (void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + int w = 0; + int h = 0; + float former_ratio = 0; + float ratio = 0; + int i = 0; +#ifdef COMPARE_RATIO + float abs_margin = 0; +#endif + + GstEvasPixmapSink *evaspixmapsink = (GstEvasPixmapSink *)data; + GST_DEBUG_OBJECT (evaspixmapsink,"[START] evas_image_object(%x), tid(%u)", obj, (unsigned int)pthread_self()); + + evas_object_geometry_get(evaspixmapsink->eo, NULL, NULL, &w, &h); + GST_DEBUG_OBJECT (evaspixmapsink,"resized : w(%d), h(%d)", w, h); + if (!evaspixmapsink->use_origin_size && + (evaspixmapsink->w != w || evaspixmapsink->h != h)) { + former_ratio = (float)evaspixmapsink->w / evaspixmapsink->h; + ratio = (float)w / h; + evaspixmapsink->w = w; + evaspixmapsink->h = h; + +#ifdef COMPARE_RATIO + GST_DEBUG_OBJECT (evaspixmapsink,"resized : ratio(%.3f=>%.3f)", former_ratio, ratio); + if ( former_ratio >= ratio ) { + abs_margin = former_ratio - ratio; + } else { + abs_margin = ratio - former_ratio; + } + /* re-link_pixmap can only be set when ratio is changed */ + if ( abs_margin >= MARGIN_OF_ERROR ) { +#endif + if (!gst_evaspixmapsink_xpixmap_link(evaspixmapsink)) { + GST_ERROR_OBJECT (evaspixmapsink,"link evas image object with pixmap failed..."); + return; + } + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { + if (evaspixmapsink->gem_info[i].ref_pixmap > 0) { + GST_LOG_OBJECT (evaspixmapsink,"set ref_pixmap(%d) to 0 in gem_info[%d]", evaspixmapsink->gem_info[i].ref_pixmap, i); + evaspixmapsink->gem_info[i].ref_pixmap = 0; + } + } + + /** + * Pixmap is reallocated. + * But native surface has old pixmap. + * In this case, evas can rendering with old pixmap. + * That makes broken image in the window. + * So I just unset evas native surface. + */ + if (evaspixmapsink->eo) { + evas_object_image_native_surface_set(evaspixmapsink->eo, NULL); + evas_object_image_data_set(evaspixmapsink->eo, NULL); + } +#ifdef COMPARE_RATIO + } +#endif + } + + if (GST_STATE(evaspixmapsink) >= GST_STATE_PAUSED) { + gst_evaspixmap_buffer_put (evaspixmapsink, evaspixmapsink->evas_pixmap_buf); + } + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); +} + +static void +evas_callback_del_event (void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + GstEvasPixmapSink *evaspixmapsink = data; + if (!evaspixmapsink) { + GST_WARNING ("evaspixmapsink is NULL.."); + return; + } + GST_DEBUG_OBJECT (evaspixmapsink,"[START]"); + + EVASPIXMAPSINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK( evaspixmapsink->eo ); + if (evaspixmapsink->eo) { + evas_object_image_native_surface_set(evaspixmapsink->eo, NULL); + evaspixmapsink->eo = NULL; + } + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); +} + +static void +evas_callback_show_event (void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + GstEvasPixmapSink *evaspixmapsink = (GstEvasPixmapSink *)data; + GST_INFO_OBJECT (evaspixmapsink,"show evas_image_object(%x) from pid(%d), tid(%u)", obj, getpid(), (unsigned int)pthread_self()); +} + +static void +evas_callback_hide_event (void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + GstEvasPixmapSink *evaspixmapsink = (GstEvasPixmapSink *)data; + GST_INFO_OBJECT (evaspixmapsink,"hide evas_image_object(%x) from pid(%d), tid(%u)", obj, getpid(), (unsigned int)pthread_self()); +} + +/* X11 stuff */ +static gboolean error_caught = FALSE; + +static int +gst_evaspixmapsink_handle_xerror (Display * display, XErrorEvent * xevent) +{ + char error_msg[1024]; + + XGetErrorText (display, xevent->error_code, error_msg, 1024); + if (display != g_my_disp) { + GST_WARNING ("evaspixmapsink triggered an XError. but display(%p) is not ours(%p), just skip it. error(%s), xevent->request_code(%d)", display, g_my_disp, error_msg, xevent->request_code); + } else { + GST_ERROR ("evaspixmapsink triggered an XError. error(%s), display(%p), xevent->request_code(%d)", error_msg, display, xevent->request_code); + error_caught = TRUE; + } + return 0; +} + +#ifdef DUMP_IMG +int util_write_rawdata(const char *file, const void *data, unsigned int size) +{ + FILE *fp; + + fp = fopen(file, "wb"); + if (fp == NULL) + { + GST_WARNING("fopen fail... size : %d", size); + return -1; + } + fwrite((char*)data, sizeof(char), size, fp); + fclose(fp); + + return 0; +} + +void * +gst_evaspixmapsink_get_buffer (Display *dpy, Pixmap pixmap, int width, int height, int i) +{ + if (g_bufinfo[i]!=NULL) + { + GST_ERROR("g_bufinfo!=NULL %p", g_bufinfo[i]); + exit (-1); + } + + g_bufinfo[i] = calloc (1, sizeof (BufferInfo)); + + g_bufinfo[i]->dpy = dpy; + g_bufinfo[i]->pixmap = pixmap; + g_bufinfo[i]->width = width; + g_bufinfo[i]->height = height; + + if (!gst_evaspixmapsink_init_dri2 (g_bufinfo[i])) + { + free (g_bufinfo[i]); + g_bufinfo[i] = NULL; + return NULL; + } + + if (g_bufinfo[i]->virtual == NULL) + { + GST_ERROR("g_bufinfo->virtual == NULL"); + exit (-1); + } + + return g_bufinfo[i]->virtual; +} + +void +gst_evaspixmapsink_free_buffer (int i) +{ + if (g_bufinfo[i] == NULL) + { + GST_ERROR("g_bufinfo == NULL"); + exit (-1); + } + + if (g_bufinfo[i]==NULL) + { + GST_ERROR("g_bufinfo==NULL"); + exit (-1); + } + + if (g_bufinfo[i]) + { + if (g_bufinfo[i]->bo) + tbm_bo_unref(g_bufinfo[i]->bo); + if (g_bufinfo[i]->dri2_buffers) + free(g_bufinfo[i]->dri2_buffers); + if (g_bufinfo[i]->bufmgr) + tbm_bufmgr_deinit (g_bufinfo[i]->bufmgr); + if (g_bufinfo[i]->drm_fd >= 0) + close (g_bufinfo[i]->drm_fd); + } + + XSync (g_bufinfo[i]->dpy, 0); + free (g_bufinfo[i]); + g_bufinfo[i] = NULL; +} + +static Bool gst_evaspixmapsink_init_dri2 (BufferInfo *bufinfo) +{ + int screen; + int dri2_base = 0; + int dri2_err_base = 0; + int dri2Major, dri2Minor; + char *driverName = NULL, *deviceName = NULL; + unsigned int attachments[1]; + int dri2_count, dri2_out_count; + int dri2_width, dri2_height; + drm_magic_t magic; + tbm_bo_handle bo_handle; + screen = DefaultScreen(bufinfo->dpy); + if (!DRI2QueryExtension (bufinfo->dpy, &dri2_base, &dri2_err_base)) + { + GST_ERROR ("no DRI2 extension. !!\n"); + goto fail_init_dri2; + } + if (!DRI2QueryVersion (bufinfo->dpy, &dri2Major, &dri2Minor)) + { + GST_ERROR ("fail : DRI2QueryVersion !!\n"); + goto fail_init_dri2; + } + if (!DRI2Connect (bufinfo->dpy, RootWindow(bufinfo->dpy, screen), &driverName, &deviceName)) + { + GST_ERROR ("fail : DRI2Connect !!\n"); + goto fail_init_dri2; + } /* drm_fd */ + bufinfo->drm_fd = open (deviceName, O_RDWR); + if (bufinfo->drm_fd < 0) + { + GST_ERROR ("fail : open drm device (%s)\n", deviceName); + goto fail_init_dri2; + } + /* get the drm magic */ + drmGetMagic(bufinfo->drm_fd, &magic); + if (!DRI2Authenticate(bufinfo->dpy, RootWindow(bufinfo->dpy, screen), magic)) + { + GST_ERROR ("fail : DRI2Authenticate (%d)\n", magic); + goto fail_init_dri2; + } + /* bufmgr */ + bufinfo->bufmgr = tbm_bufmgr_init (bufinfo->drm_fd); + if (!bufinfo->bufmgr) + { + GST_ERROR ("fail : init buffer manager \n"); + goto fail_init_dri2; + } + DRI2CreateDrawable (bufinfo->dpy, bufinfo->pixmap); + attachments[0] = DRI2BufferFrontLeft; + dri2_count = 1; + bufinfo->dri2_buffers = DRI2GetBuffers (bufinfo->dpy, bufinfo->pixmap, &dri2_width, &dri2_height, + attachments, dri2_count, &dri2_out_count); + if (!bufinfo->dri2_buffers) + { + GST_ERROR ("fail : get buffers\n"); + goto fail_init_dri2; + } + if (!bufinfo->dri2_buffers[0].name) + { + GST_ERROR ("fail : a handle of the dri2 buffer is null \n "); + goto fail_init_dri2; + } + bufinfo->bo = tbm_bo_import (bufinfo->bufmgr, bufinfo->dri2_buffers[0].name); + if (!bufinfo->bo) + { + GST_ERROR ("fail : import bo (key:%d)\n", bufinfo->dri2_buffers[0].name); + goto fail_init_dri2; + } + /* virtual */ + bo_handle = tbm_bo_map(bufinfo->bo, TBM_DEVICE_CPU, TBM_OPTION_WRITE); + if(!bo_handle.ptr) + { + GST_ERROR ("fail : tbm_bo_map"); + goto fail_init_dri2; + } + tbm_bo_unmap (bufinfo->bo); + + bufinfo->virtual = (void *)bo_handle.ptr; + if (!bufinfo->virtual) + { + GST_ERROR ("fail : map \n"); + goto fail_init_dri2; + } + + free (driverName); + free (deviceName); + + return True; + + fail_init_dri2: + if (driverName) + free (driverName); + if (deviceName) + free (deviceName); + if (bufinfo->bo) + tbm_bo_unref(bufinfo->bo); + if (bufinfo->dri2_buffers) + free(bufinfo->dri2_buffers); + if (bufinfo->bufmgr) + tbm_bufmgr_deinit (bufinfo->bufmgr); + if (bufinfo->drm_fd >= 0) + close (bufinfo->drm_fd); + return False; +} +#endif + +#ifdef HAVE_XSHM +/* This function checks that it is actually really possible to create an image + using XShm */ +static gboolean +gst_evaspixmapsink_check_xshm_calls (GstXContext * xcontext) +{ + XvImage *xvimage; + XShmSegmentInfo SHMInfo; + gint size; + int (*handler) (Display *, XErrorEvent *); + gboolean result = FALSE; + gboolean did_attach = FALSE; + + g_return_val_if_fail (xcontext != NULL, FALSE); + + /* Sync to ensure any older errors are already processed */ + XSync (xcontext->disp, FALSE); + + /* Set defaults so we don't free these later unnecessarily */ + SHMInfo.shmaddr = ((void *) -1); + SHMInfo.shmid = -1; + + /* Setting an error handler to catch failure */ + error_caught = FALSE; + handler = XSetErrorHandler (gst_evaspixmapsink_handle_xerror); + + /* Trying to create a 1x1 picture */ + GST_DEBUG ("XvShmCreateImage of 1x1"); + xvimage = XvShmCreateImage (xcontext->disp, xcontext->xv_port_id, + xcontext->im_format, NULL, 1, 1, &SHMInfo); + + /* Might cause an error, sync to ensure it is noticed */ + XSync (xcontext->disp, FALSE); + if (!xvimage || error_caught) { + GST_WARNING ("could not XvShmCreateImage a 1x1 image"); + goto beach; + } + size = xvimage->data_size; + + SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777); + if (SHMInfo.shmid == -1) { + GST_WARNING ("could not get shared memory of %d bytes", size); + goto beach; + } + + SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0); + if (SHMInfo.shmaddr == ((void *) -1)) { + GST_WARNING ("Failed to shmat: %s", g_strerror (errno)); + /* Clean up the shared memory segment */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + goto beach; + } + + xvimage->data = SHMInfo.shmaddr; + SHMInfo.readOnly = FALSE; + + if (XShmAttach (xcontext->disp, &SHMInfo) == 0) { + GST_WARNING ("Failed to XShmAttach"); + /* Clean up the shared memory segment */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + goto beach; + } + + /* Sync to ensure we see any errors we caused */ + XSync (xcontext->disp, FALSE); + + /* Delete the shared memory segment as soon as everyone is attached. + * This way, it will be deleted as soon as we detach later, and not + * leaked if we crash. */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + + if (!error_caught) { + GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid, + SHMInfo.shmseg); + + did_attach = TRUE; + /* store whether we succeeded in result */ + result = TRUE; + } else { + GST_WARNING ("MIT-SHM extension check failed at XShmAttach. " + "Not using shared memory."); + } + +beach: + /* Sync to ensure we swallow any errors we caused and reset error_caught */ + XSync (xcontext->disp, FALSE); + + error_caught = FALSE; + XSetErrorHandler (handler); + + if (did_attach) { + GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx", + SHMInfo.shmid, SHMInfo.shmseg); + XShmDetach (xcontext->disp, &SHMInfo); + XSync (xcontext->disp, FALSE); + } + if (SHMInfo.shmaddr != ((void *) -1)) + shmdt (SHMInfo.shmaddr); + if (xvimage) + XFree (xvimage); + return result; +} +#endif /* HAVE_XSHM */ + +/* This function destroys a GstEvasPixmap handling XShm availability */ +static void +gst_evaspixmap_buffer_destroy (GstEvasPixmapBuffer *evaspixmapbuf) +{ + GstEvasPixmapSink *evaspixmapsink; + + evaspixmapsink = evaspixmapbuf->evaspixmapsink; + if (G_UNLIKELY (evaspixmapsink == NULL)) { + goto no_sink; + } + GST_DEBUG_OBJECT (evaspixmapsink, "Destroying buffer"); + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + GST_OBJECT_LOCK (evaspixmapsink); + + /* We might have some buffers destroyed after changing state to NULL */ + if (evaspixmapsink->xcontext == NULL) { + GST_DEBUG_OBJECT (evaspixmapsink,"Destroying XvImage after Xcontext"); +#ifdef HAVE_XSHM + /* Need to free the shared memory segment even if the x context + * was already cleaned up */ + if (evaspixmapbuf->SHMInfo.shmaddr != ((void *) -1)) { + shmdt (evaspixmapbuf->SHMInfo.shmaddr); + } +#endif + goto beach; + } + g_mutex_lock (evaspixmapsink->x_lock); + +#ifdef HAVE_XSHM + if (evaspixmapsink->xcontext->use_xshm) { + if (evaspixmapbuf->SHMInfo.shmaddr != ((void *) -1)) { + GST_DEBUG_OBJECT (evaspixmapsink,"XServer ShmDetaching from 0x%x id 0x%lx", evaspixmapbuf->SHMInfo.shmid, evaspixmapbuf->SHMInfo.shmseg); + XShmDetach (evaspixmapsink->xcontext->disp, &evaspixmapbuf->SHMInfo); + XSync (evaspixmapsink->xcontext->disp, FALSE); + shmdt (evaspixmapbuf->SHMInfo.shmaddr); + } + if (evaspixmapbuf->xvimage) + XFree (evaspixmapbuf->xvimage); + } else +#endif /* HAVE_XSHM */ + { + if (evaspixmapbuf->xvimage) { + if (evaspixmapbuf->xvimage->data) { + g_free (evaspixmapbuf->xvimage->data); + } + XFree (evaspixmapbuf->xvimage); + } + } + + XSync (evaspixmapsink->xcontext->disp, FALSE); + + g_mutex_unlock (evaspixmapsink->x_lock); + +beach: + GST_OBJECT_UNLOCK (evaspixmapsink); + evaspixmapbuf->evaspixmapsink = NULL; + gst_object_unref (evaspixmapsink); + + GST_MINI_OBJECT_CLASS (evaspixmap_buffer_parent_class)->finalize (GST_MINI_OBJECT(evaspixmapbuf)); + + return; + + no_sink: + { + GST_WARNING ("no sink found"); + return; + } +} + +static void +gst_evaspixmap_buffer_finalize (GstEvasPixmapBuffer *evaspixmapbuf) +{ + GstEvasPixmapSink *evaspixmapsink; + + evaspixmapsink = evaspixmapbuf->evaspixmapsink; + if (G_UNLIKELY (evaspixmapsink == NULL)) { + GST_WARNING_OBJECT (evaspixmapsink,"no sink found"); + return; + } + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + /* If our geometry changed we can't reuse that image. */ + GST_LOG_OBJECT (evaspixmapsink,"destroy image as sink is shutting down"); + gst_evaspixmap_buffer_destroy (evaspixmapbuf); +} + +static void +gst_evaspixmap_buffer_free (GstEvasPixmapBuffer *evaspixmapbuf) +{ + /* make sure it is not recycled */ + evaspixmapbuf->width = -1; + evaspixmapbuf->height = -1; + gst_buffer_unref (GST_BUFFER (evaspixmapbuf)); +} + +static void +gst_evaspixmap_buffer_init (GstEvasPixmapBuffer *evaspixmapbuf, gpointer g_class) +{ +#ifdef HAVE_XSHM + evaspixmapbuf->SHMInfo.shmaddr = ((void *) -1); + evaspixmapbuf->SHMInfo.shmid = -1; +#endif +} + +static void +gst_evaspixmap_buffer_class_init (gpointer g_class, gpointer class_data) +{ + GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class); + + evaspixmap_buffer_parent_class = g_type_class_peek_parent (g_class); + + mini_object_class->finalize = (GstMiniObjectFinalizeFunction) gst_evaspixmap_buffer_finalize; +} + +static GType +gst_evaspixmap_buffer_get_type (void) +{ + static GType _gst_evaspixmap_buffer_type; + + if (G_UNLIKELY (_gst_evaspixmap_buffer_type == 0)) { + static const GTypeInfo evaspixmap_buffer_info = { + sizeof (GstBufferClass), + NULL, + NULL, + gst_evaspixmap_buffer_class_init, + NULL, + NULL, + sizeof (GstEvasPixmapBuffer), + 0, + (GInstanceInitFunc) gst_evaspixmap_buffer_init, + NULL + }; + _gst_evaspixmap_buffer_type = g_type_register_static (GST_TYPE_BUFFER, + "GstEvasPixmapBuffer", &evaspixmap_buffer_info, 0); + } + return _gst_evaspixmap_buffer_type; +} + +/* This function handles GstEvasPixmapBuffer creation depending on XShm availability */ +static GstEvasPixmapBuffer* +gst_evaspixmap_buffer_new (GstEvasPixmapSink *evaspixmapsink, GstCaps *caps) +{ + GstEvasPixmapBuffer *evaspixmapbuf = NULL; + GstStructure *structure = NULL; + gboolean succeeded = FALSE; + int (*handler) (Display *, XErrorEvent *); + + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), NULL); + + if (caps == NULL) { + return NULL; + } + + evaspixmapbuf = (GstEvasPixmapBuffer*) gst_mini_object_new (GST_TYPE_EVASPIXMAP_BUFFER); + GST_DEBUG_OBJECT (evaspixmapsink,"Creating new EvasPixmapBuffer"); + + structure = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (structure, "width", &evaspixmapbuf->width) || !gst_structure_get_int (structure, "height", &evaspixmapbuf->height)) { + GST_WARNING_OBJECT (evaspixmapsink,"failed getting geometry from caps %" GST_PTR_FORMAT, caps); + } + + GST_LOG_OBJECT (evaspixmapsink,"creating %dx%d", evaspixmapbuf->width, evaspixmapbuf->height); + + GST_LOG_OBJECT (evaspixmapsink,"aligned size %dx%d", evaspixmapsink->aligned_width, evaspixmapsink->aligned_height); + if (evaspixmapsink->aligned_width == 0 || evaspixmapsink->aligned_height == 0) { + GST_INFO_OBJECT (evaspixmapsink,"aligned size is zero. set size of caps."); + evaspixmapsink->aligned_width = evaspixmapbuf->width; + evaspixmapsink->aligned_height = evaspixmapbuf->height; + } + + evaspixmapbuf->im_format = gst_evaspixmapsink_get_format_from_caps (evaspixmapsink, caps); + if (evaspixmapbuf->im_format == -1) { + GST_WARNING_OBJECT (evaspixmapsink,"failed to get format from caps %"GST_PTR_FORMAT, caps); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE,("Failed to create output image buffer of %dx%d pixels", + evaspixmapbuf->width, evaspixmapbuf->height), ("Invalid input caps")); + goto beach_unlocked; + } + + GST_INFO_OBJECT (evaspixmapsink, "FOURCC format : %c%c%c%c", evaspixmapbuf->im_format, evaspixmapbuf->im_format>>8, + evaspixmapbuf->im_format>>16, evaspixmapbuf->im_format>>24); + + evaspixmapbuf->evaspixmapsink = gst_object_ref (evaspixmapsink); + + g_mutex_lock (evaspixmapsink->x_lock); + + /* Sync to ensure we swallow any errors we caused and reset error_caught */ + XSync (evaspixmapsink->xcontext->disp, FALSE); + + /* Setting an error handler to catch failure */ + error_caught = FALSE; + handler = XSetErrorHandler (gst_evaspixmapsink_handle_xerror); + +#ifdef HAVE_XSHM + if (evaspixmapsink->xcontext->use_xshm) { + int expected_size; + evaspixmapbuf->xvimage = XvShmCreateImage (evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, evaspixmapbuf->im_format, NULL, + evaspixmapsink->aligned_width, evaspixmapsink->aligned_height, &evaspixmapbuf->SHMInfo); + if (!evaspixmapbuf->xvimage || error_caught) { + if (error_caught) { + GST_ERROR_OBJECT (evaspixmapsink,"error_caught!"); + } + if(!evaspixmapbuf->xvimage) { + GST_ERROR_OBJECT (evaspixmapsink,"XvShmCreateImage() failed"); + } + g_mutex_unlock (evaspixmapsink->x_lock); + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + /* Push an error */ + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE,("Failed to create output image buffer of %dx%d pixels",evaspixmapbuf->width, + evaspixmapbuf->height),("could not XvShmCreateImage a %dx%d image",evaspixmapbuf->width, evaspixmapbuf->height)); + goto beach_unlocked; + } + + /* we have to use the returned data_size for our shm size */ + evaspixmapbuf->size = evaspixmapbuf->xvimage->data_size; + GST_LOG_OBJECT (evaspixmapsink,"XShm image size is %" G_GSIZE_FORMAT, evaspixmapbuf->size); + + /* calculate the expected size. This is only for sanity checking the + * number we get from X. */ + switch (evaspixmapbuf->im_format) { + case GST_MAKE_FOURCC ('I', '4', '2', '0'): + case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): + { + gint pitches[3]; + gint offsets[3]; + guint plane; + + offsets[0] = 0; + pitches[0] = GST_ROUND_UP_4 (evaspixmapbuf->width); + offsets[1] = offsets[0] + pitches[0] * GST_ROUND_UP_2 (evaspixmapbuf->height); + pitches[1] = GST_ROUND_UP_8 (evaspixmapbuf->width) / 2; + offsets[2] = + offsets[1] + pitches[1] * GST_ROUND_UP_2 (evaspixmapbuf->height) / 2; + pitches[2] = GST_ROUND_UP_8 (pitches[0]) / 2; + + expected_size = offsets[2] + pitches[2] * GST_ROUND_UP_2 (evaspixmapbuf->height) / 2; + + for (plane = 0; plane < evaspixmapbuf->xvimage->num_planes; plane++) { + GST_DEBUG_OBJECT (evaspixmapsink,"Plane %u has a expected pitch of %d bytes, " "offset of %d", + plane, pitches[plane], offsets[plane]); + } + break; + } + case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): + case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): + expected_size = evaspixmapbuf->height * GST_ROUND_UP_4 (evaspixmapbuf->width * 2); + break; + case GST_MAKE_FOURCC ('S', 'T', '1', '2'): + case GST_MAKE_FOURCC ('S', 'N', '1', '2'): + case GST_MAKE_FOURCC ('S', 'U', 'Y', 'V'): + case GST_MAKE_FOURCC ('S', 'U', 'Y', '2'): + case GST_MAKE_FOURCC ('S', '4', '2', '0'): + case GST_MAKE_FOURCC ('S', 'Y', 'V', 'Y'): + case GST_MAKE_FOURCC ('I', 'T', 'L', 'V'): + case GST_MAKE_FOURCC ('S', 'R', '3', '2'): + expected_size = sizeof(SCMN_IMGB); + break; + default: + expected_size = 0; + break; + } + if (expected_size != 0 && evaspixmapbuf->size != expected_size) { + GST_WARNING_OBJECT (evaspixmapsink,"unexpected XShm image size (got %" G_GSIZE_FORMAT ", expected %d)", evaspixmapbuf->size, expected_size); + } + + /* Be verbose about our XvImage stride */ + { + guint plane; + for (plane = 0; plane < evaspixmapbuf->xvimage->num_planes; plane++) { + GST_DEBUG_OBJECT (evaspixmapsink,"Plane %u has a pitch of %d bytes, ""offset of %d", plane, + evaspixmapbuf->xvimage->pitches[plane], evaspixmapbuf->xvimage->offsets[plane]); + } + } + + evaspixmapbuf->SHMInfo.shmid = shmget (IPC_PRIVATE, evaspixmapbuf->size,IPC_CREAT | 0777); + if (evaspixmapbuf->SHMInfo.shmid == -1) { + g_mutex_unlock (evaspixmapsink->x_lock); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", evaspixmapbuf->width, evaspixmapbuf->height), + ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",evaspixmapbuf->size)); + goto beach_unlocked; + } + + evaspixmapbuf->SHMInfo.shmaddr = shmat (evaspixmapbuf->SHMInfo.shmid, NULL, 0); + if (evaspixmapbuf->SHMInfo.shmaddr == ((void *) -1)) { + g_mutex_unlock (evaspixmapsink->x_lock); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + evaspixmapbuf->width, evaspixmapbuf->height), + ("Failed to shmat: %s", g_strerror (errno))); + /* Clean up the shared memory segment */ + shmctl (evaspixmapbuf->SHMInfo.shmid, IPC_RMID, NULL); + goto beach_unlocked; + } + + evaspixmapbuf->xvimage->data = evaspixmapbuf->SHMInfo.shmaddr; + evaspixmapbuf->SHMInfo.readOnly = FALSE; + + if (XShmAttach (evaspixmapsink->xcontext->disp, &evaspixmapbuf->SHMInfo) == 0) { + /* Clean up the shared memory segment */ + shmctl (evaspixmapbuf->SHMInfo.shmid, IPC_RMID, NULL); + + g_mutex_unlock (evaspixmapsink->x_lock); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + evaspixmapbuf->width, evaspixmapbuf->height), ("Failed to XShmAttach")); + goto beach_unlocked; + } + + XSync (evaspixmapsink->xcontext->disp, FALSE); + + /* Delete the shared memory segment as soon as we everyone is attached. + * This way, it will be deleted as soon as we detach later, and not + * leaked if we crash. */ + shmctl (evaspixmapbuf->SHMInfo.shmid, IPC_RMID, NULL); + + GST_DEBUG_OBJECT (evaspixmapsink,"XServer ShmAttached to 0x%x, id 0x%lx", evaspixmapbuf->SHMInfo.shmid, evaspixmapbuf->SHMInfo.shmseg); + } else +#endif /* HAVE_XSHM */ + { + evaspixmapbuf->xvimage = XvCreateImage (evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, + evaspixmapbuf->im_format, NULL, evaspixmapsink->aligned_width, evaspixmapsink->aligned_height); + if (!evaspixmapbuf->xvimage || error_caught) { + g_mutex_unlock (evaspixmapsink->x_lock); + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + /* Push an error */ + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, + ("Failed to create outputimage buffer of %dx%d pixels", + evaspixmapbuf->width, evaspixmapbuf->height), + ("could not XvCreateImage a %dx%d image", + evaspixmapbuf->width, evaspixmapbuf->height)); + goto beach_unlocked; + } + + /* we have to use the returned data_size for our image size */ + evaspixmapbuf->size = evaspixmapbuf->xvimage->data_size; + evaspixmapbuf->xvimage->data = g_malloc (evaspixmapbuf->size); + + XSync (evaspixmapsink->xcontext->disp, FALSE); + } + + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + + succeeded = TRUE; + + GST_BUFFER_DATA (evaspixmapbuf) = (guchar *) evaspixmapbuf->xvimage->data; + GST_BUFFER_SIZE (evaspixmapbuf) = evaspixmapbuf->size; + + g_mutex_unlock (evaspixmapsink->x_lock); + +beach_unlocked: + if (!succeeded) { + gst_evaspixmap_buffer_free (evaspixmapbuf); + evaspixmapbuf = NULL; + } + + return evaspixmapbuf; +} + +/* This function puts a GstEvasPixmapBuffer on a GstEvasPixmapSink's pixmap. Returns FALSE + * if no pixmap was available */ +static gboolean +gst_evaspixmap_buffer_put (GstEvasPixmapSink *evaspixmapsink, GstEvasPixmapBuffer *evaspixmapbuf) +{ + GstVideoRectangle result; + + GstVideoRectangle src_origin = { 0, 0, 0, 0}; + GstVideoRectangle src_input = { 0, 0, 0, 0}; + GstVideoRectangle src = { 0, 0, 0, 0}; + GstVideoRectangle dst = { 0, 0, 0, 0}; + int rotate = 0; + int ret = 0; + int idx = 0; + GstXPixmap *xpixmap = NULL; + int i = 0; + + /* We take the flow_lock. If expose is in there we don't want to run + concurrently from the data flow thread */ + g_mutex_lock (evaspixmapsink->flow_lock); + + if (G_UNLIKELY (evaspixmapsink->xpixmap[idx] == NULL)) { + GST_WARNING_OBJECT (evaspixmapsink, "xpixmap is NULL. Skip buffer_put." ); + g_mutex_unlock(evaspixmapsink->flow_lock); + return TRUE; + } + if (evaspixmapsink->visible == FALSE) { + GST_WARNING_OBJECT (evaspixmapsink, "visible is FALSE. Skip buffer_put." ); + g_mutex_unlock(evaspixmapsink->flow_lock); + return TRUE; + } + if (!evaspixmapbuf) { + GST_WARNING_OBJECT (evaspixmapsink, "evaspixmapbuf is NULL. Skip buffer_put." ); + g_mutex_unlock(evaspixmapsink->flow_lock); + return TRUE; + } + + g_mutex_lock (evaspixmapsink->pixmap_ref_lock); + if (evaspixmapsink->last_updated_idx == -1) { + /* if it has never been updated any frame in Ecore thread, do below */ + idx = 0; + xpixmap = evaspixmapsink->xpixmap[idx]; + xpixmap->ref = 1; + GST_LOG_OBJECT (evaspixmapsink, "last_updated_idx(%d), we use index[%d] of pixmap buffers : pixmap(%d), ref(%d)", + evaspixmapsink->last_updated_idx, idx, xpixmap->pixmap, xpixmap->ref); + } else { + for (idx = 0; idx < evaspixmapsink->num_of_pixmaps; idx++) { + if (idx == evaspixmapsink->last_updated_idx) { + continue; + } else { + xpixmap = evaspixmapsink->xpixmap[idx]; + if (xpixmap->ref == 0 && xpixmap->damaged_time == 0) { + xpixmap->ref++; + GST_LOG_OBJECT (evaspixmapsink, "found an available pixmap(%d) : xpixmap[%d]", xpixmap->pixmap, idx); + GST_INFO_OBJECT (evaspixmapsink,"pixmap ref-count INCREASED : pixmap(%d), refcount(%d)", xpixmap->pixmap, xpixmap->ref); + break; + } + } + } + if (idx == evaspixmapsink->num_of_pixmaps) { + GST_LOG_OBJECT (evaspixmapsink, "Could not find a pixmap with idle state, skip buffer_put." ); + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); + g_mutex_unlock(evaspixmapsink->flow_lock); + return TRUE; + } + } + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); + + gst_evaspixmapsink_xpixmap_update_geometry(evaspixmapsink, idx); + + src.x = src.y = 0; + src_origin.x = src_origin.y = src_input.x = src_input.y = 0; + src_input.w = src_origin.w = evaspixmapsink->video_width; + src_input.h = src_origin.h = evaspixmapsink->video_height; + if (evaspixmapsink->use_origin_size || + (evaspixmapsink->rotate_angle == DEGREE_0 || + evaspixmapsink->rotate_angle == DEGREE_180)) { + src.w = src_origin.w; + src.h = src_origin.h; + } else { + src.w = src_origin.h; + src.h = src_origin.w; + } + + dst.w = evaspixmapsink->render_rect.w; /* pixmap width */ + dst.h = evaspixmapsink->render_rect.h; /* pixmap height */ + + if (!evaspixmapsink->use_origin_size) { + static Atom atom_rotation = None; + static Atom atom_hflip = None; + static Atom atom_vflip = None; + gboolean set_hflip = FALSE; + gboolean set_vflip = FALSE; + + /* compensation of size information (between evas image object's and pixmap's) */ + if (evaspixmapsink->sizediff_width > 1) { + if (evaspixmapsink->sizediff_height > 1) { + dst.w -= (evaspixmapsink->sizediff_width >> 1) << 1; + dst.h -= (evaspixmapsink->sizediff_height >> 1) << 1; + } else { + dst.w -= (evaspixmapsink->sizediff_width >> 1) << 1; + } + } else if (evaspixmapsink->sizediff_height > 1) { + dst.h -= (evaspixmapsink->sizediff_height >> 1) << 1; + } + + switch (evaspixmapsink->display_geometry_method) { + case DISP_GEO_METHOD_LETTER_BOX: + gst_video_sink_center_rect (src, dst, &result, TRUE); + result.x += evaspixmapsink->render_rect.x; + result.y += evaspixmapsink->render_rect.y; + GST_DEBUG_OBJECT (evaspixmapsink, "GEO_METHOD : letter box"); + break; + case DISP_GEO_METHOD_ORIGIN_SIZE: + gst_video_sink_center_rect (src, dst, &result, FALSE); + gst_video_sink_center_rect (dst, src, &src_input, FALSE); + GST_DEBUG_OBJECT (evaspixmapsink, "GEO_METHOD : origin size"); + if (evaspixmapsink->rotate_angle == DEGREE_90 || + evaspixmapsink->rotate_angle == DEGREE_270) { + src_input.x = src_input.x ^ src_input.y; + src_input.y = src_input.x ^ src_input.y; + src_input.x = src_input.x ^ src_input.y; + src_input.w = src_input.w ^ src_input.h; + src_input.h = src_input.w ^ src_input.h; + src_input.w = src_input.w ^ src_input.h; + } + break; + case DISP_GEO_METHOD_FULL_SCREEN: + result.x = result.y = 0; + result.w = evaspixmapsink->xpixmap[idx]->width; + result.h = evaspixmapsink->xpixmap[idx]->height; + GST_DEBUG_OBJECT (evaspixmapsink, "GEO_METHOD : full screen"); + break; + case DISP_GEO_METHOD_CROPPED_FULL_SCREEN: + GST_DEBUG_OBJECT (evaspixmapsink, "GEO_METHOD : cropped full screen"); + gst_video_sink_center_rect(dst, src, &src_input, TRUE); + result.x = result.y = 0; + result.w = dst.w; + result.h = dst.h; + if (evaspixmapsink->rotate_angle == DEGREE_90 || + evaspixmapsink->rotate_angle == DEGREE_270) { + src_input.x = src_input.x ^ src_input.y; + src_input.y = src_input.x ^ src_input.y; + src_input.x = src_input.x ^ src_input.y; + src_input.w = src_input.w ^ src_input.h; + src_input.h = src_input.w ^ src_input.h; + src_input.w = src_input.w ^ src_input.h; + } + break; + case DISP_GEO_METHOD_CUSTOM_ROI: + switch (evaspixmapsink->rotate_angle) { + case DEGREE_90: + result.w = evaspixmapsink->dst_roi.h; + result.h = evaspixmapsink->dst_roi.w; + result.x = evaspixmapsink->dst_roi.y; + result.y = evaspixmapsink->xpixmap[idx]->height - evaspixmapsink->dst_roi.x - evaspixmapsink->dst_roi.w; + break; + case DEGREE_180: + result.w = evaspixmapsink->dst_roi.w; + result.h = evaspixmapsink->dst_roi.h; + result.x = evaspixmapsink->xpixmap[idx]->width - result.w - evaspixmapsink->dst_roi.x; + result.y = evaspixmapsink->xpixmap[idx]->height - result.h - evaspixmapsink->dst_roi.y; + break; + case DEGREE_270: + result.w = evaspixmapsink->dst_roi.h; + result.h = evaspixmapsink->dst_roi.w; + result.x = evaspixmapsink->xpixmap[idx]->width - evaspixmapsink->dst_roi.y - evaspixmapsink->dst_roi.h; + result.y = evaspixmapsink->dst_roi.x; + break; + default: + result.x = evaspixmapsink->dst_roi.x; + result.y = evaspixmapsink->dst_roi.y; + result.w = evaspixmapsink->dst_roi.w; + result.h = evaspixmapsink->dst_roi.h; + break; + } + GST_LOG_OBJECT(evaspixmapsink, "rotate[%d], ROI input[%d,%d,%dx%d] > result[%d,%d,%dx%d]", + evaspixmapsink->rotate_angle, + evaspixmapsink->dst_roi.x, evaspixmapsink->dst_roi.y, evaspixmapsink->dst_roi.w, evaspixmapsink->dst_roi.h, + result.x, result.y, result.w, result.h); + break; + default: + break; + } + GST_DEBUG_OBJECT (evaspixmapsink, "GEO_METHOD : src(%dx%d), dst(%dx%d), result(%dx%d), result_x(%d), result_y(%d)", + src.w,src.h,dst.w,dst.h,result.w,result.h,result.x,result.y); + + switch( evaspixmapsink->rotate_angle ) { + case DEGREE_0: + break; + case DEGREE_90: + rotate = 270; + break; + case DEGREE_180: + rotate = 180; + break; + case DEGREE_270: + rotate = 90; + break; + default: + GST_WARNING_OBJECT( evaspixmapsink, "Unsupported rotation [%d]... set DEGREE 0.", + evaspixmapsink->rotate_angle ); + break; + } + + g_mutex_lock (evaspixmapsink->x_lock); + + /* set display rotation */ + if (atom_rotation == None) { + atom_rotation = XInternAtom(evaspixmapsink->xcontext->disp, "_USER_WM_PORT_ATTRIBUTE_ROTATION", False); + } + ret = XvSetPortAttribute(evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_rotation, rotate); + if (ret != Success) { + GST_ERROR_OBJECT( evaspixmapsink, "XvSetPortAttribute failed[%d]. disp[%x],xv_port_id[%d],atom[%x],rotate[%d]", + ret, evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_rotation, rotate ); + } + + /* set display flip */ + if (atom_hflip == None) { + atom_hflip = XInternAtom(evaspixmapsink->xcontext->disp, "_USER_WM_PORT_ATTRIBUTE_HFLIP", False); + } + if (atom_vflip == None) { + atom_vflip = XInternAtom(evaspixmapsink->xcontext->disp, "_USER_WM_PORT_ATTRIBUTE_VFLIP", False); + } + + switch (evaspixmapsink->flip) { + case FLIP_HORIZONTAL: + set_hflip = TRUE; + set_vflip = FALSE; + break; + case FLIP_VERTICAL: + set_hflip = FALSE; + set_vflip = TRUE; + break; + case FLIP_BOTH: + set_hflip = TRUE; + set_vflip = TRUE; + break; + case FLIP_NONE: + default: + set_hflip = FALSE; + set_vflip = FALSE; + break; + } + GST_INFO_OBJECT(evaspixmapsink, "set rotate %d HFLIP %d, VFLIP %d", rotate, set_hflip, set_vflip); + + ret = XvSetPortAttribute(evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_hflip, set_hflip); + if (ret != Success) { + GST_WARNING("set HFLIP failed[%d]. disp[%x],xv_port_id[%d],atom[%x],hflip[%d]", + ret, evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_hflip, set_hflip); + } + ret = XvSetPortAttribute(evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_vflip, set_vflip); + if (ret != Success) { + GST_WARNING("set VFLIP failed[%d]. disp[%x],xv_port_id[%d],atom[%x],vflip[%d]", + ret, evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, atom_vflip, set_vflip); + } + + g_mutex_unlock (evaspixmapsink->x_lock); + + } else { + result.x = result.y = 0; + result.w = evaspixmapsink->xpixmap[idx]->width; + result.h = evaspixmapsink->xpixmap[idx]->height; + GST_INFO_OBJECT (evaspixmapsink, "USE ORIGIN SIZE, no geometry method, no rotation/flip" ); + } + + if((evaspixmapsink->result_prev.w != 0 && evaspixmapsink->result_prev.h != 0) + && (evaspixmapsink->src_prev.w==src_origin.w && evaspixmapsink->src_prev.h==src_origin.h) + && (evaspixmapsink->result_prev.w!=result.w || evaspixmapsink->result_prev.h!=result.h)) + { + GST_WARNING("It will execute XFillRectangle..... evaspixmapsink->result_prev.w : %d, evaspixmapsink->result_prev.h: %d, result.w : %d, result.h : %d", + evaspixmapsink->result_prev.w, evaspixmapsink->result_prev.h, result.w, result.h); + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + XFillRectangle (evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->pixmap, evaspixmapsink->xpixmap[i]->gc, + 0, 0, evaspixmapsink->xpixmap[i]->width, evaspixmapsink->xpixmap[i]->height); + GST_LOG_OBJECT (evaspixmapsink,"fill black to xpixmap[%d] with size(w:%d,h:%d)", evaspixmapsink->xpixmap[i]->pixmap, + evaspixmapsink->xpixmap[i]->width, evaspixmapsink->xpixmap[i]->height); + } + } + g_mutex_lock (evaspixmapsink->x_lock); + + /* We scale to the pixmap's geometry */ +#ifdef HAVE_XSHM + if (evaspixmapsink->xcontext->use_xshm) { + GST_LOG_OBJECT (evaspixmapsink,"XvShmPutImage with image %dx%d and pixmap %dx%d, from xvimage %" + GST_PTR_FORMAT, + evaspixmapbuf->width, evaspixmapbuf->height, + evaspixmapsink->render_rect.w, evaspixmapsink->render_rect.h, evaspixmapbuf); + + /* Trim as proper size */ + if (src_input.w % 2 == 1) { + src_input.w += 1; + } + if (src_input.h % 2 == 1) { + src_input.h += 1; + } + + GST_LOG_OBJECT (evaspixmapsink, "screen[%dx%d],pixmap[%d,%d,%dx%d],method[%d],rotate[%d],src[%dx%d],dst[%d,%d,%dx%d],input[%d,%d,%dx%d],result[%d,%d,%dx%d]", + evaspixmapsink->scr_w, evaspixmapsink->scr_h, + evaspixmapsink->xpixmap[idx]->x, evaspixmapsink->xpixmap[idx]->y, evaspixmapsink->xpixmap[idx]->width, evaspixmapsink->xpixmap[idx]->height, + evaspixmapsink->display_geometry_method, rotate, + src_origin.w, src_origin.h, + dst.x, dst.y, dst.w, dst.h, + src_input.x, src_input.y, src_input.w, src_input.h, + result.x, result.y, result.w, result.h ); + + if (evaspixmapsink->visible) { + if (evaspixmapsink->buf_shared_type == BUF_SHARE_METHOD_FD) { + EVASPIXMAPSINK_SET_PIXMAP_ID_TO_GEM_INFO (evaspixmapsink, evaspixmapsink->xpixmap[idx]->pixmap); + } + ret = XvShmPutImage (evaspixmapsink->xcontext->disp, + evaspixmapsink->xcontext->xv_port_id, + evaspixmapsink->xpixmap[idx]->pixmap, + evaspixmapsink->xpixmap[idx]->gc, evaspixmapbuf->xvimage, + src_input.x, src_input.y, src_input.w, src_input.h, + result.x, result.y, result.w, result.h, FALSE); + GST_LOG_OBJECT (evaspixmapsink, "XvShmPutImage return value [%d]", ret ); + } else { + GST_WARNING_OBJECT (evaspixmapsink, "visible is FALSE. skip this image..." ); + } + } else +#endif /* HAVE_XSHM */ + { + if (evaspixmapsink->visible) { + XvPutImage (evaspixmapsink->xcontext->disp, + evaspixmapsink->xcontext->xv_port_id, + evaspixmapsink->xpixmap[idx]->pixmap, + evaspixmapsink->xpixmap[idx]->gc, evaspixmapbuf->xvimage, + evaspixmapsink->disp_x, evaspixmapsink->disp_y, + evaspixmapsink->disp_width, evaspixmapsink->disp_height, + result.x, result.y, result.w, result.h); + } else { + GST_WARNING_OBJECT (evaspixmapsink, "visible is FALSE. skip this image..." ); + } + } + + evaspixmapsink->src_prev.w = src_origin.w; + evaspixmapsink->src_prev.h = src_origin.h; + + evaspixmapsink->result_prev.w = result.w; + evaspixmapsink->result_prev.h = result.h; + + XSync (evaspixmapsink->xcontext->disp, FALSE); + + g_mutex_unlock (evaspixmapsink->x_lock); + g_mutex_unlock (evaspixmapsink->flow_lock); + + return TRUE; +} + +static int +drm_init(GstEvasPixmapSink *evaspixmapsink) +{ + Display *dpy; + int i = 0; + int eventBase = 0; + int errorBase = 0; + int dri2Major = 0; + int dri2Minor = 0; + char *driverName = NULL; + char *deviceName = NULL; + struct drm_auth auth_arg = {0}; + + evaspixmapsink->drm_fd = -1; + + dpy = XOpenDisplay(0); + + /* DRI2 */ + if (!DRI2QueryExtension(dpy, &eventBase, &errorBase)) { + GST_ERROR_OBJECT (evaspixmapsink,"failed to DRI2QueryExtension()"); + goto ERROR_CASE; + } + + if (!DRI2QueryVersion(dpy, &dri2Major, &dri2Minor)) { + GST_ERROR_OBJECT (evaspixmapsink,"failed to DRI2QueryVersion"); + goto ERROR_CASE; + } + + if (!DRI2Connect(dpy, RootWindow(dpy, DefaultScreen(dpy)), &driverName, &deviceName)) { + GST_ERROR_OBJECT (evaspixmapsink,"failed to DRI2Connect"); + goto ERROR_CASE; + } + + if (!driverName || !deviceName) { + GST_ERROR_OBJECT (evaspixmapsink,"driverName or deviceName is not valid"); + goto ERROR_CASE; + } + + GST_INFO_OBJECT (evaspixmapsink,"Open drm device : %s", deviceName); + + /* get the drm_fd though opening the deviceName */ + evaspixmapsink->drm_fd = open(deviceName, O_RDWR); + if (evaspixmapsink->drm_fd < 0) { + GST_ERROR_OBJECT (evaspixmapsink,"cannot open drm device (%s)", deviceName); + goto ERROR_CASE; + } + + /* get magic from drm to authentication */ + if (ioctl(evaspixmapsink->drm_fd, DRM_IOCTL_GET_MAGIC, &auth_arg)) { + GST_ERROR_OBJECT (evaspixmapsink,"cannot get drm auth magic"); + close(evaspixmapsink->drm_fd); + evaspixmapsink->drm_fd = -1; + goto ERROR_CASE; + } + + if (!DRI2Authenticate(dpy, RootWindow(dpy, DefaultScreen(dpy)), auth_arg.magic)) { + GST_ERROR_OBJECT (evaspixmapsink,"cannot get drm authentication from X"); + close(evaspixmapsink->drm_fd); + evaspixmapsink->drm_fd = -1; + goto ERROR_CASE; + } + + /* init gem handle */ + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { + evaspixmapsink->gem_info[i].dmabuf_fd = 0; + evaspixmapsink->gem_info[i].gem_handle = 0; + evaspixmapsink->gem_info[i].gem_name = 0; + evaspixmapsink->gem_info[i].bo = 0; + evaspixmapsink->gem_info[i].ref_pixmap = 0; + } + + XCloseDisplay(dpy); + free(driverName); + free(deviceName); + + return 0; + +ERROR_CASE: + XCloseDisplay(dpy); + if (driverName) { + free(driverName); + } + if (deviceName) { + free(deviceName); + } + + return -1; +} + +static void +drm_fini(GstEvasPixmapSink *evaspixmapsink) +{ + if (evaspixmapsink->drm_fd >= 0) { + GST_INFO_OBJECT (evaspixmapsink,"close drm_fd(%d)", evaspixmapsink->drm_fd); + close(evaspixmapsink->drm_fd); + evaspixmapsink->drm_fd = -1; + } +} + +static unsigned int +drm_init_convert_dmabuf_gemname(GstEvasPixmapSink *evaspixmapsink, int dmabuf_fd) +{ + struct drm_prime_handle prime_arg = {0,}; + struct drm_gem_flink flink_arg = {0,}; + int i = 0; + + if (evaspixmapsink->drm_fd < 0) { + GST_ERROR_OBJECT (evaspixmapsink,"DRM is not opened"); + return 0; + } + + if (dmabuf_fd <= 0) { + GST_DEBUG_OBJECT (evaspixmapsink,"Ignore wrong dmabuf fd(%d)", dmabuf_fd); /* temporarily change log level to DEBUG for reducing WARNING level log */ + return 0; + } + + /* check duplicated dmabuf fd */ + for (i = 0 ; i < MAX_GEM_BUFFER_NUM ; i++) { + if (evaspixmapsink->gem_info[i].dmabuf_fd == dmabuf_fd) { + GST_LOG_OBJECT (evaspixmapsink,"already got fd(%u) with name(%u)", dmabuf_fd, evaspixmapsink->gem_info[i].gem_name); + return evaspixmapsink->gem_info[i].gem_name; + } + + if (evaspixmapsink->gem_info[i].dmabuf_fd == 0) { + GST_LOG_OBJECT (evaspixmapsink,"empty gem_info[%d] found", i); + break; + } + } + + if (i == MAX_GEM_BUFFER_NUM) { + GST_WARNING_OBJECT (evaspixmapsink,"too many buffers[dmabuf_fd(%d). skip it]", dmabuf_fd); + return 0; + } + + evaspixmapsink->gem_info[i].dmabuf_fd = dmabuf_fd; + prime_arg.fd = dmabuf_fd; + if (ioctl(evaspixmapsink->drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_arg)) { + GST_ERROR_OBJECT (evaspixmapsink,"non dmabuf fd(%d)", dmabuf_fd); + return 0; + } + + evaspixmapsink->gem_info[i].gem_handle = prime_arg.handle; + GST_LOG_OBJECT (evaspixmapsink,"gem_info[%d].gem_handle = %u", i, prime_arg.handle); + + flink_arg.handle = prime_arg.handle; + if (ioctl(evaspixmapsink->drm_fd, DRM_IOCTL_GEM_FLINK, &flink_arg)) { + GST_ERROR_OBJECT (evaspixmapsink,"cannot convert drm handle to name"); + return 0; + } + + evaspixmapsink->gem_info[i].gem_name = flink_arg.name; + GST_LOG_OBJECT (evaspixmapsink,"gem_info[%d].gem_name = %u", i, flink_arg.name); + + return flink_arg.name; +} + +static unsigned int +tbm_init_convert_bo_gemname(GstEvasPixmapSink *evaspixmapsink, tbm_bo bo) +{ + int i = 0; + + if (bo == NULL) { + GST_DEBUG_OBJECT (evaspixmapsink,"Ignore wrong bo(%u)", bo); /* temporarily change log level to DEBUG for reducing WARNING level log */ + return 0; + } + + /* check duplicated dmabuf bo */ + for (i = 0 ; i < MAX_GEM_BUFFER_NUM ; i++) { + if (evaspixmapsink->gem_info[i].bo == bo) { + GST_LOG_OBJECT (evaspixmapsink,"already got bo(%u) with name(%u)", bo, evaspixmapsink->gem_info[i].gem_name); + return evaspixmapsink->gem_info[i].gem_name; + } + + if (evaspixmapsink->gem_info[i].bo == 0) { + GST_LOG_OBJECT (evaspixmapsink,"empty gem_info[%d] found", i); + break; + } + } + + if (i == MAX_GEM_BUFFER_NUM) { + GST_WARNING_OBJECT (evaspixmapsink,"too many buffers[dmabuf_bo(%d). skip it]", bo); + return 0; + } + + evaspixmapsink->gem_info[i].bo = bo; + evaspixmapsink->gem_info[i].gem_name = tbm_bo_export(bo); + GST_LOG_OBJECT (evaspixmapsink,"gem_info[%d].gem_name = %u", i, evaspixmapsink->gem_info[i].gem_name); + + return evaspixmapsink->gem_info[i].gem_name; +} + +static void +drm_fini_close_gem_handle(GstEvasPixmapSink *evaspixmapsink, Pixmap pixmap_id) +{ + int i = 0; + if (evaspixmapsink->drm_fd >= 0) { + if (pixmap_id == 0) { + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { + if (evaspixmapsink->gem_info[i].dmabuf_fd > 0 || evaspixmapsink->gem_info[i].bo > 0) { + if (evaspixmapsink->buf_shared_type == BUF_SHARE_METHOD_FD) { + GST_INFO_OBJECT (evaspixmapsink,"close gem_handle(%u)", evaspixmapsink->gem_info[i].gem_handle); + drm_close_gem(evaspixmapsink, evaspixmapsink->gem_info[i].gem_handle); + } + evaspixmapsink->gem_info[i].dmabuf_fd = 0; + evaspixmapsink->gem_info[i].gem_handle = 0; + evaspixmapsink->gem_info[i].gem_name = 0; + evaspixmapsink->gem_info[i].bo = 0; + evaspixmapsink->gem_info[i].ref_pixmap = 0; + GST_LOG_OBJECT (evaspixmapsink,"gem_info[%d] is cleared",i); + } + } + } else { + for (i = 0; i < MAX_GEM_BUFFER_NUM; i++) { + if (evaspixmapsink->gem_info[i].ref_pixmap == pixmap_id) { + if (evaspixmapsink->buf_shared_type == BUF_SHARE_METHOD_FD) { + GST_INFO_OBJECT (evaspixmapsink,"close gem_handle(%u) for pixmap_id(%d)", + evaspixmapsink->gem_info[i].gem_handle, pixmap_id); + drm_close_gem(evaspixmapsink, evaspixmapsink->gem_info[i].gem_handle); + } + evaspixmapsink->gem_info[i].dmabuf_fd = 0; + evaspixmapsink->gem_info[i].gem_handle = 0; + evaspixmapsink->gem_info[i].gem_name = 0; + evaspixmapsink->gem_info[i].bo = 0; + evaspixmapsink->gem_info[i].ref_pixmap = 0; + GST_LOG_OBJECT (evaspixmapsink,"gem_info[%d] is cleared",i); + break; + } + } + } + } +} + +static void +drm_close_gem(GstEvasPixmapSink *evaspixmapsink, unsigned int gem_handle) +{ + struct drm_gem_close close_arg = {0,}; + + if (evaspixmapsink->drm_fd < 0) { + GST_ERROR_OBJECT (evaspixmapsink,"DRM is not opened"); + return; + } + + if (gem_handle == 0) { + GST_ERROR_OBJECT (evaspixmapsink,"invalid gem_handle(%d)",gem_handle); + return; + } + + close_arg.handle = gem_handle; + if (gem_handle > 0 && ioctl(evaspixmapsink->drm_fd, DRM_IOCTL_GEM_CLOSE, &close_arg)) { + GST_ERROR_OBJECT (evaspixmapsink,"cannot close drm gem handle(%d)", gem_handle); + return; + } + + return; +} + +/* This function destroys a GstXPixmap */ +static void +gst_evaspixmapsink_xpixmap_destroy (GstEvasPixmapSink *evaspixmapsink, GstXPixmap *xpixmap) +{ + g_return_if_fail (xpixmap != NULL); + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + g_mutex_lock (evaspixmapsink->x_lock); + + if(xpixmap->pixmap) { + GST_LOG_OBJECT (evaspixmapsink,"Free pixmap(%d)", xpixmap->pixmap); + XFreePixmap(evaspixmapsink->xcontext->disp, xpixmap->pixmap); + xpixmap->pixmap = 0; + } + + if (xpixmap->gc) { + XFreeGC (evaspixmapsink->xcontext->disp, xpixmap->gc); + } + + XSync (evaspixmapsink->xcontext->disp, FALSE); + + g_mutex_unlock (evaspixmapsink->x_lock); + + g_free (xpixmap); +} + +static void +gst_evaspixmapsink_xpixmap_update_geometry (GstEvasPixmapSink *evaspixmapsink, int idx) +{ + Window root_window; + XWindowAttributes root_attr; + + int cur_pixmap_x = 0; + int cur_pixmap_y = 0; + unsigned int cur_pixmap_width = 0; + unsigned int cur_pixmap_height = 0; + unsigned int cur_pixmap_border_width = 0; + unsigned int cur_pixmap_depth = 0; + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + /* Update the window geometry */ + g_mutex_lock (evaspixmapsink->x_lock); + if (G_UNLIKELY (evaspixmapsink->xpixmap[idx] == NULL)) { + g_mutex_unlock (evaspixmapsink->x_lock); + return; + } + + /* Get root window and size of current pixmap */ + XGetGeometry( evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[idx]->pixmap, &root_window, + &cur_pixmap_x, &cur_pixmap_y, /* relative x, y, for pixmap these are alway 0 */ + &cur_pixmap_width, &cur_pixmap_height, + &cur_pixmap_border_width, &cur_pixmap_depth ); /* cur_pixmap_border_width, cur_pixmap_depth are not used */ + + evaspixmapsink->xpixmap[idx]->width = cur_pixmap_width; + evaspixmapsink->xpixmap[idx]->height = cur_pixmap_height; + + evaspixmapsink->xpixmap[idx]->x = cur_pixmap_x; + evaspixmapsink->xpixmap[idx]->y = cur_pixmap_y; + + /* Get size of root window == size of screen */ + XGetWindowAttributes(evaspixmapsink->xcontext->disp, root_window, &root_attr); + + evaspixmapsink->scr_w = root_attr.width; + evaspixmapsink->scr_h = root_attr.height; + + if (!evaspixmapsink->have_render_rect) { + evaspixmapsink->render_rect.x = evaspixmapsink->render_rect.y = 0; + evaspixmapsink->render_rect.w = cur_pixmap_width; + evaspixmapsink->render_rect.h = cur_pixmap_height; + } + + GST_LOG_OBJECT (evaspixmapsink,"screen size %dx%d, current pixmap geometry %d,%d,%dx%d, render_rect %d,%d,%dx%d", + evaspixmapsink->scr_w, evaspixmapsink->scr_h, + evaspixmapsink->xpixmap[idx]->x, evaspixmapsink->xpixmap[idx]->y, + evaspixmapsink->xpixmap[idx]->width, evaspixmapsink->xpixmap[idx]->height, + evaspixmapsink->render_rect.x, evaspixmapsink->render_rect.y, + evaspixmapsink->render_rect.w, evaspixmapsink->render_rect.h); + + g_mutex_unlock (evaspixmapsink->x_lock); +} + +static void +gst_evaspixmapsink_xpixmap_clear (GstEvasPixmapSink *evaspixmapsink, GstXPixmap *xpixmap) +{ + g_return_if_fail (xpixmap != NULL); + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + if (!xpixmap->pixmap) { + GST_WARNING_OBJECT (evaspixmapsink,"pixmap was not created.."); + return; + } + + g_mutex_lock (evaspixmapsink->x_lock); + + if (!evaspixmapsink->xcontext) { + GST_WARNING_OBJECT (evaspixmapsink,"xcontext is null.."); + g_mutex_unlock (evaspixmapsink->x_lock); + return; + } + + if (evaspixmapsink->stop_video) { + XvStopVideo (evaspixmapsink->xcontext->disp, evaspixmapsink->xcontext->xv_port_id, xpixmap->pixmap); + } + XSync (evaspixmapsink->xcontext->disp, FALSE); + + g_mutex_unlock (evaspixmapsink->x_lock); + + g_mutex_lock (evaspixmapsink->pixmap_ref_lock); + evaspixmapsink->last_updated_idx = -1; + xpixmap->ref = 0; + xpixmap->damaged_time = 0; + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); +} + +/* This function commits our internal colorbalance settings to our grabbed Xv + port. If the xcontext is not initialized yet it simply returns */ +static void +gst_evaspixmapsink_update_colorbalance (GstEvasPixmapSink *evaspixmapsink) +{ + GList *channels = NULL; + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + /* If we haven't initialized the X context we can't update anything */ + if (evaspixmapsink->xcontext == NULL) + return; + + /* Don't set the attributes if they haven't been changed, to avoid + * rounding errors changing the values */ + if (!evaspixmapsink->cb_changed) + return; + + /* For each channel of the colorbalance we calculate the correct value + doing range conversion and then set the Xv port attribute to match our + values. */ + channels = evaspixmapsink->xcontext->channels_list; + + while (channels) { + if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) { + GstColorBalanceChannel *channel = NULL; + Atom prop_atom; + gint value = 0; + gdouble convert_coef; + + channel = GST_COLOR_BALANCE_CHANNEL (channels->data); + g_object_ref (channel); + + /* Our range conversion coef */ + convert_coef = (channel->max_value - channel->min_value) / 2000.0; + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + value = evaspixmapsink->hue; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + value = evaspixmapsink->saturation; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + value = evaspixmapsink->contrast; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + value = evaspixmapsink->brightness; + } else { + g_warning ("got an unknown channel %s", channel->label); + g_object_unref (channel); + return; + } + + /* Committing to Xv port */ + g_mutex_lock (evaspixmapsink->x_lock); + prop_atom = + XInternAtom (evaspixmapsink->xcontext->disp, channel->label, True); + if (prop_atom != None) { + int xv_value; + xv_value = + floor (0.5 + (value + 1000) * convert_coef + channel->min_value); + XvSetPortAttribute (evaspixmapsink->xcontext->disp, + evaspixmapsink->xcontext->xv_port_id, prop_atom, xv_value); + } + g_mutex_unlock (evaspixmapsink->x_lock); + + g_object_unref (channel); + } + channels = g_list_next (channels); + } +} + +static void +gst_lookup_xv_port_from_adaptor (GstXContext *xcontext, XvAdaptorInfo *adaptors, int adaptor_no) +{ + gint j; + gint res; + + /* Do we support XvImageMask ? */ + if (!(adaptors[adaptor_no].type & XvImageMask)) { + GST_DEBUG ("XV Adaptor %s has no support for XvImageMask", adaptors[adaptor_no].name); + return; + } + + /* We found such an adaptor, looking for an available port */ + for (j = 0; j < adaptors[adaptor_no].num_ports && !xcontext->xv_port_id; j++) { + /* We try to grab the port */ + res = XvGrabPort (xcontext->disp, adaptors[adaptor_no].base_id + j, 0); + if (Success == res) { + xcontext->xv_port_id = adaptors[adaptor_no].base_id + j; + GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_no].name, adaptors[adaptor_no].num_ports); + } else { + GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j, adaptors[adaptor_no].name, res); + } + } +} + +/* This function generates a caps with all supported format by the first + Xv grabable port we find. We store each one of the supported formats in a + format list and append the format to a newly created caps that we return + If this function does not return NULL because of an error, it also grabs + the port via XvGrabPort */ +static GstCaps* +gst_evaspixmapsink_get_xv_support (GstEvasPixmapSink *evaspixmapsink, GstXContext *xcontext) +{ + gint i; + XvAdaptorInfo *adaptors; + gint nb_formats; + XvImageFormatValues *formats = NULL; + guint nb_encodings; + XvEncodingInfo *encodings = NULL; + gulong max_w = G_MAXINT, max_h = G_MAXINT; + GstCaps *caps = NULL; + GstCaps *rgb_caps = NULL; + + g_return_val_if_fail (xcontext != NULL, NULL); + + /* First let's check that XVideo extension is available */ + if (!XQueryExtension (xcontext->disp, "XVideo", &i, &i, &i)) { + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), + ("XVideo extension is not available")); + return NULL; + } + + /* Then we get adaptors list */ + if (Success != XvQueryAdaptors (xcontext->disp, xcontext->root, + &xcontext->nb_adaptors, &adaptors)) { + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), + ("Failed getting XV adaptors list")); + return NULL; + } + + xcontext->xv_port_id = 0; + + GST_DEBUG_OBJECT (evaspixmapsink,"Found %u XV adaptor(s)", xcontext->nb_adaptors); + + xcontext->adaptors = + (gchar **) g_malloc0 (xcontext->nb_adaptors * sizeof (gchar *)); + + /* Now fill up our adaptor name array */ + for (i = 0; i < xcontext->nb_adaptors; i++) { + xcontext->adaptors[i] = g_strdup (adaptors[i].name); + } + + if (evaspixmapsink->adaptor_no < xcontext->nb_adaptors) { + /* Find xv port from user defined adaptor */ + gst_lookup_xv_port_from_adaptor (xcontext, adaptors, evaspixmapsink->adaptor_no); + } + + if (!xcontext->xv_port_id) { + /* Now search for an adaptor that supports XvImageMask */ + for (i = 0; i < xcontext->nb_adaptors && !xcontext->xv_port_id; i++) { + gst_lookup_xv_port_from_adaptor (xcontext, adaptors, i); + evaspixmapsink->adaptor_no = i; + } + } + + XvFreeAdaptorInfo (adaptors); + + if (!xcontext->xv_port_id) { + evaspixmapsink->adaptor_no = -1; + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, BUSY, + ("Could not initialise Xv output"), ("No port available")); + return NULL; + } + + /* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */ + { + int count, todo = 3; + XvAttribute *const attr = XvQueryPortAttributes (xcontext->disp, + xcontext->xv_port_id, &count); + static const char autopaint[] = "XV_AUTOPAINT_COLORKEY"; + static const char dbl_buffer[] = "XV_DOUBLE_BUFFER"; + static const char colorkey[] = "XV_COLORKEY"; + + GST_DEBUG_OBJECT (evaspixmapsink,"Checking %d Xv port attributes", count); + + evaspixmapsink->have_autopaint_colorkey = FALSE; + evaspixmapsink->have_double_buffer = FALSE; + evaspixmapsink->have_colorkey = FALSE; + + for (i = 0; ((i < count) && todo); i++) + if (!strcmp (attr[i].name, autopaint)) { + const Atom atom = XInternAtom (xcontext->disp, autopaint, False); + + /* turn on autopaint colorkey */ + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (evaspixmapsink->autopaint_colorkey ? 1 : 0)); + todo--; + evaspixmapsink->have_autopaint_colorkey = TRUE; + } else if (!strcmp (attr[i].name, dbl_buffer)) { + const Atom atom = XInternAtom (xcontext->disp, dbl_buffer, False); + + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (evaspixmapsink->double_buffer ? 1 : 0)); + todo--; + evaspixmapsink->have_double_buffer = TRUE; + } else if (!strcmp (attr[i].name, colorkey)) { + /* Set the colorkey, default is something that is dark but hopefully + * won't randomly appear on the screen elsewhere (ie not black or greys) + * can be overridden by setting "colorkey" property + */ + const Atom atom = XInternAtom (xcontext->disp, colorkey, False); + guint32 ckey = 0; + gboolean set_attr = TRUE; + guint cr, cg, cb; + + /* set a colorkey in the right format RGB565/RGB888 + * We only handle these 2 cases, because they're the only types of + * devices we've encountered. If we don't recognise it, leave it alone + */ + cr = (evaspixmapsink->colorkey >> 16); + cg = (evaspixmapsink->colorkey >> 8) & 0xFF; + cb = (evaspixmapsink->colorkey) & 0xFF; + switch (xcontext->depth) { + case 16: /* RGB 565 */ + cr >>= 3; + cg >>= 2; + cb >>= 3; + ckey = (cr << 11) | (cg << 5) | cb; + break; + case 24: + case 32: /* RGB 888 / ARGB 8888 */ + ckey = (cr << 16) | (cg << 8) | cb; + break; + default: + GST_DEBUG_OBJECT (evaspixmapsink,"Unknown bit depth %d for Xv Colorkey - not adjusting", xcontext->depth); + set_attr = FALSE; + break; + } + + if (set_attr) { + ckey = CLAMP (ckey, (guint32) attr[i].min_value, + (guint32) attr[i].max_value); + GST_LOG_OBJECT (evaspixmapsink,"Setting color key for display depth %d to 0x%x", xcontext->depth, ckey); + + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (gint) ckey); + } + todo--; + evaspixmapsink->have_colorkey = TRUE; + } + + XFree (attr); + } + + /* Get the list of encodings supported by the adapter and look for the + * XV_IMAGE encoding so we can determine the maximum width and height + * supported */ + XvQueryEncodings (xcontext->disp, xcontext->xv_port_id, &nb_encodings, + &encodings); + + for (i = 0; i < nb_encodings; i++) { + GST_LOG_OBJECT (evaspixmapsink, + "Encoding %d, name %s, max wxh %lux%lu rate %d/%d", + i, encodings[i].name, encodings[i].width, encodings[i].height, + encodings[i].rate.numerator, encodings[i].rate.denominator); + if (strcmp (encodings[i].name, "XV_IMAGE") == 0) { + max_w = encodings[i].width; + max_h = encodings[i].height; + evaspixmapsink->scr_w = max_w; + evaspixmapsink->scr_h = max_h; + } + } + + XvFreeEncodingInfo (encodings); + + /* We get all image formats supported by our port */ + formats = XvListImageFormats (xcontext->disp, + xcontext->xv_port_id, &nb_formats); + caps = gst_caps_new_empty (); + for (i = 0; i < nb_formats; i++) { + GstCaps *format_caps = NULL; + gboolean is_rgb_format = FALSE; + + /* We set the image format of the xcontext to an existing one. This + is just some valid image format for making our xshm calls check before + caps negotiation really happens. */ + xcontext->im_format = formats[i].id; + + switch (formats[i].type) { + case XvRGB: + { + XvImageFormatValues *fmt = &(formats[i]); + gint endianness = G_BIG_ENDIAN; + + if (fmt->byte_order == LSBFirst) { + /* our caps system handles 24/32bpp RGB as big-endian. */ + if (fmt->bits_per_pixel == 24 || fmt->bits_per_pixel == 32) { + fmt->red_mask = GUINT32_TO_BE (fmt->red_mask); + fmt->green_mask = GUINT32_TO_BE (fmt->green_mask); + fmt->blue_mask = GUINT32_TO_BE (fmt->blue_mask); + + if (fmt->bits_per_pixel == 24) { + fmt->red_mask >>= 8; + fmt->green_mask >>= 8; + fmt->blue_mask >>= 8; + } + } else + endianness = G_LITTLE_ENDIAN; + } + + format_caps = gst_caps_new_simple ("video/x-raw-rgb", + "format", GST_TYPE_FOURCC, formats[i].id, + "endianness", G_TYPE_INT, endianness, + "depth", G_TYPE_INT, fmt->depth, + "bpp", G_TYPE_INT, fmt->bits_per_pixel, + "red_mask", G_TYPE_INT, fmt->red_mask, + "green_mask", G_TYPE_INT, fmt->green_mask, + "blue_mask", G_TYPE_INT, fmt->blue_mask, + "width", GST_TYPE_INT_RANGE, 1, max_w, + "height", GST_TYPE_INT_RANGE, 1, max_h, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + + is_rgb_format = TRUE; + break; + } + case XvYUV: + format_caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, formats[i].id, + "width", GST_TYPE_INT_RANGE, 1, max_w, + "height", GST_TYPE_INT_RANGE, 1, max_h, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + break; + default: + g_assert_not_reached (); + break; + } + + if (format_caps) { + GstEvasPixmapFormat *format = NULL; + + format = g_new0 (GstEvasPixmapFormat, 1); + if (format) { + format->format = formats[i].id; + format->caps = gst_caps_copy (format_caps); + xcontext->formats_list = g_list_append (xcontext->formats_list, format); + } + + if (is_rgb_format) { + if (rgb_caps == NULL) + rgb_caps = format_caps; + else + gst_caps_append (rgb_caps, format_caps); + } else + gst_caps_append (caps, format_caps); + } + } + + /* Collected all caps into either the caps or rgb_caps structures. + * Append rgb_caps on the end of YUV, so that YUV is always preferred */ + if (rgb_caps) + gst_caps_append (caps, rgb_caps); + + if (formats) + XFree (formats); + + GST_DEBUG_OBJECT (evaspixmapsink,"Generated the following caps: %" GST_PTR_FORMAT, caps); + + if (gst_caps_is_empty (caps)) { + gst_caps_unref (caps); + XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0); + GST_ELEMENT_ERROR (evaspixmapsink, STREAM, WRONG_TYPE, (NULL), + ("No supported format found")); + return NULL; + } + + return caps; +} + +static gpointer +gst_evaspixmapsink_event_thread (GstEvasPixmapSink * evaspixmapsink) +{ + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), NULL); + int damage_base = 0; + int damage_err_base = 0; + int damage_case = 0; + XEvent e; + int i = 0; + Display *disp = NULL; + +#ifdef DUMP_IMG + int ret = 0; + char file_name[128]; + char *dump_data; +#endif + + + GST_OBJECT_LOCK (evaspixmapsink); + + if (evaspixmapsink->xcontext && evaspixmapsink->xcontext->disp) { + disp = evaspixmapsink->xcontext->disp; + } else { + GST_ERROR_OBJECT (evaspixmapsink,"evaspixmapsink->xcontext(->disp) is not ready"); + return NULL; + } + + if (!XDamageQueryExtension(evaspixmapsink->xcontext->disp, &damage_base, &damage_err_base)) { + GST_ERROR_OBJECT (evaspixmapsink,"XDamageQueryExtension() failed"); + return NULL; + } + damage_case = (int)damage_base + XDamageNotify; + + while (evaspixmapsink->running) { + GST_OBJECT_UNLOCK (evaspixmapsink); + + g_mutex_lock (evaspixmapsink->x_lock); + while (XPending (disp)) { + XNextEvent (disp, &e); + g_mutex_unlock (evaspixmapsink->x_lock); + if (e.type == damage_case ) { + XDamageNotifyEvent *damage_ev = (XDamageNotifyEvent *)&e; + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + GstXPixmap *xpixmap = evaspixmapsink->xpixmap[i]; + if (xpixmap && damage_ev->drawable == xpixmap->pixmap) { + g_mutex_lock(evaspixmapsink->pixmap_ref_lock); + if (xpixmap->ref) { + /* set it only if damage event comes from _buffer_put() */ + xpixmap->damaged_time = (gint)get_millis_time(); + } + g_mutex_unlock(evaspixmapsink->pixmap_ref_lock); + if (GST_STATE(evaspixmapsink) < GST_STATE_PLAYING || log_warn_count < 20) { + GST_WARNING_OBJECT (evaspixmapsink,"event_handler : got a damage event for pixmap(%d), refcount(%d), damaged_time(%d)", + xpixmap->pixmap, xpixmap->ref, xpixmap->damaged_time); + } else { + GST_DEBUG_OBJECT (evaspixmapsink,"event_handler : got a damage event for pixmap(%d), refcount(%d), damaged_time(%d)", + xpixmap->pixmap, xpixmap->ref, xpixmap->damaged_time); + } + if (evaspixmapsink->epipe_request_count > EPIPE_REQUEST_LIMIT) { + GST_WARNING_OBJECT (evaspixmapsink,"event_handler : epipe_request_count(%d), skip ecore_pipe_write()", evaspixmapsink->epipe_request_count); + } else { + ecore_pipe_write(evaspixmapsink->epipe, evaspixmapsink, sizeof(GstEvasPixmapSink)); + evaspixmapsink->epipe_request_count++; + if (GST_STATE(evaspixmapsink) < GST_STATE_PLAYING || log_warn_count < 20 ) { + GST_WARNING_OBJECT (evaspixmapsink,"event_handler : after call ecore_pipe_write() for pixmap(%d)", xpixmap->pixmap); + log_warn_count++; + } else { + GST_DEBUG_OBJECT (evaspixmapsink,"event_handler : after call ecore_pipe_write() for pixmap(%d)", xpixmap->pixmap); + } + } + g_mutex_lock (evaspixmapsink->x_lock); + XDamageSubtract (evaspixmapsink->xcontext->disp, evaspixmapsink->damage[i], None, None ); + g_mutex_unlock (evaspixmapsink->x_lock); + +#ifdef DUMP_IMG + int dump_w = evaspixmapsink->w + (32 - (evaspixmapsink->w%32)); // for multiples of 32 + if(g_cnt<100) + { + evaspixmapsink->pixmap_addr[i] = gst_evaspixmapsink_get_buffer(evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->pixmap, dump_w, evaspixmapsink->h, i); + GST_ERROR(" pixmap[%d] addr %p (w : %d, h : %d)", g_cnt, i, evaspixmapsink->pixmap_addr[i], dump_w, evaspixmapsink->h); + sprintf(file_name, "/opt/usr/media/DUMP_%2.2d.dump", g_cnt); + + dump_data = g_malloc(dump_w * evaspixmapsink->h * 4); + memcpy (dump_data, evaspixmapsink->pixmap_addr[i], dump_w * evaspixmapsink->h * 4); + + ret = util_write_rawdata(file_name, dump_data, dump_w * evaspixmapsink->h * 4); + if (ret) { + GST_ERROR_OBJECT (evaspixmapsink, "util_write_rawdata() failed"); + } + else + g_cnt++; + g_free(dump_data); + + if (g_bufinfo[i]) { + gst_evaspixmapsink_free_buffer (i); + g_bufinfo[i] = NULL; + } + evaspixmapsink->pixmap_addr[i] = NULL; + } +#endif + if (evaspixmapsink->buf_shared_type == BUF_SHARE_METHOD_FD) { + if (GST_STATE(evaspixmapsink) == GST_STATE_PLAYING) { + drm_fini_close_gem_handle(evaspixmapsink, xpixmap->pixmap); + } + } + break; + } + } + if (i == evaspixmapsink->num_of_pixmaps) { + GST_WARNING_OBJECT (evaspixmapsink,"event_handler : could not find corresponding pixmap with this damage event(%d)", damage_ev->drawable); + } + } else { + GST_LOG_OBJECT (evaspixmapsink,"event_handler : unidentified event(%d)", e.type); + g_mutex_lock (evaspixmapsink->x_lock); + continue; + } + g_mutex_lock (evaspixmapsink->x_lock); + } + //XSync (disp, FALSE); + g_mutex_unlock (evaspixmapsink->x_lock); + + g_usleep (G_USEC_PER_SEC / 40); + GST_OBJECT_LOCK (evaspixmapsink); + } + GST_OBJECT_UNLOCK (evaspixmapsink); + return NULL; +} + +static void +gst_evaspixmapsink_manage_event_thread (GstEvasPixmapSink *evaspixmapsink) +{ + /* don't start the thread too early */ + if (evaspixmapsink->xcontext == NULL) { + GST_ERROR_OBJECT (evaspixmapsink,"xcontext is NULL.."); + return; + } + + GST_OBJECT_LOCK (evaspixmapsink); + + if (!evaspixmapsink->event_thread) { + /* Setup our event listening thread */ + GST_DEBUG_OBJECT (evaspixmapsink,"run xevent thread"); + evaspixmapsink->running = TRUE; + evaspixmapsink->event_thread = g_thread_create ( (GThreadFunc) gst_evaspixmapsink_event_thread, evaspixmapsink, TRUE, NULL); + } else { + GST_WARNING_OBJECT (evaspixmapsink,"there already existed the event_thread.. keep going"); + /* Do not finalize the thread in here, Only finalize the thread by calling gst_evaspixmapsink_reset() */ + } + + GST_OBJECT_UNLOCK (evaspixmapsink); +} + + +/* This function calculates the pixel aspect ratio based on the properties + * in the xcontext structure and stores it there. */ +static void +gst_evaspixmapsink_calculate_pixel_aspect_ratio (GstXContext *xcontext) +{ + static const gint par[][2] = { + {1, 1}, /* regular screen */ + {16, 15}, /* PAL TV */ + {11, 10}, /* 525 line Rec.601 video */ + {54, 59}, /* 625 line Rec.601 video */ + {64, 45}, /* 1280x1024 on 16:9 display */ + {5, 3}, /* 1280x1024 on 4:3 display */ + {4, 3} /* 800x600 on 16:9 display */ + }; + gint i; + gint index; + gdouble ratio; + gdouble delta; + +#define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1]))) + + /* first calculate the "real" ratio based on the X values; + * which is the "physical" w/h divided by the w/h in pixels of the display */ + ratio = (gdouble) (xcontext->widthmm * xcontext->height) + / (xcontext->heightmm * xcontext->width); + + /* DirectFB's X in 720x576 reports the physical dimensions wrong, so + * override here */ + if (xcontext->width == 720 && xcontext->height == 576) { + ratio = 4.0 * 576 / (3.0 * 720); + } + GST_DEBUG ("calculated pixel aspect ratio: %f", ratio); + /* now find the one from par[][2] with the lowest delta to the real one */ + delta = DELTA (0); + index = 0; + + for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) { + gdouble this_delta = DELTA (i); + + if (this_delta < delta) { + index = i; + delta = this_delta; + } + } + + GST_DEBUG ("Decided on index %d (%d/%d)", index, + par[index][0], par[index][1]); + + g_free (xcontext->par); + xcontext->par = g_new0 (GValue, 1); + g_value_init (xcontext->par, GST_TYPE_FRACTION); + gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]); + GST_DEBUG ("set xcontext PAR to %d/%d", + gst_value_get_fraction_numerator (xcontext->par), + gst_value_get_fraction_denominator (xcontext->par)); +} + +/* This function gets the X Display and global info about it. Everything is + stored in our object and will be cleaned when the object is disposed. Note + here that caps for supported format are generated without any window or + image creation */ +static GstXContext* +gst_evaspixmapsink_xcontext_get (GstEvasPixmapSink *evaspixmapsink) +{ + GstXContext *xcontext = NULL; + XPixmapFormatValues *px_formats = NULL; + gint nb_formats = 0, i, j, N_attr; + XvAttribute *xv_attr; + Atom prop_atom; + const char *channels[4] = { "XV_HUE", "XV_SATURATION", "XV_BRIGHTNESS", "XV_CONTRAST"}; + + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), NULL); + + xcontext = g_new0 (GstXContext, 1); + xcontext->im_format = 0; + + g_mutex_lock (evaspixmapsink->x_lock); + + xcontext->disp = XOpenDisplay (evaspixmapsink->display_name); + if (!xcontext->disp) { + g_mutex_unlock (evaspixmapsink->x_lock); + g_free (xcontext); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, ("Could not initialise Xv output"), ("Could not open display")); + return NULL; + } + GST_WARNING_OBJECT (evaspixmapsink,"Display(0x%x) is opened", xcontext->disp); + g_my_disp = xcontext->disp; + + xcontext->screen = DefaultScreenOfDisplay (xcontext->disp); + xcontext->screen_num = DefaultScreen (xcontext->disp); + xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num); + xcontext->root = DefaultRootWindow (xcontext->disp); + xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num); + xcontext->depth = DefaultDepthOfScreen (xcontext->screen); + + xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num); + xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num); + xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num); + xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num); + + GST_DEBUG_OBJECT (evaspixmapsink,"X reports %dx%d pixels and %d mm x %d mm", xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm); + + gst_evaspixmapsink_calculate_pixel_aspect_ratio (xcontext); + /* We get supported pixmap formats at supported depth */ + px_formats = XListPixmapFormats (xcontext->disp, &nb_formats); + + if (!px_formats) { + XCloseDisplay (xcontext->disp); + g_mutex_unlock (evaspixmapsink->x_lock); + g_free (xcontext->par); + g_free (xcontext); + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), ("Could not get pixel formats")); + g_my_disp = NULL; + return NULL; + } + + /* We get bpp value corresponding to our running depth */ + for (i = 0; i < nb_formats; i++) { + if (px_formats[i].depth == xcontext->depth) + xcontext->bpp = px_formats[i].bits_per_pixel; + } + + XFree (px_formats); + + xcontext->endianness = (ImageByteOrder (xcontext->disp) == LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN; + + /* our caps system handles 24/32bpp RGB as big-endian. */ + if ((xcontext->bpp == 24 || xcontext->bpp == 32) && xcontext->endianness == G_LITTLE_ENDIAN) { + xcontext->endianness = G_BIG_ENDIAN; + xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask); + xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask); + xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask); + if (xcontext->bpp == 24) { + xcontext->visual->red_mask >>= 8; + xcontext->visual->green_mask >>= 8; + xcontext->visual->blue_mask >>= 8; + } + } + + xcontext->caps = gst_evaspixmapsink_get_xv_support (evaspixmapsink, xcontext); + + if (!xcontext->caps) { + XCloseDisplay (xcontext->disp); + g_mutex_unlock (evaspixmapsink->x_lock); + g_free (xcontext->par); + g_free (xcontext); + /* GST_ELEMENT_ERROR is thrown by gst_evaspixmapsink_get_xv_support */ + g_my_disp = NULL; + return NULL; + } +#ifdef HAVE_XSHM + /* Search for XShm extension support */ + if (XShmQueryExtension (xcontext->disp) && gst_evaspixmapsink_check_xshm_calls (xcontext)) { + xcontext->use_xshm = TRUE; + GST_DEBUG_OBJECT (evaspixmapsink,"evaspixmapsink is using XShm extension"); + } else +#endif /* HAVE_XSHM */ + { + xcontext->use_xshm = FALSE; + GST_DEBUG_OBJECT (evaspixmapsink,"evaspixmapsink is not using XShm extension"); + } + + xv_attr = XvQueryPortAttributes (xcontext->disp, xcontext->xv_port_id, &N_attr); + + /* Generate the channels list */ + for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) { + XvAttribute *matching_attr = NULL; + + /* Retrieve the property atom if it exists. If it doesn't exist, + * the attribute itself must not either, so we can skip */ + prop_atom = XInternAtom (xcontext->disp, channels[i], True); + if (prop_atom == None) { + continue; + } + + if (xv_attr != NULL) { + for (j = 0; j < N_attr && matching_attr == NULL; ++j) { + if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name)) { + matching_attr = xv_attr + j; + } + } + } + + if (matching_attr) { + GstColorBalanceChannel *channel; + channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL); + channel->label = g_strdup (channels[i]); + channel->min_value = matching_attr->min_value; + channel->max_value = matching_attr->max_value; + + xcontext->channels_list = g_list_append (xcontext->channels_list, channel); + + /* If the colorbalance settings have not been touched we get Xv values + as defaults and update our internal variables */ + if (!evaspixmapsink->cb_changed) { + gint val; + XvGetPortAttribute (xcontext->disp, xcontext->xv_port_id, prop_atom, &val); + /* Normalize val to [-1000, 1000] */ + val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) / (double) (channel->max_value - channel->min_value)); + + if (!g_ascii_strcasecmp (channels[i], "XV_HUE")) { + evaspixmapsink->hue = val; + } else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION")) { + evaspixmapsink->saturation = val; + } else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS")) { + evaspixmapsink->brightness = val; + } else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST")) { + evaspixmapsink->contrast = val; + } + } + } + } + + if (xv_attr) { + XFree (xv_attr); + } + + g_mutex_unlock (evaspixmapsink->x_lock); + + return xcontext; +} + +/* This function cleans the X context. Closing the Display, releasing the XV + port and unrefing the caps for supported formats. */ +static void +gst_evaspixmapsink_xcontext_clear (GstEvasPixmapSink *evaspixmapsink) +{ + GList *formats_list, *channels_list; + GstXContext *xcontext; + gint i = 0; + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + + GST_OBJECT_LOCK (evaspixmapsink); + if (evaspixmapsink->xcontext == NULL) { + GST_OBJECT_UNLOCK (evaspixmapsink); + return; + } + + /* Take the XContext from the sink and clean it up */ + xcontext = evaspixmapsink->xcontext; + evaspixmapsink->xcontext = NULL; + + GST_OBJECT_UNLOCK (evaspixmapsink); + + formats_list = xcontext->formats_list; + + while (formats_list) { + GstEvasPixmapFormat *format = formats_list->data; + + gst_caps_unref (format->caps); + g_free (format); + formats_list = g_list_next (formats_list); + } + + if (xcontext->formats_list) + g_list_free (xcontext->formats_list); + + channels_list = xcontext->channels_list; + + while (channels_list) { + GstColorBalanceChannel *channel = channels_list->data; + + g_object_unref (channel); + channels_list = g_list_next (channels_list); + } + + if (xcontext->channels_list) + g_list_free (xcontext->channels_list); + + gst_caps_unref (xcontext->caps); + + for (i = 0; i < xcontext->nb_adaptors; i++) { + g_free (xcontext->adaptors[i]); + } + + g_free (xcontext->adaptors); + + g_free (xcontext->par); + + g_mutex_lock (evaspixmapsink->x_lock); + + GST_DEBUG_OBJECT (evaspixmapsink,"Closing display and freeing X Context"); + + XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0); + + XCloseDisplay (xcontext->disp); + g_my_disp = NULL; + + g_mutex_unlock (evaspixmapsink->x_lock); + + g_free (xcontext); +} + +/* Element stuff */ + +/* This function tries to get a format matching with a given caps in the + supported list of formats we generated in gst_evaspixmapsink_get_xv_support */ +static gint +gst_evaspixmapsink_get_format_from_caps (GstEvasPixmapSink *evaspixmapsink, GstCaps *caps) +{ + GList *list = NULL; + + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), 0); + + list = evaspixmapsink->xcontext->formats_list; + + while (list) { + GstEvasPixmapFormat *format = list->data; + + if (format) { + if (gst_caps_can_intersect (caps, format->caps)) { + return format->format; + } + } + list = g_list_next (list); + } + + return -1; +} + +static GstCaps * +gst_evaspixmapsink_getcaps (GstBaseSink *bsink) +{ + GstEvasPixmapSink *evaspixmapsink; + + evaspixmapsink = GST_EVASPIXMAPSINK (bsink); + + if (evaspixmapsink->xcontext) + return gst_caps_ref (evaspixmapsink->xcontext->caps); + + return + gst_caps_copy (gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD + (evaspixmapsink))); +} + +static gboolean +gst_evaspixmapsink_setcaps (GstBaseSink *bsink, GstCaps *caps) +{ + GstEvasPixmapSink *evaspixmapsink; + GstStructure *structure; + guint32 im_format = 0; + gboolean ret; + gint video_width, video_height; + gint disp_x, disp_y; + gint disp_width, disp_height; + gint video_par_n, video_par_d; /* video's PAR */ + gint display_par_n, display_par_d; /* display's PAR */ + const GValue *caps_par; + const GValue *caps_disp_reg; + const GValue *fps; + guint num, den; + gboolean enable_last_buffer; + + evaspixmapsink = GST_EVASPIXMAPSINK (bsink); + + GST_DEBUG_OBJECT (evaspixmapsink,"In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %" GST_PTR_FORMAT, evaspixmapsink->xcontext->caps, caps); + + if (!gst_caps_can_intersect (evaspixmapsink->xcontext->caps, caps)) { + goto incompatible_caps; + } + + structure = gst_caps_get_structure (caps, 0); + ret = gst_structure_get_int (structure, "width", &video_width); + ret &= gst_structure_get_int (structure, "height", &video_height); + fps = gst_structure_get_value (structure, "framerate"); + ret &= (fps != NULL); + + if (!ret) { + goto incomplete_caps; + } + + evaspixmapsink->aligned_width = video_width; + evaspixmapsink->aligned_height = video_height; + + /* get combine prop of hevc */ + if(gst_structure_get_int (structure, "yuvcombine", &(evaspixmapsink->need_combine_data))) + { + GST_INFO_OBJECT(evaspixmapsink, "need combine data : %d", evaspixmapsink->need_combine_data); + } + else + { + /*Not need to combine data, just directly copy*/ + evaspixmapsink->need_combine_data = 0; + } + + /* get enable-last-buffer */ + g_object_get(G_OBJECT(evaspixmapsink), "enable-last-buffer", &enable_last_buffer, NULL); + GST_INFO_OBJECT (evaspixmapsink,"current enable-last-buffer : %d", enable_last_buffer); + /* flush if enable-last-buffer is TRUE */ + if (enable_last_buffer) { + GST_INFO_OBJECT (evaspixmapsink,"flush last-buffer"); + g_object_set(G_OBJECT(evaspixmapsink), "enable-last-buffer", FALSE, NULL); + g_object_set(G_OBJECT(evaspixmapsink), "enable-last-buffer", TRUE, NULL); + } + + evaspixmapsink->fps_n = gst_value_get_fraction_numerator (fps); + evaspixmapsink->fps_d = gst_value_get_fraction_denominator (fps); + + if (evaspixmapsink->video_width != video_width || evaspixmapsink->video_height != video_height) { + evaspixmapsink->src_prev.w = evaspixmapsink->video_width = video_width; + evaspixmapsink->src_prev.h = evaspixmapsink->video_height = video_height; + evaspixmapsink->need_to_fill_black = TRUE; + } + + im_format = gst_evaspixmapsink_get_format_from_caps (evaspixmapsink, caps); + if (im_format == -1) { + goto invalid_format; + } + + /* get aspect ratio from caps if it's present, and + * convert video width and height to a display width and height + * using wd / hd = wv / hv * PARv / PARd */ + + /* get video's PAR */ + caps_par = gst_structure_get_value (structure, "pixel-aspect-ratio"); + if (caps_par) { + video_par_n = gst_value_get_fraction_numerator (caps_par); + video_par_d = gst_value_get_fraction_denominator (caps_par); + } else { + video_par_n = 1; + video_par_d = 1; + } + /* get display's PAR */ + if (evaspixmapsink->par) { + display_par_n = gst_value_get_fraction_numerator (evaspixmapsink->par); + display_par_d = gst_value_get_fraction_denominator (evaspixmapsink->par); + } else { + display_par_n = 1; + display_par_d = 1; + } + + /* get the display region */ + caps_disp_reg = gst_structure_get_value (structure, "display-region"); + if (caps_disp_reg) { + disp_x = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 0)); + disp_y = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 1)); + disp_width = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 2)); + disp_height = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 3)); + } else { + disp_x = disp_y = 0; + disp_width = video_width; + disp_height = video_height; + } + + if (!gst_video_calculate_display_ratio (&num, &den, video_width, video_height, video_par_n, video_par_d, display_par_n, display_par_d)) { + goto no_disp_ratio; + } + + evaspixmapsink->disp_x = disp_x; + evaspixmapsink->disp_y = disp_y; + evaspixmapsink->disp_width = disp_width; + evaspixmapsink->disp_height = disp_height; + + GST_DEBUG_OBJECT (evaspixmapsink,"video width/height: %dx%d, calculated display ratio: %d/%d", video_width, video_height, num, den); + + /* now find a width x height that respects this display ratio. + * prefer those that have one of w/h the same as the incoming video + * using wd / hd = num / den */ + + /* start with same height, because of interlaced video */ + /* check hd / den is an integer scale factor, and scale wd with the PAR */ + if (video_height % den == 0) { + GST_DEBUG_OBJECT (evaspixmapsink,"keeping video height"); + GST_VIDEO_SINK_WIDTH (evaspixmapsink) = (guint) gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (evaspixmapsink) = video_height; + } else if (video_width % num == 0) { + GST_DEBUG_OBJECT (evaspixmapsink,"keeping video width"); + GST_VIDEO_SINK_WIDTH (evaspixmapsink) = video_width; + GST_VIDEO_SINK_HEIGHT (evaspixmapsink) = (guint) gst_util_uint64_scale_int (video_width, den, num); + } else { + GST_DEBUG_OBJECT (evaspixmapsink,"approximating while keeping video height"); + GST_VIDEO_SINK_WIDTH (evaspixmapsink) = (guint) gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (evaspixmapsink) = video_height; + } + GST_DEBUG_OBJECT (evaspixmapsink,"scaling to %dx%d", GST_VIDEO_SINK_WIDTH (evaspixmapsink), GST_VIDEO_SINK_HEIGHT (evaspixmapsink)); + + /* Creating our window and our image with the display size in pixels */ + if (GST_VIDEO_SINK_WIDTH (evaspixmapsink) <= 0 || GST_VIDEO_SINK_HEIGHT (evaspixmapsink) <= 0) { + goto no_display_size; + } + + g_mutex_lock (evaspixmapsink->flow_lock); + + /* We renew our evaspixmap buffer only if size or format changed; + * the evaspixmap buffer is the same size as the video pixel size */ + if ((evaspixmapsink->evas_pixmap_buf) && ((im_format != evaspixmapsink->evas_pixmap_buf->im_format) + || (video_width != evaspixmapsink->evas_pixmap_buf->width) || (video_height != evaspixmapsink->evas_pixmap_buf->height))) { + GST_DEBUG_OBJECT (evaspixmapsink,"old format %" GST_FOURCC_FORMAT ", new format %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (evaspixmapsink->evas_pixmap_buf->im_format), GST_FOURCC_ARGS (im_format)); + GST_DEBUG_OBJECT (evaspixmapsink,"renewing evaspixmap buffer"); + gst_buffer_unref (GST_BUFFER (evaspixmapsink->evas_pixmap_buf)); + evaspixmapsink->evas_pixmap_buf = NULL; + } + + g_mutex_unlock (evaspixmapsink->flow_lock); + + if (evaspixmapsink->eo) { + if (!gst_evaspixmapsink_xpixmap_link (evaspixmapsink)) { + GST_ERROR_OBJECT (evaspixmapsink,"link evas image object with pixmap failed..."); + return FALSE; + } else { + gst_evaspixmapsink_manage_event_thread (evaspixmapsink); + } + } else { + GST_ERROR_OBJECT (evaspixmapsink,"setcaps success, but there is no evas image object.."); + return FALSE; + } + + return TRUE; + + /* ERRORS */ + incompatible_caps: + { + GST_ERROR_OBJECT (evaspixmapsink,"caps incompatible"); + return FALSE; + } + incomplete_caps: + { + GST_DEBUG_OBJECT (evaspixmapsink,"Failed to retrieve either width, ""height or framerate from intersected caps"); + return FALSE; + } + invalid_format: + { + GST_DEBUG_OBJECT (evaspixmapsink,"Could not locate image format from caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + no_disp_ratio: + { + GST_ELEMENT_ERROR (evaspixmapsink, CORE, NEGOTIATION, (NULL), ("Error calculating the output display ratio of the video.")); + return FALSE; + } + no_display_size: + { + GST_ELEMENT_ERROR (evaspixmapsink, CORE, NEGOTIATION, (NULL), ("Error calculating the output display ratio of the video.")); + return FALSE; + } +} + +static GstStateChangeReturn +gst_evaspixmapsink_change_state (GstElement *element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstEvasPixmapSink *evaspixmapsink; + + evaspixmapsink = GST_EVASPIXMAPSINK (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + g_mutex_lock (evaspixmapsink->flow_lock); + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_NULL_TO_READY"); + + /* open drm to use gem */ + if (drm_init(evaspixmapsink)) { + GST_ERROR_OBJECT (evaspixmapsink,"drm_init() failure"); + g_mutex_unlock (evaspixmapsink->flow_lock); + return GST_STATE_CHANGE_FAILURE; + } + + /* check if there exist evas image object, need to write code related to making internal evas image object */ + if (!is_evas_image_object (evaspixmapsink->eo)) { + GST_ERROR_OBJECT (evaspixmapsink,"There is no evas image object.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return GST_STATE_CHANGE_FAILURE; + } + + /* Set xcontext and display */ + if (!evaspixmapsink->xcontext) { + evaspixmapsink->xcontext = gst_evaspixmapsink_xcontext_get (evaspixmapsink); + if (!evaspixmapsink->xcontext) { + GST_ERROR_OBJECT (evaspixmapsink,"could not get xcontext.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return GST_STATE_CHANGE_FAILURE; + } + } + + /* update object's par with calculated one if not set yet */ + if (!evaspixmapsink->par) { + evaspixmapsink->par = g_new0 (GValue, 1); + gst_value_init_and_copy (evaspixmapsink->par, evaspixmapsink->xcontext->par); + GST_DEBUG_OBJECT (evaspixmapsink,"set calculated PAR on object's PAR"); + } + + /* call XSynchronize with the current value of synchronous */ + GST_DEBUG_OBJECT (evaspixmapsink,"XSynchronize called with %s", evaspixmapsink->synchronous ? "TRUE" : "FALSE"); + g_mutex_lock (evaspixmapsink->x_lock); + XSynchronize (evaspixmapsink->xcontext->disp, evaspixmapsink->synchronous); + g_mutex_unlock (evaspixmapsink->x_lock); + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + + g_mutex_unlock (evaspixmapsink->flow_lock); + break; + + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_READY_TO_PAUSED"); + break; + + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_PAUSED_TO_PLAYING"); + break; + + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_PLAYING_TO_PAUSED"); + log_warn_count = 0; + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + int i = 0; + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_PAUSED_TO_READY"); + evaspixmapsink->fps_n = 0; + evaspixmapsink->fps_d = 1; + GST_VIDEO_SINK_WIDTH (evaspixmapsink) = 0; + GST_VIDEO_SINK_HEIGHT (evaspixmapsink) = 0; + drm_fini_close_gem_handle(evaspixmapsink, 0); + } + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + GST_WARNING_OBJECT (evaspixmapsink,"GST_STATE_CHANGE_READY_TO_NULL"); + g_mutex_lock (evaspixmapsink->flow_lock); + gst_evaspixmapsink_reset(evaspixmapsink); + g_mutex_unlock (evaspixmapsink->flow_lock); + /* close drm */ + drm_fini(evaspixmapsink); + break; + + default: + break; + } + return ret; +} + +static void +gst_evaspixmapsink_get_times (GstBaseSink *bsink, GstBuffer *buf, GstClockTime *start, GstClockTime *end) +{ + GstEvasPixmapSink *evaspixmapsink; + + evaspixmapsink = GST_EVASPIXMAPSINK (bsink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + *start = GST_BUFFER_TIMESTAMP (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) { + *end = *start + GST_BUFFER_DURATION (buf); + } else { + if (evaspixmapsink->fps_n > 0) { + *end = *start + + gst_util_uint64_scale_int (GST_SECOND, evaspixmapsink->fps_d, + evaspixmapsink->fps_n); + } + } + } +} + +static void +gst_evaspixmapsink_combine_i420_scmn_data(GstEvasPixmapSink *evaspixmapsink, GstBuffer *buf) +{ + unsigned int outsize = 0; + unsigned char *temp_outbuf = evaspixmapsink->evas_pixmap_buf->xvimage->data; + int cnt = 0, j = 0; + unsigned char *temp_imgb; + SCMN_IMGB *imgb = NULL; + int stride, w, h, i; + + GST_DEBUG("Begin"); + + imgb = (SCMN_IMGB *)GST_BUFFER_DATA(buf); + if(imgb == NULL || imgb->a[0] == NULL) + { + return; + } + + stride = GST_ROUND_UP_4 (imgb->w[0]); + h = imgb->h[0]; + w = imgb->w[0]; + /*Y plane copy */ + for (i = 0; i < h; i++) + { + memcpy (temp_outbuf + i * stride, imgb->a[0] + i * imgb->s[0], w); + } + + temp_outbuf += GST_ROUND_UP_4 (imgb->w[0]) * GST_ROUND_UP_2 (imgb->h[0]); + + stride = GST_ROUND_UP_4 (GST_ROUND_UP_2 (imgb->w[0]) / 2); + h = GST_ROUND_UP_2 (imgb->h[0]) / 2; + w = GST_ROUND_UP_2 (imgb->w[0]) / 2; + /* Cb plane copy */ + for (i = 0; i < h; i++) + { + memcpy (temp_outbuf + i * stride, imgb->a[1] + i * imgb->s[1], w); + } + + temp_outbuf += GST_ROUND_UP_4 (GST_ROUND_UP_2 (imgb->w[0]) / 2) * (GST_ROUND_UP_2 (imgb->h[0]) / 2); + /* Same stride, height, width as above */ + /* Cr plane copy */ + for (i = 0; i < h; i++) + { + memcpy (temp_outbuf + i * stride, imgb->a[2] + i * imgb->s[2], w); + } + + outsize = imgb->w[0] * imgb->h[0]* 3 >> 1; + evaspixmapsink->evas_pixmap_buf->size = MIN(outsize, evaspixmapsink->evas_pixmap_buf->size); + + GST_DEBUG("End"); +} + +static GstFlowReturn +gst_evaspixmapsink_show_frame (GstVideoSink *vsink, GstBuffer *buf) +{ + GstEvasPixmapSink *evaspixmapsink; + XV_DATA_PTR img_data = NULL; + SCMN_IMGB *scmn_imgb = NULL; + gint format = 0; + + evaspixmapsink = GST_EVASPIXMAPSINK (vsink); + + if (GST_STATE(evaspixmapsink) < GST_STATE_PLAYING || log_warn_count < 20) { + GST_WARNING_OBJECT (evaspixmapsink, "enter >>>" ); + } + + if( evaspixmapsink->stop_video ) { + GST_INFO_OBJECT (evaspixmapsink, "Stop video is TRUE. so skip show frame..." ); + goto skip_frame; + } + + if (!evaspixmapsink->evas_pixmap_buf) { + GST_DEBUG_OBJECT (evaspixmapsink,"creating our evaspixmap buffer"); + format = gst_evaspixmapsink_get_format_from_caps(evaspixmapsink, GST_BUFFER_CAPS(buf)); + switch (format) { + case GST_MAKE_FOURCC('S', 'T', '1', '2'): + case GST_MAKE_FOURCC('S', 'N', '1', '2'): + case GST_MAKE_FOURCC('S', '4', '2', '0'): + case GST_MAKE_FOURCC('S', 'U', 'Y', '2'): + case GST_MAKE_FOURCC('S', 'U', 'Y', 'V'): + case GST_MAKE_FOURCC('S', 'Y', 'V', 'Y'): + case GST_MAKE_FOURCC('I', 'T', 'L', 'V'): + case GST_MAKE_FOURCC('S', 'R', '3', '2'): + scmn_imgb = (SCMN_IMGB *)GST_BUFFER_MALLOCDATA(buf); + if(scmn_imgb == NULL) { + GST_DEBUG_OBJECT (evaspixmapsink, "scmn_imgb is NULL. Skip buffer put..." ); + goto skip_frame; + } + /* skip buffer if aligned size is smaller than size of caps */ + if (scmn_imgb->s[0] < evaspixmapsink->video_width || scmn_imgb->e[0] < evaspixmapsink->video_height) { + GST_WARNING_OBJECT (evaspixmapsink,"invalid size[caps:%dx%d,aligned:%dx%d]. Skip this buffer...", + evaspixmapsink->video_width, evaspixmapsink->video_height, scmn_imgb->s[0], scmn_imgb->e[0]); + goto skip_frame; + } + evaspixmapsink->aligned_width = scmn_imgb->s[0]; + evaspixmapsink->aligned_height = scmn_imgb->e[0]; + GST_DEBUG_OBJECT (evaspixmapsink,"video width,height[%dx%d]",evaspixmapsink->video_width, evaspixmapsink->video_height); + GST_INFO_OBJECT (evaspixmapsink,"Use aligned width,height[%dx%d]",evaspixmapsink->aligned_width, evaspixmapsink->aligned_height); + break; + default: + GST_INFO_OBJECT (evaspixmapsink,"Use original width,height of caps"); + break; + } + evaspixmapsink->evas_pixmap_buf = gst_evaspixmap_buffer_new (evaspixmapsink, GST_BUFFER_CAPS (buf)); + if (!evaspixmapsink->evas_pixmap_buf) { + /* The create method should have posted an informative error */ + goto no_image; + } + if (evaspixmapsink->evas_pixmap_buf->size < GST_BUFFER_SIZE (buf)) { + GST_ELEMENT_ERROR (evaspixmapsink, RESOURCE, WRITE, ("Failed to create output image buffer of %dx%d pixels", evaspixmapsink->evas_pixmap_buf->width, evaspixmapsink->evas_pixmap_buf->height),("XServer allocated buffer size did not match input buffer")); + gst_evaspixmap_buffer_destroy (evaspixmapsink->evas_pixmap_buf); + evaspixmapsink->evas_pixmap_buf = NULL; + goto no_image; + } + } + + switch (evaspixmapsink->evas_pixmap_buf->im_format) { + /* Cases for specified formats of Samsung extension */ + case GST_MAKE_FOURCC('S', 'T', '1', '2'): + case GST_MAKE_FOURCC('S', 'N', '1', '2'): + case GST_MAKE_FOURCC('S', '4', '2', '0'): + case GST_MAKE_FOURCC('S', 'U', 'Y', '2'): + case GST_MAKE_FOURCC('S', 'U', 'Y', 'V'): + case GST_MAKE_FOURCC('S', 'Y', 'V', 'Y'): + case GST_MAKE_FOURCC('I', 'T', 'L', 'V'): + case GST_MAKE_FOURCC('S', 'R', '3', '2'): + { + GST_DEBUG_OBJECT (evaspixmapsink,"Samsung extension display format activated. fourcc:%d", evaspixmapsink->evas_pixmap_buf->im_format); + + if (evaspixmapsink->evas_pixmap_buf->xvimage->data) { + img_data = (XV_DATA_PTR) evaspixmapsink->evas_pixmap_buf->xvimage->data; + XV_INIT_DATA(img_data); + scmn_imgb = (SCMN_IMGB *)GST_BUFFER_MALLOCDATA(buf); + if (scmn_imgb == NULL) { + GST_DEBUG_OBJECT (evaspixmapsink, "scmn_imgb is NULL. Skip buffer put..." ); + goto skip_frame; + } + + if (scmn_imgb->buf_share_method == BUF_SHARE_METHOD_PADDR) { + img_data->YBuf = (unsigned int)scmn_imgb->p[0]; + img_data->CbBuf = (unsigned int)scmn_imgb->p[1]; + img_data->CrBuf = (unsigned int)scmn_imgb->p[2]; + img_data->BufType = XV_BUF_TYPE_LEGACY; + + } else if (scmn_imgb->buf_share_method == BUF_SHARE_METHOD_FD) { + /* set gem information to gem_info structure of handle and convert dma-buf fd into drm gem name */ + img_data->YBuf = drm_init_convert_dmabuf_gemname(evaspixmapsink, (int)scmn_imgb->dma_buf_fd[0]); + img_data->CbBuf = drm_init_convert_dmabuf_gemname(evaspixmapsink, (int)scmn_imgb->dma_buf_fd[1]); + img_data->CrBuf = drm_init_convert_dmabuf_gemname(evaspixmapsink, (int)scmn_imgb->dma_buf_fd[2]); + img_data->BufType = XV_BUF_TYPE_DMABUF; + if (evaspixmapsink->buf_shared_type != BUF_SHARE_METHOD_FD) { + evaspixmapsink->buf_shared_type = BUF_SHARE_METHOD_FD; + GST_WARNING_OBJECT (evaspixmapsink, "BUF SHARED TYPE : FD"); + } + + } else if (scmn_imgb->buf_share_method == BUF_SHARE_METHOD_TIZEN_BUFFER) { + img_data->bo[0] = scmn_imgb->bo[0]; + img_data->bo[1] = scmn_imgb->bo[1]; + img_data->bo[2] = scmn_imgb->bo[2]; + GST_DEBUG("TBM bo %p %p %p", img_data->bo[0], img_data->bo[1], img_data->bo[2]); + /* export bo */ + if (img_data->bo[0]) { + img_data->YBuf = tbm_init_convert_bo_gemname(evaspixmapsink, (tbm_bo)img_data->bo[0]); + } + if (img_data->bo[1]) { + img_data->CbBuf = tbm_init_convert_bo_gemname(evaspixmapsink, (tbm_bo)img_data->bo[1]); + } + if (img_data->bo[2]) { + img_data->CrBuf = tbm_init_convert_bo_gemname(evaspixmapsink, (tbm_bo)img_data->bo[2]); + } + if (evaspixmapsink->buf_shared_type != BUF_SHARE_METHOD_TIZEN_BUFFER) { + evaspixmapsink->buf_shared_type = BUF_SHARE_METHOD_TIZEN_BUFFER; + GST_WARNING_OBJECT (evaspixmapsink, "BUF SHARED TYPE : TIZEN BUFFER"); + } + } else { + GST_WARNING_OBJECT (evaspixmapsink, "Not supported, buf_share_method(%d)", scmn_imgb->buf_share_method); + goto skip_frame; + } + if (!img_data->YBuf) { + GST_WARNING_OBJECT (evaspixmapsink, "img_data->YBuf is NULL. skip buffer put..." ); + goto skip_frame; + } + GST_LOG_OBJECT(evaspixmapsink, "YBuf[%d], CbBuf[%d], CrBuf[%d]", + img_data->YBuf, img_data->CbBuf, img_data->CrBuf ); + } else { + GST_WARNING_OBJECT (evaspixmapsink, "xvimage->data is NULL. skip buffer put..." ); + goto skip_frame; + } + break; + } + default: + { + if (evaspixmapsink->buf_shared_type != BUF_SHARE_METHOD_NONE) { + evaspixmapsink->buf_shared_type = BUF_SHARE_METHOD_NONE; + } + GST_DEBUG_OBJECT (evaspixmapsink,"Normal format activated. fourcc = %d", evaspixmapsink->evas_pixmap_buf->im_format); + if(evaspixmapsink->need_combine_data == 1) + { + gst_evaspixmapsink_combine_i420_scmn_data(evaspixmapsink, buf); + } + else + { + memcpy (evaspixmapsink->evas_pixmap_buf->xvimage->data, + GST_BUFFER_DATA (buf), + MIN (GST_BUFFER_SIZE (buf), evaspixmapsink->evas_pixmap_buf->size)); + } + break; + } + } + gst_evaspixmap_buffer_put (evaspixmapsink, evaspixmapsink->evas_pixmap_buf); + +skip_frame: + + if (GST_STATE(evaspixmapsink) < GST_STATE_PLAYING || log_warn_count < 20) { + GST_WARNING_OBJECT (evaspixmapsink, "leave <<<" ); + } + + return GST_FLOW_OK; + + /* ERRORS */ + no_image: + { + /* No image available. That's very bad ! */ + GST_WARNING_OBJECT (evaspixmapsink,"could not create image"); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_evaspixmapsink_event (GstBaseSink *sink, GstEvent *event) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (sink); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + GST_DEBUG_OBJECT (evaspixmapsink,"GST_EVENT_FLUSH_START"); + break; + case GST_EVENT_FLUSH_STOP: + GST_DEBUG_OBJECT (evaspixmapsink,"GST_EVENT_FLUSH_STOP"); + break; + default: + break; + } + if (GST_BASE_SINK_CLASS (parent_class)->event) { + return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); + } else { + return TRUE; + } +} + +/* Interfaces stuff */ + +static gboolean +gst_evaspixmapsink_interface_supported (GstImplementsInterface *iface, GType type) +{ + g_assert (type == GST_TYPE_NAVIGATION || type == GST_TYPE_COLOR_BALANCE || type == GST_TYPE_PROPERTY_PROBE); + return TRUE; +} + +static void +gst_evaspixmapsink_interface_init (GstImplementsInterfaceClass *klass) +{ + klass->supported = gst_evaspixmapsink_interface_supported; +} + +static void +gst_evaspixmapsink_navigation_send_event (GstNavigation *navigation, GstStructure *structure) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (navigation); + GstPad *peer; + int i = 0; + + if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (evaspixmapsink)))) { + GstEvent *event; + GstVideoRectangle result; + gdouble x, y, xscale = 1.0, yscale = 1.0; + + event = gst_event_new_navigation (structure); + + /* We take the flow_lock while we look at the window */ + g_mutex_lock (evaspixmapsink->flow_lock); + + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i ++) { + if (!evaspixmapsink->xpixmap[i]) { + g_mutex_unlock (evaspixmapsink->flow_lock); + return; + } + } + + memcpy (&result, &evaspixmapsink->render_rect, sizeof (GstVideoRectangle)); + + g_mutex_unlock (evaspixmapsink->flow_lock); + + /* We calculate scaling using the original video frames geometry to include + pixel aspect ratio scaling. */ + xscale = (gdouble) evaspixmapsink->video_width / result.w; + yscale = (gdouble) evaspixmapsink->video_height / result.h; + + /* Converting pointer coordinates to the non scaled geometry */ + if (gst_structure_get_double (structure, "pointer_x", &x)) { + x = MIN (x, result.x + result.w); + x = MAX (x - result.x, 0); + gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, + (gdouble) x * xscale, NULL); + } + if (gst_structure_get_double (structure, "pointer_y", &y)) { + y = MIN (y, result.y + result.h); + y = MAX (y - result.y, 0); + gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, + (gdouble) y * yscale, NULL); + } + + gst_pad_send_event (peer, event); + gst_object_unref (peer); + } +} + +static void +gst_evaspixmapsink_navigation_init (GstNavigationInterface *iface) +{ + iface->send_event = gst_evaspixmapsink_navigation_send_event; +} + +static const GList* +gst_evaspixmapsink_colorbalance_list_channels (GstColorBalance *balance) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (balance); + + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), NULL); + + if (evaspixmapsink->xcontext) + return evaspixmapsink->xcontext->channels_list; + else + return NULL; +} + +static void +gst_evaspixmapsink_colorbalance_set_value (GstColorBalance *balance, GstColorBalanceChannel *channel, gint value) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (balance); + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink)); + g_return_if_fail (channel->label != NULL); + + evaspixmapsink->cb_changed = TRUE; + + /* Normalize val to [-1000, 1000] */ + value = floor (0.5 + -1000 + 2000 * (value - channel->min_value) / + (double) (channel->max_value - channel->min_value)); + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + evaspixmapsink->hue = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + evaspixmapsink->saturation = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + evaspixmapsink->contrast = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + evaspixmapsink->brightness = value; + } else { + g_warning ("got an unknown channel %s", channel->label); + return; + } + + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); +} + +static gint +gst_evaspixmapsink_colorbalance_get_value (GstColorBalance *balance, GstColorBalanceChannel *channel) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (balance); + gint value = 0; + + g_return_val_if_fail (GST_IS_EVASPIXMAPSINK (evaspixmapsink), 0); + g_return_val_if_fail (channel->label != NULL, 0); + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + value = evaspixmapsink->hue; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + value = evaspixmapsink->saturation; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + value = evaspixmapsink->contrast; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + value = evaspixmapsink->brightness; + } else { + g_warning ("got an unknown channel %s", channel->label); + } + + /* Normalize val to [channel->min_value, channel->max_value] */ + value = channel->min_value + (channel->max_value - channel->min_value) * (value + 1000) / 2000; + + return value; +} + +static void +gst_evaspixmapsink_colorbalance_init (GstColorBalanceClass *iface) +{ + GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE; + iface->list_channels = gst_evaspixmapsink_colorbalance_list_channels; + iface->set_value = gst_evaspixmapsink_colorbalance_set_value; + iface->get_value = gst_evaspixmapsink_colorbalance_get_value; +} + +static const GList * +gst_evaspixmapsink_probe_get_properties (GstPropertyProbe *probe) +{ + GObjectClass *klass = G_OBJECT_GET_CLASS (probe); + static GList *list = NULL; + + if (!list) { + list = g_list_append (NULL, g_object_class_find_property (klass, "device")); + list = g_list_append (list, g_object_class_find_property (klass, "autopaint-colorkey")); + list = g_list_append (list, g_object_class_find_property (klass, "double-buffer")); + list = g_list_append (list, g_object_class_find_property (klass, "colorkey")); + } + + return list; +} + +static void +gst_evaspixmapsink_probe_probe_property (GstPropertyProbe *probe, guint prop_id, const GParamSpec *pspec) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (probe); + + switch (prop_id) { + case PROP_DEVICE: + case PROP_AUTOPAINT_COLORKEY: + case PROP_DOUBLE_BUFFER: + case PROP_COLORKEY: + GST_DEBUG_OBJECT (evaspixmapsink,"probing device list and get capabilities"); + if (!evaspixmapsink->xcontext) { + GST_DEBUG_OBJECT (evaspixmapsink,"generating xcontext"); + evaspixmapsink->xcontext = gst_evaspixmapsink_xcontext_get (evaspixmapsink); + if (!evaspixmapsink->xcontext) { + GST_ERROR_OBJECT (evaspixmapsink,"could not get xcontext.."); + } + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } +} + +static gboolean +gst_evaspixmapsink_probe_needs_probe (GstPropertyProbe *probe, guint prop_id, const GParamSpec *pspec) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (probe); + gboolean ret = FALSE; + + switch (prop_id) { + case PROP_DEVICE: + case PROP_AUTOPAINT_COLORKEY: + case PROP_DOUBLE_BUFFER: + case PROP_COLORKEY: + if (evaspixmapsink->xcontext != NULL) { + ret = FALSE; + } else { + ret = TRUE; + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } + + return ret; +} + +static GValueArray * +gst_evaspixmapsink_probe_get_values (GstPropertyProbe *probe, guint prop_id, const GParamSpec *pspec) +{ + GstEvasPixmapSink *evaspixmapsink = GST_EVASPIXMAPSINK (probe); + GValueArray *array = NULL; + + if (G_UNLIKELY (!evaspixmapsink->xcontext)) { + GST_WARNING_OBJECT (evaspixmapsink,"we don't have any xcontext, can't " + "get values"); + goto beach; + } + + switch (prop_id) { + case PROP_DEVICE: + { + guint i; + GValue value = { 0 }; + + array = g_value_array_new (evaspixmapsink->xcontext->nb_adaptors); + g_value_init (&value, G_TYPE_STRING); + + for (i = 0; i < evaspixmapsink->xcontext->nb_adaptors; i++) { + gchar *adaptor_id_s = g_strdup_printf ("%u", i); + + g_value_set_string (&value, adaptor_id_s); + g_value_array_append (array, &value); + g_free (adaptor_id_s); + } + g_value_unset (&value); + break; + } + case PROP_AUTOPAINT_COLORKEY: + if (evaspixmapsink->have_autopaint_colorkey) { + GValue value = { 0 }; + + array = g_value_array_new (2); + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, FALSE); + g_value_array_append (array, &value); + g_value_set_boolean (&value, TRUE); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + case PROP_DOUBLE_BUFFER: + if (evaspixmapsink->have_double_buffer) { + GValue value = { 0 }; + + array = g_value_array_new (2); + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, FALSE); + g_value_array_append (array, &value); + g_value_set_boolean (&value, TRUE); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + case PROP_COLORKEY: + if (evaspixmapsink->have_colorkey) { + GValue value = { 0 }; + + array = g_value_array_new (1); + g_value_init (&value, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&value, 0, 0xffffff); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } + +beach: + return array; +} + +static void +gst_evaspixmapsink_property_probe_interface_init (GstPropertyProbeInterface *iface) +{ + iface->get_properties = gst_evaspixmapsink_probe_get_properties; + iface->probe_property = gst_evaspixmapsink_probe_probe_property; + iface->needs_probe = gst_evaspixmapsink_probe_needs_probe; + iface->get_values = gst_evaspixmapsink_probe_get_values; +} + +static gboolean +gst_evaspixmapsink_xpixmap_link (GstEvasPixmapSink *evaspixmapsink) +{ + Display *dpy; + Pixmap *pixmap_id[NUM_OF_PIXMAP]; + int evas_object_width = 0; + int evas_object_height = 0; + int pixmap_width = 0; + int pixmap_height = 0; + unsigned int xw = 0; + unsigned int xh = 0; + int i = 0; + XGCValues gc_values_black; + gc_values_black.foreground = 0xff000000; +#ifdef DUMP_IMG + int ret = 0; + char file_name[128]; + char *dump_data; + int dump_w = 0; +#endif + + GST_DEBUG_OBJECT (evaspixmapsink,"[START]"); + + if (!evaspixmapsink) { + GST_ERROR_OBJECT (evaspixmapsink,"could not get evaspixmapsink.."); + return FALSE; + } + g_mutex_lock (evaspixmapsink->flow_lock); + + /* Set xcontext and display */ + if (!evaspixmapsink->xcontext) { + GST_WARNING_OBJECT (evaspixmapsink,"there's no xcontext, try to get one.."); + evaspixmapsink->xcontext = gst_evaspixmapsink_xcontext_get (evaspixmapsink); + if (!evaspixmapsink->xcontext) { + GST_ERROR_OBJECT (evaspixmapsink,"could not get xcontext.."); + g_mutex_unlock (evaspixmapsink->flow_lock); + return FALSE; + } + } + + /* Set evas image object size */ + evas_object_geometry_get(evaspixmapsink->eo, NULL, NULL, &evas_object_width, &evas_object_height); + + /* check if it is redundant request */ + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]) { + if (!evaspixmapsink->use_origin_size && evaspixmapsink->xpixmap[i]->width && evaspixmapsink->xpixmap[i]->height) { + if (evaspixmapsink->xpixmap[i]->width == evas_object_width && evaspixmapsink->xpixmap[i]->height == evas_object_height) { + GST_WARNING_OBJECT (evaspixmapsink,"pixmap was already created(w:%d, h:%d), skip it..", + evaspixmapsink->xpixmap[i]->width, evaspixmapsink->xpixmap[i]->height); + if (evaspixmapsink->need_to_fill_black) { + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + g_mutex_lock (evaspixmapsink->x_lock); + XFillRectangle (evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->pixmap, evaspixmapsink->xpixmap[i]->gc, + 0, 0, evaspixmapsink->xpixmap[i]->width, evaspixmapsink->xpixmap[i]->height); + GST_LOG_OBJECT (evaspixmapsink,"fill black to xpixmap[%d] with size(w:%d,h:%d)", evaspixmapsink->xpixmap[i]->pixmap, + evaspixmapsink->xpixmap[i]->width, evaspixmapsink->xpixmap[i]->height); +#ifdef DUMP_IMG + dump_w = evaspixmapsink->xpixmap[i]->width + (32 - (evaspixmapsink->xpixmap[i]->width%32)); // for multiples of 32 + if(g_cnt<100) + { + evaspixmapsink->pixmap_addr[i] = gst_evaspixmapsink_get_buffer(evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->pixmap, dump_w, evaspixmapsink->xpixmap[i]->height, i); + GST_ERROR(" pixmap[%d] addr %p (w : %d, h : %d)", g_cnt, i, evaspixmapsink->pixmap_addr[i], dump_w, evaspixmapsink->xpixmap[i]->height); + sprintf(file_name, "/opt/usr/media/DUMP_%2.2d.dump", g_cnt); + + dump_data = g_malloc(dump_w * evaspixmapsink->xpixmap[i]->height * 4); + memcpy (dump_data, evaspixmapsink->pixmap_addr[i], dump_w * evaspixmapsink->xpixmap[i]->height * 4); + + ret = util_write_rawdata(file_name, dump_data, dump_w * evaspixmapsink->xpixmap[i]->height * 4); + if (ret) { + GST_ERROR_OBJECT (evaspixmapsink, "util_write_rawdata() failed"); + } + else + g_cnt++; + g_free(dump_data); + + if (g_bufinfo[i]) { + gst_evaspixmapsink_free_buffer (i); + g_bufinfo[i] = NULL; + } + evaspixmapsink->pixmap_addr[i] = NULL; + } +#endif + g_mutex_unlock (evaspixmapsink->x_lock); + } + evaspixmapsink->need_to_fill_black = FALSE; + } + goto SKIP_LINK; + } + } + } + } + + dpy = evaspixmapsink->xcontext->disp; + + if (evaspixmapsink->use_origin_size || !evas_object_width || !evas_object_height) { + pixmap_width = evaspixmapsink->video_width; + pixmap_height = evaspixmapsink->video_height; + GST_INFO_OBJECT (evaspixmapsink,"set size to media src size(%dx%d)", pixmap_width, pixmap_height); + } + + g_mutex_lock (evaspixmapsink->x_lock); + evaspixmapsink->sizediff_width = 0; + evaspixmapsink->sizediff_height = 0; + if (evaspixmapsink->use_origin_size || !evas_object_width || !evas_object_height) { + XvQueryBestSize(dpy, evaspixmapsink->xcontext->xv_port_id,0,0,0, pixmap_width, pixmap_height, &xw, &xh); + if (!evas_object_width || !evas_object_height) { + evaspixmapsink->w = xw; + evaspixmapsink->h = xh; + } else { + evaspixmapsink->w = evas_object_width; + evaspixmapsink->h = evas_object_height; + } + GST_DEBUG_OBJECT (evaspixmapsink,"XvQueryBestSize : xv_port_id(%d), w(%d),h(%d) => xw(%d),xh(%d)", evaspixmapsink->xcontext->xv_port_id, pixmap_width, pixmap_height, xw, xh); + } else { + XvQueryBestSize(dpy, evaspixmapsink->xcontext->xv_port_id,0,0,0, evas_object_width, evas_object_height, &xw, &xh); + GST_DEBUG_OBJECT (evaspixmapsink,"XvQueryBestSize : xv_port_id(%d), w(%d),h(%d) => xw(%d),xh(%d)", evaspixmapsink->xcontext->xv_port_id, evas_object_width, evas_object_height, xw, xh); + evaspixmapsink->w = xw; + evaspixmapsink->h = xh; + /* update difference of size information (between evas image object's and pixmap's) */ + evaspixmapsink->sizediff_width = xw - evas_object_width; + evaspixmapsink->sizediff_height = xh - evas_object_height; + } + + /* create xpixmap structure */ + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (!evaspixmapsink->xpixmap[i]) { + /* xpixmap can be created in this function only */ + evaspixmapsink->xpixmap[i] = g_new0 (GstXPixmap, 1); + if(!evaspixmapsink->xpixmap[i]) { + GST_ERROR_OBJECT (evaspixmapsink,"xpixmap is not valid.."); + int j = 0; + for (j = 0; j < i; j++) { + g_free(evaspixmapsink->xpixmap[j]); + } + goto GO_OUT_OF_FUNC; + } + } + } + + /* create pixmap */ + if (!xw || !xh) { + GST_WARNING_OBJECT (evaspixmapsink,"skip creating pixmap..xw(%d),xh(%d)",xw,xh); + goto GO_OUT_OF_FUNC; + } else { + /* multiple pixmaps creation */ + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + pixmap_id[i] = XCreatePixmap(dpy, DefaultRootWindow(dpy), xw, xh, DefaultDepth(dpy, DefaultScreen(dpy))); + if ( (int)pixmap_id[i] == BadAlloc || (int)pixmap_id[i] == BadDrawable || (int)pixmap_id[i] == BadValue ) { + GST_ERROR_OBJECT (evaspixmapsink,"pixmap[%d] allocation error..", i); + int j = 0; + for (j = 0; j < i; j++) { + XFreePixmap(dpy, pixmap_id[j]); + } + goto GO_OUT_OF_FUNC; + } + evaspixmapsink->need_to_fill_black = FALSE; + GST_INFO_OBJECT (evaspixmapsink,"creation pixmap_id[%d]:%d success", i, pixmap_id[i]); + GST_DEBUG_OBJECT (evaspixmapsink,"evas_object_width(%d),evas_object_height(%d),pixmap:%d,depth:%d", + evas_object_width,evas_object_height,pixmap_id[i],DefaultDepth(dpy, DefaultScreen(dpy))); + } + } + + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]->pixmap && pixmap_id[i] != evaspixmapsink->xpixmap[i]->pixmap) { + g_mutex_unlock (evaspixmapsink->x_lock); + g_mutex_lock (evaspixmapsink->pixmap_ref_lock); + + /* If we reset another pixmap, do below */ + if (evaspixmapsink->xpixmap[i]->prev_pixmap) { + GST_LOG_OBJECT (evaspixmapsink,"Free pixmap(%d), gc(%x), destroy previous damage(%d)", + evaspixmapsink->xpixmap[i]->prev_pixmap, evaspixmapsink->xpixmap[i]->prev_gc, evaspixmapsink->prev_damage[i]); + g_mutex_lock (evaspixmapsink->x_lock); + XFreePixmap(evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->prev_pixmap); + XFreeGC (evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->prev_gc); + XDamageDestroy(evaspixmapsink->xcontext->disp, evaspixmapsink->prev_damage[i]); + XSync(evaspixmapsink->xcontext->disp, FALSE); + g_mutex_unlock (evaspixmapsink->x_lock); + evaspixmapsink->prev_damage[i] = NULL; + } + evaspixmapsink->xpixmap[i]->prev_pixmap = evaspixmapsink->xpixmap[i]->pixmap; + evaspixmapsink->xpixmap[i]->prev_gc = evaspixmapsink->xpixmap[i]->gc; + evaspixmapsink->xpixmap[i]->pixmap = 0; + evaspixmapsink->xpixmap[i]->ref = 0; + evaspixmapsink->xpixmap[i]->damaged_time = 0; + + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); + g_mutex_lock (evaspixmapsink->x_lock); + } + } + + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + /* Set pixmap id and create GC */ + evaspixmapsink->xpixmap[i]->pixmap = pixmap_id[i]; + evaspixmapsink->xpixmap[i]->gc = XCreateGC(dpy, evaspixmapsink->xpixmap[i]->pixmap, GCForeground, &gc_values_black); + evaspixmapsink->xpixmap[i]->width = xw; + evaspixmapsink->xpixmap[i]->height = xh; + + /* Create XDamage */ + if (evaspixmapsink->damage[i]) { + evaspixmapsink->prev_damage[i] = evaspixmapsink->damage[i]; + } + evaspixmapsink->damage[i] = XDamageCreate (dpy, evaspixmapsink->xpixmap[i]->pixmap, XDamageReportRawRectangles); + + GST_WARNING_OBJECT (evaspixmapsink,"xpixmap[%d]->(pixmap:%d,gc:%p), damage[%d]:%d", + i, evaspixmapsink->xpixmap[i]->pixmap, evaspixmapsink->xpixmap[i]->gc, i, evaspixmapsink->damage[i]); + + /* Fill blackcolor for the new pixmap */ + XFillRectangle (evaspixmapsink->xcontext->disp, pixmap_id[i], evaspixmapsink->xpixmap[i]->gc, + 0, 0, evaspixmapsink->w, evaspixmapsink->h); + GST_LOG_OBJECT (evaspixmapsink,"fill black to xpixmap[%d] with size(w:%d,h:%d)", pixmap_id[i], + evaspixmapsink->w, evaspixmapsink->h); + +#ifdef DUMP_IMG + dump_w = evaspixmapsink->w + (32 - (evaspixmapsink->w%32)); // for multiples of 32 + if(g_cnt<100) + { + evaspixmapsink->pixmap_addr[i] = gst_evaspixmapsink_get_buffer(evaspixmapsink->xcontext->disp, evaspixmapsink->xpixmap[i]->pixmap, dump_w, evaspixmapsink->h, i); + GST_ERROR(" pixmap[%d] addr %p (w : %d, h : %d)", g_cnt, i, evaspixmapsink->pixmap_addr[i], dump_w, evaspixmapsink->h); + sprintf(file_name, "/opt/usr/media/DUMP_%2.2d.dump", g_cnt); + + dump_data = g_malloc(dump_w * evaspixmapsink->h * 4); + memcpy (dump_data, evaspixmapsink->pixmap_addr[i], dump_w * evaspixmapsink->h * 4); + + ret = util_write_rawdata(file_name, dump_data, dump_w * evaspixmapsink->h * 4); + if (ret) { + GST_ERROR_OBJECT (evaspixmapsink, "util_write_rawdata() failed"); + } + else + g_cnt++; + g_free(dump_data); + + if (g_bufinfo[i]) { + gst_evaspixmapsink_free_buffer (i); + g_bufinfo[i] = NULL; + } + evaspixmapsink->pixmap_addr[i] = NULL; + } +#endif + } + + XSync(dpy, FALSE); + + /* Set flag for mapping evas object with xpixmap */ + evaspixmapsink->do_link = TRUE; + if (evaspixmapsink->epipe_request_count > EPIPE_REQUEST_LIMIT) { + GST_WARNING_OBJECT (evaspixmapsink,"epipe_request_count(%d), skip ecore_pipe_write()", evaspixmapsink->epipe_request_count); + } else { + ecore_pipe_write(evaspixmapsink->epipe, evaspixmapsink, sizeof(GstEvasPixmapSink)); + evaspixmapsink->epipe_request_count++; + } + + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + + g_mutex_unlock (evaspixmapsink->x_lock); + + +SKIP_LINK: + g_mutex_lock (evaspixmapsink->pixmap_ref_lock); + evaspixmapsink->last_updated_idx = -1; + g_mutex_unlock (evaspixmapsink->pixmap_ref_lock); + + g_mutex_unlock (evaspixmapsink->flow_lock); + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); + + return TRUE; + +GO_OUT_OF_FUNC: + g_mutex_unlock (evaspixmapsink->x_lock); + g_mutex_unlock (evaspixmapsink->flow_lock); + return FALSE; +} + +static void +gst_evaspixmapsink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstEvasPixmapSink *evaspixmapsink; + g_return_if_fail (GST_IS_EVASPIXMAPSINK (object)); + evaspixmapsink = GST_EVASPIXMAPSINK (object); + + switch (prop_id) { + case PROP_HUE: + evaspixmapsink->hue = g_value_get_int (value); + evaspixmapsink->cb_changed = TRUE; + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + break; + case PROP_CONTRAST: + evaspixmapsink->contrast = g_value_get_int (value); + evaspixmapsink->cb_changed = TRUE; + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + break; + case PROP_BRIGHTNESS: + evaspixmapsink->brightness = g_value_get_int (value); + evaspixmapsink->cb_changed = TRUE; + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + break; + case PROP_SATURATION: + evaspixmapsink->saturation = g_value_get_int (value); + evaspixmapsink->cb_changed = TRUE; + gst_evaspixmapsink_update_colorbalance (evaspixmapsink); + break; + case PROP_DISPLAY: + evaspixmapsink->display_name = g_strdup (g_value_get_string (value)); + break; + case PROP_SYNCHRONOUS: + evaspixmapsink->synchronous = g_value_get_boolean (value); + if (evaspixmapsink->xcontext) { + g_mutex_lock (evaspixmapsink->x_lock); + XSynchronize (evaspixmapsink->xcontext->disp, evaspixmapsink->synchronous); + g_mutex_unlock (evaspixmapsink->x_lock); + GST_DEBUG_OBJECT (evaspixmapsink,"XSynchronize called with %s", evaspixmapsink->synchronous ? "TRUE" : "FALSE"); + } + break; + case PROP_PIXEL_ASPECT_RATIO: + g_free (evaspixmapsink->par); + evaspixmapsink->par = g_new0 (GValue, 1); + g_value_init (evaspixmapsink->par, GST_TYPE_FRACTION); + if (!g_value_transform (value, evaspixmapsink->par)) { + g_warning ("Could not transform string to aspect ratio"); + gst_value_set_fraction (evaspixmapsink->par, 1, 1); + } + GST_DEBUG_OBJECT (evaspixmapsink,"set PAR to %d/%d", gst_value_get_fraction_numerator (evaspixmapsink->par), gst_value_get_fraction_denominator (evaspixmapsink->par)); + break; + case PROP_DEVICE: + evaspixmapsink->adaptor_no = atoi (g_value_get_string (value)); + break; + case PROP_DOUBLE_BUFFER: + evaspixmapsink->double_buffer = g_value_get_boolean (value); + break; + case PROP_AUTOPAINT_COLORKEY: + evaspixmapsink->autopaint_colorkey = g_value_get_boolean (value); + break; + case PROP_COLORKEY: + evaspixmapsink->colorkey = g_value_get_int (value); + break; + case PROP_PIXMAP_WIDTH: + { + /* To do : code related to pixmap re-link */ + GST_LOG_OBJECT (evaspixmapsink, "Not supported"); + break; + } + case PROP_PIXMAP_HEIGHT: + { + /* To do : code related to pixmap re-link */ + GST_LOG_OBJECT (evaspixmapsink, "Not supported"); + break; + } + case PROP_DISPLAY_GEOMETRY_METHOD: + { + guint new_val = g_value_get_enum (value); + if (evaspixmapsink->display_geometry_method != new_val) { + evaspixmapsink->display_geometry_method = new_val; + GST_INFO_OBJECT (evaspixmapsink,"Overlay geometry method update, display_geometry_method(%d)",evaspixmapsink->display_geometry_method); + if( evaspixmapsink->display_geometry_method != DISP_GEO_METHOD_FULL_SCREEN && + evaspixmapsink->display_geometry_method != DISP_GEO_METHOD_CROPPED_FULL_SCREEN ) { + if( evaspixmapsink->xcontext ) { + g_mutex_lock( evaspixmapsink->flow_lock ); + int i = 0; + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]) { + gst_evaspixmapsink_xpixmap_clear (evaspixmapsink, evaspixmapsink->xpixmap[i]); + } + } + g_mutex_unlock( evaspixmapsink->flow_lock ); + } + } + if (evaspixmapsink->xcontext) { + gst_evaspixmap_buffer_put (evaspixmapsink, evaspixmapsink->evas_pixmap_buf); + } + } + break; + } + case PROP_DST_ROI_X: + evaspixmapsink->dst_roi.x = g_value_get_int (value); + GST_INFO_OBJECT (evaspixmapsink, "ROI_X(%d)",evaspixmapsink->dst_roi.x ); + break; + case PROP_DST_ROI_Y: + evaspixmapsink->dst_roi.y = g_value_get_int (value); + GST_INFO_OBJECT (evaspixmapsink, "ROI_Y(%d)",evaspixmapsink->dst_roi.y ); + break; + case PROP_DST_ROI_W: + evaspixmapsink->dst_roi.w = g_value_get_int (value); + GST_INFO_OBJECT (evaspixmapsink, "ROI_W(%d)",evaspixmapsink->dst_roi.w ); + break; + case PROP_DST_ROI_H: + evaspixmapsink->dst_roi.h = g_value_get_int (value); + GST_INFO_OBJECT (evaspixmapsink, "ROI_H(%d)",evaspixmapsink->dst_roi.h ); + break; + case PROP_STOP_VIDEO: + evaspixmapsink->stop_video = g_value_get_int (value); + g_mutex_lock( evaspixmapsink->flow_lock ); + if( evaspixmapsink->stop_video ) { + GST_INFO_OBJECT (evaspixmapsink, "XPixmap CLEAR when set video-stop property" ); + int i = 0; + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]) { + gst_evaspixmapsink_xpixmap_clear (evaspixmapsink, evaspixmapsink->xpixmap[i]); + } + } + } + GST_INFO_OBJECT (evaspixmapsink, "video-stop property(%d)", evaspixmapsink->stop_video); + + g_mutex_unlock( evaspixmapsink->flow_lock ); + break; + case PROP_EVAS_OBJECT: + { + Evas_Object *eo = g_value_get_pointer (value); + if ( is_evas_image_object (eo)) { + if (!evaspixmapsink->epipe) { + evaspixmapsink->epipe = ecore_pipe_add (ecore_pipe_callback_handler, evaspixmapsink); + if (!evaspixmapsink->epipe) { + GST_ERROR_OBJECT (evaspixmapsink,"Cannot set evas-object property: ecore_pipe_add() failed"); + break; + } + } + if (evaspixmapsink->eo == NULL) { + evaspixmapsink->eo = eo; + /* add evas object callbacks on a new evas image object */ + EVASPIXMAPSINK_SET_EVAS_OBJECT_EVENT_CALLBACK (eo, evaspixmapsink); + if (GST_STATE(evaspixmapsink) < GST_STATE_PAUSED) { + GST_INFO_OBJECT (evaspixmapsink,"It's the first time the new evas image object(%x) is set, skip gst_evaspixmapsink_xpixmap_link()..", eo); + break; + } else { + if (!gst_evaspixmapsink_xpixmap_link(evaspixmapsink)) { + GST_WARNING_OBJECT (evaspixmapsink,"link evas image object with pixmap failed..."); + evaspixmapsink->eo = NULL; + break; + } + } + } + if (eo == evaspixmapsink->eo) { + GST_LOG_OBJECT (evaspixmapsink,"new evas image object(%x) is same as the previous one(%x)", eo, evaspixmapsink->eo); + } else { + GST_INFO_OBJECT (evaspixmapsink,"new evas image object(%x), previous one(%x)", eo, evaspixmapsink->eo); + /* delete evas object callbacks registrated on a former evas image object */ + EVASPIXMAPSINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK (evaspixmapsink->eo); + evaspixmapsink->eo = eo; + if (!gst_evaspixmapsink_xpixmap_link(evaspixmapsink)) { + GST_WARNING_OBJECT (evaspixmapsink,"link evas image object with pixmap failed..."); + evaspixmapsink->eo = NULL; + break; + } + /* add evas object callbacks on a new evas image object */ + EVASPIXMAPSINK_SET_EVAS_OBJECT_EVENT_CALLBACK (eo, evaspixmapsink); + } + GST_INFO_OBJECT (evaspixmapsink,"Evas image object(%x) is set", evaspixmapsink->eo); + } else { + GST_ERROR_OBJECT (evaspixmapsink,"Cannot set evas-object property: value is not an evas image object"); + } + break; + } + case PROP_FLIP: + evaspixmapsink->flip = g_value_get_enum(value); + break; + case PROP_ROTATE_ANGLE: + evaspixmapsink->rotate_angle = g_value_get_enum (value); + break; + case PROP_VISIBLE: + { + Eina_Bool r = EINA_FALSE; + gboolean visible = g_value_get_boolean (value); + GST_INFO_OBJECT (evaspixmapsink,"evaspixmapsink->visible(%d), new value of visible(%d)", evaspixmapsink->visible, visible); + if (evaspixmapsink->visible != visible) { + evaspixmapsink->visible = visible; + if (evaspixmapsink->eo) { + evaspixmapsink->update_visibility = UPDATE_TRUE; + if (evaspixmapsink->epipe_request_count > EPIPE_REQUEST_LIMIT) { + GST_WARNING_OBJECT (evaspixmapsink,"skip ecore_pipe_write()", evaspixmapsink->epipe_request_count); + if (visible) { + evas_object_show(evaspixmapsink->eo); + GST_WARNING_OBJECT (evaspixmapsink, "object show (forcely)"); + } + } else { + r = ecore_pipe_write (evaspixmapsink->epipe, &evaspixmapsink->update_visibility, SIZE_FOR_UPDATE_VISIBILITY); + if (r == EINA_FALSE) { + GST_WARNING ("Failed to ecore_pipe_write() for updating visibility)\n"); + } + evaspixmapsink->epipe_request_count++; + } + + if (!visible) { + int i = 0; + g_mutex_lock( evaspixmapsink->flow_lock ); + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if (evaspixmapsink->xpixmap[i]) { + gst_evaspixmapsink_xpixmap_clear (evaspixmapsink, evaspixmapsink->xpixmap[i]); + } + } + evas_object_hide(evaspixmapsink->eo); + GST_INFO_OBJECT (evaspixmapsink, "object hide (forcely)"); + g_mutex_unlock( evaspixmapsink->flow_lock ); + } else { + gst_evaspixmap_buffer_put (evaspixmapsink, evaspixmapsink->evas_pixmap_buf); + } + } else { + GST_WARNING_OBJECT (evaspixmapsink,"evas image object was not set"); + } + } + break; + } + case PROP_ORIGIN_SIZE: + evaspixmapsink->use_origin_size = g_value_get_boolean (value); + GST_INFO_OBJECT (evaspixmapsink,"set origin-size (%d)",evaspixmapsink->use_origin_size); + if (evaspixmapsink->previous_origin_size != evaspixmapsink->use_origin_size) { + if (!gst_evaspixmapsink_xpixmap_link(evaspixmapsink)) { + GST_WARNING_OBJECT (evaspixmapsink,"link evas image object with pixmap failed..."); + } + evaspixmapsink->previous_origin_size = evaspixmapsink->use_origin_size; + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_evaspixmapsink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstEvasPixmapSink *evaspixmapsink; + + g_return_if_fail (GST_IS_EVASPIXMAPSINK (object)); + + evaspixmapsink = GST_EVASPIXMAPSINK (object); + + switch (prop_id) { + case PROP_HUE: + g_value_set_int (value, evaspixmapsink->hue); + break; + case PROP_CONTRAST: + g_value_set_int (value, evaspixmapsink->contrast); + break; + case PROP_BRIGHTNESS: + g_value_set_int (value, evaspixmapsink->brightness); + break; + case PROP_SATURATION: + g_value_set_int (value, evaspixmapsink->saturation); + break; + case PROP_DISPLAY: + g_value_set_string (value, evaspixmapsink->display_name); + break; + case PROP_SYNCHRONOUS: + g_value_set_boolean (value, evaspixmapsink->synchronous); + break; + case PROP_PIXEL_ASPECT_RATIO: + if (evaspixmapsink->par) { + if (!g_value_transform (evaspixmapsink->par, value)) { + g_warning ("g_value_transform() failure"); + } + } + break; + case PROP_DEVICE: + { + char *adaptor_no_s = g_strdup_printf ("%u", evaspixmapsink->adaptor_no); + g_value_set_string (value, adaptor_no_s); + g_free (adaptor_no_s); + break; + } + case PROP_DEVICE_NAME: + if (evaspixmapsink->xcontext && evaspixmapsink->xcontext->adaptors) { + g_value_set_string (value, + evaspixmapsink->xcontext->adaptors[evaspixmapsink->adaptor_no]); + } else { + g_value_set_string (value, NULL); + } + break; + case PROP_DOUBLE_BUFFER: + g_value_set_boolean (value, evaspixmapsink->double_buffer); + break; + case PROP_AUTOPAINT_COLORKEY: + g_value_set_boolean (value, evaspixmapsink->autopaint_colorkey); + break; + case PROP_COLORKEY: + g_value_set_int (value, evaspixmapsink->colorkey); + break; + case PROP_PIXMAP_WIDTH: + { + GST_LOG_OBJECT (evaspixmapsink, "Not supported"); + break; + } + case PROP_PIXMAP_HEIGHT: + { + GST_LOG_OBJECT (evaspixmapsink, "Not supported"); + break; + } + case PROP_DISPLAY_GEOMETRY_METHOD: + g_value_set_enum (value, evaspixmapsink->display_geometry_method); + break; + case PROP_DST_ROI_X: + g_value_set_int (value, evaspixmapsink->dst_roi.x); + break; + case PROP_DST_ROI_Y: + g_value_set_int (value, evaspixmapsink->dst_roi.y); + break; + case PROP_DST_ROI_W: + g_value_set_int (value, evaspixmapsink->dst_roi.w); + break; + case PROP_DST_ROI_H: + g_value_set_int (value, evaspixmapsink->dst_roi.h); + break; + case PROP_STOP_VIDEO: + g_value_set_int (value, evaspixmapsink->stop_video); + break; + case PROP_EVAS_OBJECT: + g_value_set_pointer (value, evaspixmapsink->eo); + break; + case PROP_FLIP: + g_value_set_enum(value, evaspixmapsink->flip); + break; + case PROP_ROTATE_ANGLE: + g_value_set_enum (value, evaspixmapsink->rotate_angle); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, evaspixmapsink->visible); + break; + case PROP_ORIGIN_SIZE: + g_value_set_boolean (value, evaspixmapsink->use_origin_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_evaspixmapsink_reset (GstEvasPixmapSink *evaspixmapsink) +{ + GST_WARNING_OBJECT (evaspixmapsink,"[START]"); + + GThread *thread; + GST_OBJECT_LOCK (evaspixmapsink); + evaspixmapsink->running = FALSE; + int i = 0; + + /* grab thread and mark it as NULL */ + thread = evaspixmapsink->event_thread; + evaspixmapsink->event_thread = NULL; + GST_OBJECT_UNLOCK (evaspixmapsink); + + /* Wait for our event thread to finish before we clean up our stuff. */ + if (thread) { + g_thread_join (thread); + } + + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { + if(evaspixmapsink->damage[i]) { + g_mutex_lock (evaspixmapsink->x_lock); + XDamageDestroy(evaspixmapsink->xcontext->disp, evaspixmapsink->damage[i]); + g_mutex_unlock (evaspixmapsink->x_lock); + evaspixmapsink->damage[i] = NULL; + } + } + EVASPIXMAPSINK_UNSET_EVAS_OBJECT_EVENT_CALLBACK( evaspixmapsink->eo ); + + if (evaspixmapsink->evas_pixmap_buf) { + gst_buffer_unref (GST_BUFFER_CAST (evaspixmapsink->evas_pixmap_buf)); + evaspixmapsink->evas_pixmap_buf = NULL; + } + + for (i = 0; i < evaspixmapsink->num_of_pixmaps; i++) { +#ifdef DUMP_IMG + if (g_bufinfo[i]) { + gst_evaspixmapsink_free_buffer (i); + g_bufinfo[i] = NULL; + } + evaspixmapsink->pixmap_addr[i] = NULL; +#endif + gst_evaspixmapsink_xpixmap_clear (evaspixmapsink, evaspixmapsink->xpixmap[i]); + gst_evaspixmapsink_xpixmap_destroy (evaspixmapsink, evaspixmapsink->xpixmap[i]); + evaspixmapsink->xpixmap[i] = NULL; + } + if (evaspixmapsink->eo) { + evas_object_image_native_surface_set(evaspixmapsink->eo, NULL); + evas_object_image_data_set(evaspixmapsink->eo, NULL); + evaspixmapsink->eo = NULL; + } + + if (evaspixmapsink->epipe) { + ecore_pipe_del (evaspixmapsink->epipe); + evaspixmapsink->epipe = NULL; + } + + evaspixmapsink->render_rect.x = evaspixmapsink->render_rect.y = + evaspixmapsink->render_rect.w = evaspixmapsink->render_rect.h = 0; + evaspixmapsink->have_render_rect = FALSE; + + evaspixmapsink->epipe_request_count = 0; + + gst_evaspixmapsink_xcontext_clear (evaspixmapsink); + + GST_WARNING_OBJECT (evaspixmapsink,"[END]"); +} + +/* Finalize is called only once, dispose can be called multiple times. + * We use mutexes and don't reset stuff to NULL here so let's register + * as a finalize. */ +static void +gst_evaspixmapsink_finalize (GObject *object) +{ + GstEvasPixmapSink *evaspixmapsink; + evaspixmapsink = GST_EVASPIXMAPSINK (object); + GST_DEBUG_OBJECT (evaspixmapsink,"[START]"); + + if (evaspixmapsink->display_name) { + g_free (evaspixmapsink->display_name); + evaspixmapsink->display_name = NULL; + } + if (evaspixmapsink->par) { + g_free (evaspixmapsink->par); + evaspixmapsink->par = NULL; + } + if (evaspixmapsink->x_lock) { + g_mutex_free (evaspixmapsink->x_lock); + evaspixmapsink->x_lock = NULL; + } + if (evaspixmapsink->flow_lock) { + g_mutex_free (evaspixmapsink->flow_lock); + evaspixmapsink->flow_lock = NULL; + } + if (evaspixmapsink->pixmap_ref_lock) { + g_mutex_free (evaspixmapsink->pixmap_ref_lock); + evaspixmapsink->pixmap_ref_lock = NULL; + } + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); + + G_OBJECT_CLASS (parent_class)->finalize (object); + +} + +static void +gst_evaspixmapsink_init (GstEvasPixmapSink *evaspixmapsink) +{ + int i = 0; + + GST_DEBUG_OBJECT (evaspixmapsink,"[START]"); + + evaspixmapsink->display_name = NULL; + evaspixmapsink->adaptor_no = 0; + evaspixmapsink->xcontext = NULL; + + for (i = 0; i < NUM_OF_PIXMAP; i++) { + evaspixmapsink->xpixmap[i] = NULL; + evaspixmapsink->damage[i] = 0; +#ifdef DUMP_IMG + g_bufinfo[i] = NULL; + evaspixmapsink->pixmap_addr[i] = NULL; +#endif + } + + evaspixmapsink->evas_pixmap_buf = NULL; + + evaspixmapsink->hue = evaspixmapsink->saturation = 0; + evaspixmapsink->contrast = evaspixmapsink->brightness = 0; + evaspixmapsink->cb_changed = FALSE; + + evaspixmapsink->fps_n = 0; + evaspixmapsink->fps_d = 0; + evaspixmapsink->video_width = 0; + evaspixmapsink->video_height = 0; + evaspixmapsink->need_to_fill_black = FALSE; + + evaspixmapsink->x_lock = g_mutex_new (); + evaspixmapsink->flow_lock = g_mutex_new (); + evaspixmapsink->pixmap_ref_lock = g_mutex_new(); + + evaspixmapsink->synchronous = FALSE; + evaspixmapsink->double_buffer = TRUE; + evaspixmapsink->par = NULL; + evaspixmapsink->autopaint_colorkey = TRUE; + evaspixmapsink->running = FALSE; + + /* on 16bit displays this becomes r,g,b = 1,2,3 + * on 24bit displays this becomes r,g,b = 8,8,16 + * as a port atom value + */ + evaspixmapsink->colorkey = (8 << 16) | (8 << 8) | 16; + + evaspixmapsink->display_geometry_method = DEF_DISPLAY_GEOMETRY_METHOD; + evaspixmapsink->dst_roi.x = 0; + evaspixmapsink->dst_roi.y = 0; + evaspixmapsink->dst_roi.w = 0; + evaspixmapsink->dst_roi.h = 0; + evaspixmapsink->scr_w = 0; + evaspixmapsink->scr_h = 0; + evaspixmapsink->aligned_width = 0; + evaspixmapsink->aligned_height = 0; + evaspixmapsink->stop_video = FALSE; + evaspixmapsink->eo = NULL; + evaspixmapsink->epipe = NULL; + evaspixmapsink->epipe_request_count = 0; + evaspixmapsink->do_link = FALSE; + evaspixmapsink->flip = DEF_DISPLAY_FLIP; + evaspixmapsink->rotate_angle = DEGREE_0; + evaspixmapsink->visible = TRUE; + evaspixmapsink->update_visibility = UPDATE_FALSE; + evaspixmapsink->use_origin_size = FALSE; + evaspixmapsink->previous_origin_size = FALSE; + + evaspixmapsink->num_of_pixmaps = NUM_OF_PIXMAP; + + evaspixmapsink->buf_shared_type = BUF_SHARE_METHOD_NONE; + + memset(&evaspixmapsink->src_prev, 0, sizeof (GstVideoRectangle)); + memset(&evaspixmapsink->result_prev, 0, sizeof (GstVideoRectangle)); + +#ifdef DUMP_IMG + g_cnt = 0; +#endif + + GST_DEBUG_OBJECT (evaspixmapsink,"[END]"); + } + +static void +gst_evaspixmapsink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details_simple (element_class, + "EvasPixmapSink", "Sink/Video", + "evas image object videosink based on Xv extension", "Sangchul Lee "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_evaspixmapsink_sink_template_factory)); +} + +static void +gst_evaspixmapsink_class_init (GstEvasPixmapSinkClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstVideoSinkClass *videosink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + videosink_class = (GstVideoSinkClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gst_evaspixmapsink_set_property; + gobject_class->get_property = gst_evaspixmapsink_get_property; + + g_object_class_install_property (gobject_class, PROP_CONTRAST, + g_param_spec_int ("contrast", "Contrast", "The contrast of the video", + -1000, 1000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BRIGHTNESS, + g_param_spec_int ("brightness", "Brightness", + "The brightness of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_HUE, + g_param_spec_int ("hue", "Hue", "The hue of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SATURATION, + g_param_spec_int ("saturation", "Saturation", + "The saturation of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DISPLAY, + g_param_spec_string ("display", "Display", "X Display name", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS, + g_param_spec_boolean ("synchronous", "Synchronous", + "When enabled, runs " + "the X display in synchronous mode. (used only for debugging)", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO, + g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", "1/1", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Adaptor number", + "The number of the video adaptor", "0", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Adaptor name", + "The name of the video adaptor", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:double-buffer + * + * Whether to double-buffer the output. + * + * Since: 0.10.14 + */ + g_object_class_install_property (gobject_class, PROP_DOUBLE_BUFFER, + g_param_spec_boolean ("double-buffer", "Double-buffer", + "Whether to double-buffer the output", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstEvasPixmapSink:autopaint-colorkey + * + * Whether to autofill overlay with colorkey + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_AUTOPAINT_COLORKEY, + g_param_spec_boolean ("autopaint-colorkey", "Autofill with colorkey", + "Whether to autofill overlay with colorkey", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstEvasPixmapSink:colorkey + * + * Color to use for the overlay mask. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, PROP_COLORKEY, + g_param_spec_int ("colorkey", "Colorkey", + "Color to use for the overlay mask", G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:pixmap-width + * + * Actual width of the pixmap. + */ + g_object_class_install_property (gobject_class, PROP_PIXMAP_WIDTH, + g_param_spec_uint64 ("pixmap-width", "pixmap-width", "Width of the pixmap", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:pixmap-height + * + * Actual height of the pixmap. + */ + g_object_class_install_property (gobject_class, PROP_PIXMAP_HEIGHT, + g_param_spec_uint64 ("pixmap-height", "pixmap-height", "Height of the pixmap", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:display-geometry-method + * + * Display geometrical method setting + */ + g_object_class_install_property(gobject_class, PROP_DISPLAY_GEOMETRY_METHOD, + g_param_spec_enum("display-geometry-method", "Display geometry method", + "Geometrical method for display", + GST_TYPE_EVASPIXMAPSINK_DISPLAY_GEOMETRY_METHOD, DEF_DISPLAY_GEOMETRY_METHOD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:dst-roi-x + * + * X value of Destination ROI + */ + g_object_class_install_property (gobject_class, PROP_DST_ROI_X, + g_param_spec_int ("dst-roi-x", "Dst-ROI-X", + "X value of Destination ROI(only effective \"CUSTOM_ROI\")", 0, XV_SCREEN_SIZE_WIDTH, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:dst-roi-y + * + * Y value of Destination ROI + */ + g_object_class_install_property (gobject_class, PROP_DST_ROI_Y, + g_param_spec_int ("dst-roi-y", "Dst-ROI-Y", + "Y value of Destination ROI(only effective \"CUSTOM_ROI\")", 0, XV_SCREEN_SIZE_HEIGHT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:dst-roi-w + * + * W value of Destination ROI + */ + g_object_class_install_property (gobject_class, PROP_DST_ROI_W, + g_param_spec_int ("dst-roi-w", "Dst-ROI-W", + "W value of Destination ROI(only effective \"CUSTOM_ROI\")", 0, XV_SCREEN_SIZE_WIDTH, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:dst-roi-h + * + * H value of Destination ROI + */ + g_object_class_install_property (gobject_class, PROP_DST_ROI_H, + g_param_spec_int ("dst-roi-h", "Dst-ROI-H", + "H value of Destination ROI(only effective \"CUSTOM_ROI\")", 0, XV_SCREEN_SIZE_HEIGHT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:stop-video + * + * Stop video for releasing video source buffer + */ + g_object_class_install_property (gobject_class, PROP_STOP_VIDEO, + g_param_spec_int ("stop-video", "Stop-Video", "Stop video for releasing video source buffer", 0, 1, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:evas-object + * + * Evas image object for rendering + */ + g_object_class_install_property (gobject_class, PROP_EVAS_OBJECT, + g_param_spec_pointer ("evas-object", "Destination Evas Object", "Destination evas image object", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:display-flip + * + * Display flip setting + */ + g_object_class_install_property(gobject_class, PROP_FLIP, + g_param_spec_enum("flip", "Display flip", + "Flip for display", + GST_TYPE_EVASPIXMAPSINK_FLIP, DEF_DISPLAY_FLIP, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:rotate + * + * draw rotation angle setting + */ + g_object_class_install_property(gobject_class, PROP_ROTATE_ANGLE, + g_param_spec_enum("rotate", "Rotate angle", "Rotate angle of display output",GST_TYPE_EVASPIXMAPSINK_ROTATE_ANGLE, DEGREE_0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:visible + * + * visible setting for a evas image object + */ + g_object_class_install_property (gobject_class, PROP_VISIBLE, + g_param_spec_boolean ("visible", "Visible", "When setting it false, evas image object does not show", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstEvasPixmapSink:origin-size + * + * Set pixmap size with media source's width and height + */ + g_object_class_install_property (gobject_class, PROP_ORIGIN_SIZE, + g_param_spec_boolean ("origin-size", "Origin-Size", "When setting it true, pixmap will be created with media source's width and height", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gobject_class->finalize = gst_evaspixmapsink_finalize; + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_change_state); + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_getcaps); + gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_setcaps); + gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_get_times); + gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_event); + videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_evaspixmapsink_show_frame); +} + +/* Object typing & Creation */ +GType +gst_evaspixmapsink_get_type (void) +{ + static GType evaspixmapsink_type = 0; + + if (!evaspixmapsink_type) { + static const GTypeInfo evaspixmapsink_info = { + sizeof (GstEvasPixmapSinkClass), + gst_evaspixmapsink_base_init, + NULL, + (GClassInitFunc) gst_evaspixmapsink_class_init, + NULL, + NULL, + sizeof (GstEvasPixmapSink), + 0, + (GInstanceInitFunc) gst_evaspixmapsink_init, + }; + static const GInterfaceInfo iface_info = { + (GInterfaceInitFunc) gst_evaspixmapsink_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo navigation_info = { + (GInterfaceInitFunc) gst_evaspixmapsink_navigation_init, + NULL, + NULL, + }; + static const GInterfaceInfo colorbalance_info = { + (GInterfaceInitFunc) gst_evaspixmapsink_colorbalance_init, + NULL, + NULL, + }; + static const GInterfaceInfo propertyprobe_info = { + (GInterfaceInitFunc) gst_evaspixmapsink_property_probe_interface_init, + NULL, + NULL, + }; + evaspixmapsink_type = g_type_register_static (GST_TYPE_VIDEO_SINK, "GstEvasPixmapSink", &evaspixmapsink_info, 0); + + g_type_add_interface_static (evaspixmapsink_type, GST_TYPE_IMPLEMENTS_INTERFACE, &iface_info); + g_type_add_interface_static (evaspixmapsink_type, GST_TYPE_NAVIGATION, &navigation_info); + g_type_add_interface_static (evaspixmapsink_type, GST_TYPE_COLOR_BALANCE, &colorbalance_info); + g_type_add_interface_static (evaspixmapsink_type, GST_TYPE_PROPERTY_PROBE, &propertyprobe_info); + + /* register type and create class in a more safe place instead of at + * runtime since the type registration and class creation is not + * threadsafe. */ + g_type_class_ref (gst_evaspixmap_buffer_get_type ()); + } + + return evaspixmapsink_type; +} + +static gboolean +plugin_init (GstPlugin *plugin) +{ + if (!gst_element_register (plugin, "evaspixmapsink", GST_RANK_NONE, GST_TYPE_EVASPIXMAPSINK)) { + return FALSE; + } + GST_DEBUG_CATEGORY_INIT (gst_debug_evaspixmapsink, "evaspixmapsink", 0, "evaspixmapsink element"); + GST_DEBUG_CATEGORY_GET (GST_CAT_PERFORMANCE, "GST_PERFORMANCE"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "evaspixmapsink","Evas image object render plugin using Xv extension", plugin_init, + VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/evaspixmapsink/evaspixmapsink.h b/evaspixmapsink/evaspixmapsink.h new file mode 100755 index 0000000..25d8495 --- /dev/null +++ b/evaspixmapsink/evaspixmapsink.h @@ -0,0 +1,416 @@ +/* + * EvasPixmapSink + * + * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Sangchul Lee + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GST_EVASPIXMAPSINK_H__ +#define __GST_EVASPIXMAPSINK_H__ + +#include + +#ifdef HAVE_XSHM +#include +#include +#include +#endif /* HAVE_XSHM */ + +#include +#include + +#ifdef HAVE_XSHM +#include +#endif /* HAVE_XSHM */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +//#define DUMP_IMG + +#define MAX_PLANE_NUM 3 +#define MAX_BUFFER_NUM 20 +#define MAX_GEM_BUFFER_NUM (MAX_PLANE_NUM * MAX_BUFFER_NUM) +typedef struct _gem_info_t { + int dmabuf_fd; + unsigned int gem_handle; + unsigned int gem_name; + tbm_bo bo; + Pixmap ref_pixmap; +} gem_info_t; + +typedef enum { + BUF_SHARE_METHOD_NONE = -1, + BUF_SHARE_METHOD_PADDR = 0, + BUF_SHARE_METHOD_FD, + BUF_SHARE_METHOD_TIZEN_BUFFER +} buf_share_method_t; + +G_BEGIN_DECLS + +#define GST_TYPE_EVASPIXMAPSINK \ + (gst_evaspixmapsink_get_type()) +#define GST_EVASPIXMAPSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_EVASPIXMAPSINK, GstEvasPixmapSink)) +#define GST_EVASPIXMAPSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_EVASPIXMAPSINK, GstEvasPixmapSinkClass)) +#define GST_IS_EVASPIXMAPSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_EVASPIXMAPSINK)) +#define GST_IS_EVASPIXMAPSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_EVASPIXMAPSINK)) + +#define XV_SCREEN_SIZE_WIDTH 4096 +#define XV_SCREEN_SIZE_HEIGHT 4096 + +#define MARGIN_OF_ERROR 0.005 +#define NUM_OF_PIXMAP 3 + +typedef struct _GstXContext GstXContext; +typedef struct _GstXPixmap GstXPixmap; +typedef struct _GstEvasPixmapFormat GstEvasPixmapFormat; +typedef struct _GstEvasPixmapBuffer GstEvasPixmapBuffer; +typedef struct _GstEvasPixmapBufferClass GstEvasPixmapBufferClass; +typedef struct _GstEvasPixmapSink GstEvasPixmapSink; +typedef struct _GstEvasPixmapSinkClass GstEvasPixmapSinkClass; + +/* + * GstXContext: + * @disp: the X11 Display of this context + * @screen: the default Screen of Display @disp + * @screen_num: the Screen number of @screen + * @visual: the default Visual of Screen @screen + * @root: the root Window of Display @disp + * @white: the value of a white pixel on Screen @screen + * @black: the value of a black pixel on Screen @screen + * @depth: the color depth of Display @disp + * @bpp: the number of bits per pixel on Display @disp + * @endianness: the endianness of image bytes on Display @disp + * @width: the width in pixels of Display @disp + * @height: the height in pixels of Display @disp + * @widthmm: the width in millimeters of Display @disp + * @heightmm: the height in millimeters of Display @disp + * @par: the pixel aspect ratio calculated from @width, @widthmm and @height, + * @heightmm ratio + * @use_xshm: used to known wether of not XShm extension is usable or not even + * if the Extension is present + * @xv_port_id: the XVideo port ID + * @im_format: used to store at least a valid format for XShm calls checks + * @formats_list: list of supported image formats on @xv_port_id + * @channels_list: list of #GstColorBalanceChannels + * @caps: the #GstCaps that Display @disp can accept + * + * Structure used to store various informations collected/calculated for a + * Display. + */ +struct _GstXContext { + Display *disp; + + Screen *screen; + gint screen_num; + + Visual *visual; + + Window root; + + gulong white; + + gint depth; + gint bpp; + gint endianness; + + gint width, height; + gint widthmm, heightmm; + GValue *par; /* calculated pixel aspect ratio */ + + gboolean use_xshm; + + XvPortID xv_port_id; + guint nb_adaptors; + gchar ** adaptors; + gint im_format; + + GList *formats_list; + GList *channels_list; + + GstCaps *caps; +}; + +/* + * GstXPixmap: + * @pixmap: the pixmap ID of this X11 pixmap + * @width: the width in pixels of Pixmap @pixmap + * @height: the height in pixels of Pixmap @pixmap + * @gc: the Graphical Context of Pixmap @pixmap + * + * Structure used to store informations about a Pixmap. + */ +struct _GstXPixmap { + Pixmap pixmap; + gint x, y; + gint width, height; + GC gc; + guint ref; + gint damaged_time; + Pixmap prev_pixmap; + GC prev_gc; +}; + +/** + * GstEvasPixmapFormat: + * @format: the image format + * @caps: generated #GstCaps for this image format + * + * Structure storing image format to #GstCaps association. + */ +struct _GstEvasPixmapFormat { + gint format; + GstCaps *caps; +}; + +/** + * GstEvasPixmapBuffer: + * @evaspixmapsink: a reference to our #GstEvasPixmapSink + * @xvimage: the XvImage of this buffer + * @width: the width in pixels of XvImage @xvimage + * @height: the height in pixels of XvImage @xvimage + * @im_format: the image format of XvImage @xvimage + * @size: the size in bytes of XvImage @xvimage + * + * Subclass of #GstBuffer containing additional information about an XvImage. + */ +struct _GstEvasPixmapBuffer { + GstBuffer buffer; + + /* Reference to the evaspixmapsink we belong to */ + GstEvasPixmapSink *evaspixmapsink; + XvImage *xvimage; + +#ifdef HAVE_XSHM + XShmSegmentInfo SHMInfo; +#endif /* HAVE_XSHM */ + + gint width, height; + gint im_format; + size_t size; +}; + +/** + * GstEvasPixmapSink: + * @display_name: the name of the Display we want to render to + * @xcontext: our instance's #GstXContext + * @xpixmap: the #GstXPixmap we are rendering to + * @fps_n: the framerate fraction numerator + * @fps_d: the framerate fraction denominator + * @x_lock: used to protect X calls as we are not using the XLib in threaded + * mode + * @flow_lock: used to protect data flow routines from external calls such as + * methods from the #GstXOverlay interface + * @par: used to override calculated pixel aspect ratio from @xcontext + * @synchronous: used to store if XSynchronous should be used or not (for + * debugging purpose only) + * @keep_aspect: used to remember if reverse negotiation scaling should respect + * aspect ratio + * @brightness: used to store the user settings for color balance brightness + * @contrast: used to store the user settings for color balance contrast + * @hue: used to store the user settings for color balance hue + * @saturation: used to store the user settings for color balance saturation + * @cb_changed: used to store if the color balance settings where changed + * @video_width: the width of incoming video frames in pixels + * @video_height: the height of incoming video frames in pixels + * + * The #GstEvasPixmapSink data structure. + */ +struct _GstEvasPixmapSink { + /* Our element stuff */ + GstVideoSink videosink; + + char *display_name; + guint adaptor_no; + + GstXContext *xcontext; + GstXPixmap *xpixmap[NUM_OF_PIXMAP]; + GstEvasPixmapBuffer *evas_pixmap_buf; + + GThread *event_thread; + gboolean running; + + gint fps_n; + gint fps_d; + + GMutex *x_lock; + GMutex *flow_lock; + GMutex *pixmap_ref_lock; + + /* object-set pixel aspect ratio */ + GValue *par; + + gboolean synchronous; + gboolean double_buffer; + + gint brightness; + gint contrast; + gint hue; + gint saturation; + gboolean cb_changed; + + /* size of incoming video, used as the size for XvImage */ + guint video_width, video_height; + gboolean need_to_fill_black; + + /* display sizes, used for clipping the image */ + gint disp_x, disp_y; + gint disp_width, disp_height; + + /* port attributes */ + gboolean autopaint_colorkey; + gint colorkey; + + /* port features */ + gboolean have_autopaint_colorkey; + gboolean have_colorkey; + gboolean have_double_buffer; + + /* target video rectagle */ + GstVideoRectangle render_rect; + gboolean have_render_rect; + + /* display */ + guint flip; + guint rotate_angle; + guint display_geometry_method; + GstVideoRectangle dst_roi; + guint scr_w, scr_h; + /* needed if fourcc is one if S series */ + guint aligned_width; + guint aligned_height; + + gboolean stop_video; + buf_share_method_t buf_shared_type; + + /* ecore & evas object */ + Ecore_Pipe *epipe; + Evas_Object *eo; + Evas_Coord w; + Evas_Coord h; + gboolean visible; + gint last_updated_idx; + gchar update_visibility; + gint epipe_request_count; + + /* pixmap */ + gboolean do_link; + gboolean use_origin_size; + gboolean previous_origin_size; + gint sizediff_width; + gint sizediff_height; + guint num_of_pixmaps; + + /* damage event */ + Damage damage[NUM_OF_PIXMAP]; + Damage prev_damage[NUM_OF_PIXMAP]; + int damage_case; + + gint drm_fd; + gem_info_t gem_info[MAX_GEM_BUFFER_NUM]; + + /* if needed combine planes data */ + gint need_combine_data; + + GstVideoRectangle src_prev; + GstVideoRectangle result_prev; + +#ifdef DUMP_IMG + void *pixmap_addr[NUM_OF_PIXMAP]; +#endif +}; + +/* max plane count *********************************************************/ +#define MPLANE_IMGB_MAX_COUNT (4) + +/* image buffer definition *************************************************** + + +------------------------------------------+ --- + | | ^ + | uaddr[], index[] | | + | +---------------------------+ --- | | + | | | ^ | | + | |<-------- width[] -------->| | | | + | | | | | | + | | | | + | | |height[]|elevation[] + | | | | + | | | | | | + | | | | | | + | | | v | | + | +---------------------------+ --- | | + | | v + +------------------------------------------+ --- + + |<----------------- stride[] ------------------>| +*/ +typedef struct _GstMultiPlaneImageBuffer GstMultiPlaneImageBuffer; +struct _GstMultiPlaneImageBuffer +{ + GstBuffer buffer; + + /* width of each image plane */ + gint width[MPLANE_IMGB_MAX_COUNT]; + /* height of each image plane */ + gint height[MPLANE_IMGB_MAX_COUNT]; + /* stride of each image plane */ + gint stride[MPLANE_IMGB_MAX_COUNT]; + /* elevation of each image plane */ + gint elevation[MPLANE_IMGB_MAX_COUNT]; + /* user space address of each image plane */ + gpointer uaddr[MPLANE_IMGB_MAX_COUNT]; + /* Index of real address of each image plane, if needs */ + gpointer index[MPLANE_IMGB_MAX_COUNT]; + /* left postion, if needs */ + gint x; + /* top position, if needs */ + gint y; + /* to align memory */ + gint __dummy2; + /* arbitrary data */ + gint data[16]; +}; + +struct _GstEvasPixmapSinkClass { + GstVideoSinkClass parent_class; +}; + +GType gst_evaspixmapsink_get_type(void); + +G_END_DECLS + +#endif /* __GST_EVASPIXMAPSINK_H__ */ diff --git a/evaspixmapsink/xv_types.h b/evaspixmapsink/xv_types.h new file mode 100644 index 0000000..8266e34 --- /dev/null +++ b/evaspixmapsink/xv_types.h @@ -0,0 +1,101 @@ +/************************************************************************** + +xserver-xorg-video-exynos + +Copyright 2010 - 2011 Samsung Electronics co., Ltd. All Rights Reserved. + +Contact: Boram Park + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +/* */ +/* File name : xv_types.h */ +/* Author : Boram Park (boram1288.park@samsung.com) */ +/* Protocol Version : 1.0.1 (Dec 16th 2009) */ +/* This file is for describing Xv APIs' buffer encoding method. */ +/* */ + +#ifndef __XV_TYPE_H__ +#define __XV_TYPE_H__ + +#define XV_DATA_HEADER 0xDEADCD01 +#define XV_DATA_VERSION 0x00010001 + +/* Return Values */ +#define XV_OK 0 +#define XV_HEADER_ERROR -1 +#define XV_VERSION_MISMATCH -2 + +/* Video Mode */ +#define DISPLAY_MODE_DEFAULT 0 +#define DISPLAY_MODE_PRI_VIDEO_ON_AND_SEC_VIDEO_FULL_SCREEN 1 +#define DISPLAY_MODE_PRI_VIDEO_OFF_AND_SEC_VIDEO_FULL_SCREEN 2 + +/* Color space range */ +#define CSC_RANGE_NARROW 0 +#define CSC_RANGE_WIDE 1 + +/* Buffer Type */ +#define XV_BUF_TYPE_DMABUF 0 +#define XV_BUF_TYPE_LEGACY 1 +#define XV_BUF_PLANE_NUM 3 + +/* Data structure for XvPutImage / XvShmPutImage */ +typedef struct +{ + unsigned int _header; /* for internal use only */ + unsigned int _version; /* for internal use only */ + + unsigned int YBuf; + unsigned int CbBuf; + unsigned int CrBuf; + unsigned int BufType; + unsigned int dmabuf_fd[XV_BUF_PLANE_NUM]; + unsigned int gem_handle[XV_BUF_PLANE_NUM]; + void *bo[XV_BUF_PLANE_NUM]; +} XV_DATA, * XV_DATA_PTR; + +static void +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif +XV_INIT_DATA (XV_DATA_PTR data) +{ + data->_header = XV_DATA_HEADER; + data->_version = XV_DATA_VERSION; +} + +static int +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif +XV_VALIDATE_DATA (XV_DATA_PTR data) +{ + if (data->_header != XV_DATA_HEADER) + return XV_HEADER_ERROR; + if (data->_version != XV_DATA_VERSION) + return XV_VERSION_MISMATCH; + return XV_OK; +} + +#endif diff --git a/gst-plugins-ext0.10.manifest b/gst-plugins-ext0.10.manifest new file mode 100755 index 0000000..a76fdba --- /dev/null +++ b/gst-plugins-ext0.10.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/hlsdemux2/Makefile.am b/hlsdemux2/Makefile.am new file mode 100755 index 0000000..308a09c --- /dev/null +++ b/hlsdemux2/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/hlsdemux2/predefined_frame/blackframe_QVGA.264 b/hlsdemux2/predefined_frame/blackframe_QVGA.264 new file mode 100755 index 0000000..03e3d8a Binary files /dev/null and b/hlsdemux2/predefined_frame/blackframe_QVGA.264 differ diff --git a/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.264 b/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.264 new file mode 100755 index 0000000..068599c Binary files /dev/null and b/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.264 differ diff --git a/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.jpg b/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.jpg new file mode 100755 index 0000000..55d43b4 Binary files /dev/null and b/hlsdemux2/predefined_frame/sec_audio_fixed_qvga.jpg differ diff --git a/hlsdemux2/src/Makefile.am b/hlsdemux2/src/Makefile.am new file mode 100755 index 0000000..88525c3 --- /dev/null +++ b/hlsdemux2/src/Makefile.am @@ -0,0 +1,31 @@ + +plugin_LTLIBRARIES = libgsthlsdemux2.la + +libgsthlsdemux2_la_SOURCES = \ + m3u8.c \ + gsthlsdemux2.c + +libgsthlsdemux2_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(SOUP_CFLAGS) $(GST_APP_CFLAGS) $(CRYPTO_CFLAGS) +libgsthlsdemux2_la_LIBADD = -lgsttag-@GST_MAJORMINOR@ \ + $(GST_LIBS) $(GST_BASE_LIBS) $(SOUP_LIBS) $(GST_APP_LIBS) -lgstapp-0.10 $(CRYPTO_LIBS) +libgsthlsdemux2_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -no-undefined +libgsthlsdemux2_la_LIBTOOLFLAGS = --tag=disable-static + +# headers we need but don't want installed +noinst_HEADERS = \ + gsthlsdemux2.h \ + m3u8.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgsthls -:SHARED libgsthls \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgsthls_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgsthls_la_CFLAGS) \ + -:LDFLAGS $(libgsthls_la_LDFLAGS) \ + $(libgsthls_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/hlsdemux2/src/gsthlsdemux2.c b/hlsdemux2/src/gsthlsdemux2.c new file mode 100755 index 0000000..4f378d2 --- /dev/null +++ b/hlsdemux2/src/gsthlsdemux2.c @@ -0,0 +1,5443 @@ +/* + * hlsdemux2 + * + * Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: JongHyuk Choi , Naveen Cherukuri + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include +#include +#include +#include +#include +#include +#include + +#include "gsthlsdemux2.h" + +enum +{ + PROP_0, + PROP_BITRATE_SWITCH_TOLERANCE, + PROP_FORCE_LOWER_BITRATE, + PROP_BLOCKSIZE, + PROP_LAST +}; + +#define HLS_VIDEO_CAPS \ + GST_STATIC_CAPS (\ + "video/mpeg, " \ + "mpegversion = (int) { 1, 2, 4 }, " \ + "systemstream = (boolean) FALSE; " \ + "video/x-h264,stream-format=(string)byte-stream," \ + "alignment=(string)nal;" \ + "video/x-dirac;" \ + "video/x-wmv," \ + "wmvversion = (int) 3, " \ + "format = (fourcc) WVC1" \ + ) + +#define HLS_AUDIO_CAPS \ + GST_STATIC_CAPS ( \ + "audio/mpeg, " \ + "mpegversion = (int) { 1, 4 };" \ + "audio/x-lpcm, " \ + "width = (int) { 16, 20, 24 }, " \ + "rate = (int) { 48000, 96000 }, " \ + "channels = (int) [ 1, 8 ], " \ + "dynamic_range = (int) [ 0, 255 ], " \ + "emphasis = (boolean) { FALSE, TRUE }, " \ + "mute = (boolean) { FALSE, TRUE }; " \ + "audio/x-ac3; audio/x-eac3;" \ + "audio/x-dts;" \ + "audio/x-private-ts-lpcm;" \ + "application/x-id3" \ + ) + +/* Can also use the subpicture pads for text subtitles? */ +#define HLS_SUBPICTURE_CAPS \ + GST_STATIC_CAPS ("subpicture/x-pgs; video/x-dvd-subpicture") + +static GstStaticPadTemplate hlsdemux2_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-hls")); + +static GstStaticPadTemplate hlsdemux2_videosrc_template = +GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + HLS_VIDEO_CAPS); + +static GstStaticPadTemplate hlsdemux2_audiosrc_template = +GST_STATIC_PAD_TEMPLATE ("audio", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + HLS_AUDIO_CAPS); + +static GstStaticPadTemplate hlsdemux2_subpicture_template = +GST_STATIC_PAD_TEMPLATE ("subpicture", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + HLS_SUBPICTURE_CAPS); + +static GstStaticPadTemplate hlsdemux2_private_template = +GST_STATIC_PAD_TEMPLATE ("private", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gst_hlsdemux2_debug); +#define GST_CAT_DEFAULT gst_hlsdemux2_debug + +GST_DEBUG_CATEGORY (hlsdemux2_m3u8_debug); + +static const float update_interval_factor[] = { 1, 0.5, 1.5, 3 }; + +#define HLS_DEFAULT_FRAME_DURATION (0.04 * GST_SECOND) // 40ms +#define DEFAULT_BLOCKSIZE (8 * 1024) // 8 kbytes +#define DEFAULT_FAST_SWITCH_BUFFER_SIZE 0.7 // factor +static void _do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (gst_hlsdemux2_debug, "hlsdemux2", 0, "hlsdemux2 element"); + GST_DEBUG_CATEGORY_INIT (hlsdemux2_m3u8_debug, "hlsdemux2m3u8", 0, "m3u8 parser"); +} + +GST_BOILERPLATE_FULL (GstHLSDemux2, gst_hlsdemux2, GstElement, GST_TYPE_ELEMENT, _do_init); + +#define DEFAULT_BITRATE_SWITCH_TOLERANCE 0.4 +#define DEFAULT_FAILED_COUNT 3 +#define DEFAULT_TARGET_DURATION 10 +#define DEFAULT_NUM_FRAGMENTS_CACHE 3 +#define DEFAULT_TOTAL_CACHE_DURATION (DEFAULT_NUM_FRAGMENTS_CACHE * DEFAULT_TARGET_DURATION * GST_SECOND) + +#define PREDEFINED_VIDEO_FRAME_LOCATION "/usr/etc/sec_audio_fixed_qvga.264" +//#define PREDEFINED_VIDEO_FRAME_LOCATION "/usr/etc/blackframe_QVGA.264" +#define PREDEFINED_IMAGE_FRAME_LOCATION "/usr/etc/sec_audio_fixed_qvga.jpg" + +#define HLSDEMUX2_SOUP_FAILED_CNT 10 +#define DEFAULT_FORCE_LOWER_BITRATE FALSE +#define FORCE_LOW_BITRATE_AFTER_CNT 4 +#define HLSDEMUX2_HTTP_TIMEOUT 30 //30sec + +enum { + HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE = 0, + HLSDEMUX2_HTTP_ERROR_RECOVERABLE = 1, +}; + +typedef struct _HLSDemux2_HTTP_error HLSDemux2_HTTP_error; + +typedef gboolean (*HLS_HTTP_error_handle_function) (GstHLSDemux2 *demux, gchar *element_name); + +struct _HLSDemux2_HTTP_error { + guint HTTP_error_code; + const gchar *error_phrase; + guint error_type; + HLS_HTTP_error_handle_function handle_error; +}; + +struct _GstHLSDemux2PvtStream +{ + void *parent; + GstPad *sinkpad; + GstElement *sink; + gulong sink_eprobe; + gulong sink_bprobe; + HLSDEMUX2_STREAM_TYPE type; + GValue codec_type; + GstBuffer *id3_buffer; + GstBuffer *image_buffer; + GstBuffer *video_buffer; + gboolean got_img_buffer; + GstElement *convert_pipe; + GCond *img_load_cond; + GMutex *img_load_lock; + GCond *convert_cond; + GMutex *convert_lock; +}; + +struct _GstHLSDemux2Stream +{ + void *parent; + GstPad *pad; + GQueue *queue; + GMutex *queue_lock; + GCond *queue_full; + GCond *queue_empty; + gint64 percent; + HLSDEMUX2_STREAM_TYPE type; + gboolean is_linked; + gboolean eos; + gboolean apply_disc; + gint64 cached_duration; + GThread *dummy_data_thread; /* for pushing dummy video data */ + GstClockTime base_ts; /* base ts start */ + GstClockTime fts; /* first valid timestamp in a fragment */ + GstClockTime lts; /* last valid timestamp in a fragment */ + /* expected first timestamp in next fragment : usefule for handling fragments discontinuities*/ + GstClockTime nts; + GstClockTime prev_nts; /* stores last nts for handling .aac files */ + gboolean valid_fts_rcvd; + GstClockTime cdisc; /* current disc time */ + GstClockTime frame_duration; + guint64 total_stream_time; + GstClockTime total_disc; + GQueue *downloader_queue; + gboolean need_newsegment; + GstEvent *newsegment; +}; + +/* GStreamer virtual functions */ +static void gst_hlsdemux2_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_hlsdemux2_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean gst_hlsdemux2_sink_event (GstPad * pad, GstEvent * event); +static GstStateChangeReturn gst_hlsdemux2_change_state (GstElement * element, GstStateChange transition); +static void gst_hlsdemux2_dispose (GObject * obj); +static GstFlowReturn gst_hlsdemux2_chain (GstPad * pad, GstBuffer * buf); +static gboolean gst_hlsdemux2_handle_src_event (GstPad * pad, GstEvent * event); + +/* init & de-init functions */ +static void gst_hlsdemux2_private_init (GstHLSDemux2 *demux); +static void gst_hlsdemux2_private_deinit (GstHLSDemux2 *demux); +static void gst_hlsdemux2_playlist_downloader_init (GstHLSDemux2 *demux); +static void gst_hlsdemux2_playlist_downloader_deinit (GstHLSDemux2 *demux); +static void gst_hlsdemux2_key_downloader_init (GstHLSDemux2 *demux); +static void gst_hlsdemux2_key_downloader_deinit (GstHLSDemux2 *demux); +static void gst_hlsdemux2_fragment_downloader_init (GstHLSDemux2 *demux); +static void gst_hlsdemux2_fragment_downloader_deinit (GstHLSDemux2 *demux); + +/* helper functions */ +static void gst_hlsdemux2_stop (GstHLSDemux2 * demux); +static gboolean gst_hlsdemux2_set_location (GstHLSDemux2 * demux, const gchar * uri); +static gchar *gst_hlsdemux2_src_buf_to_utf8_playlist (GstBuffer * buf); +static void gst_hlsdemux2_new_pad_added (GstElement *element, GstPad *pad, gpointer data); +static void gst_hlsdemux2_get_cookies(GstHLSDemux2 *demux); +static void gst_hlsdemux2_get_user_agent(GstHLSDemux2 *demux); +static gboolean gst_hlsdemux2_update_playlist (GstHLSDemux2 * demux, gboolean update, gboolean *is_error); +static gboolean gst_hlsdemux2_schedule (GstHLSDemux2 * demux); +static void gst_hlsdemux2_calculate_pushed_duration (GstHLSDemux2 *demux, GstHLSDemux2Stream *stream, + GstBuffer *inbuf); + +/* stream specific functions */ +static void gst_hlsdemux2_stream_init (GstHLSDemux2 *demux, GstHLSDemux2Stream *stream, + HLSDEMUX2_STREAM_TYPE stream_type, gchar *name, GstCaps *src_caps); +static void gst_hlsdemux2_stream_deinit (GstHLSDemux2Stream *stream, gpointer data); +static void gst_hlsdemux2_stop_stream (GstHLSDemux2 *demux); +static HLSDemux2SinkBin *gst_hlsdemux2_create_stream (GstHLSDemux2 *demux, gchar *name, GstCaps *caps); + +/* task functions */ +static void gst_hlsdemux2_push_loop (GstHLSDemux2Stream *stream); +static void gst_hlsdemux2_fragment_download_loop (GstHLSDemux2 * demux); + +/* playlist download related functions */ +static void gst_hlsdemux2_on_playlist_buffer (GstElement * appsink, void* data); +static GstBusSyncReply gst_hlsdemux2_playlist_download_bus_sync_cb (GstBus * bus, GstMessage *msg, gpointer data); + +/* key file download related functions */ +static gboolean gst_hlsdemux2_key_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data); +static void gst_hlsdemux2_on_key_buffer (GstElement *appsink, void *data); + +/* fragment download related functions */ +static gboolean gst_hlsdemux2_fragment_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data); +static void gst_hlsdemux2_downloader_new_buffer (GstElement *appsink, void *user_data); +static void gst_hlsdemux2_downloader_eos (GstElement * appsink, void* user_data); + +/* probe functions */ +static gboolean gst_hlsdemux2_sink_event_handler (GstPad * pad, GstEvent * event, gpointer data); +static gboolean gst_hlsdemux2_change_playlist (GstHLSDemux2 * demux, guint max_bitrate, gboolean *is_switched); +static gboolean gst_hlsdemux2_download_monitor_thread (GstHLSDemux2 *demux); +static void gst_hlsdemux2_push_eos (GstHLSDemux2 *demux); +static void gst_hlsdemux2_apply_disc (GstHLSDemux2 * demux); +static gboolean gst_hlsdemux2_queue_buffer_handler (GstPad * pad, GstBuffer *buffer, gpointer data); +static gboolean hlsdemux2_HTTP_not_found(GstHLSDemux2 *demux, gchar *element_name); +static gboolean hlsdemux2_HTTP_time_out (GstHLSDemux2 *demux, gchar *element_name); +static gboolean hlsdemux2_HTTP_repeat_request (GstHLSDemux2 *demux, gchar *element_name); +static void gst_hlsdemux2_handle_private_pad (GstHLSDemux2 *demux, GstPad *srcpad); +static void gst_hlsdemux2_private_sink_on_new_buffer (GstElement *appsink, void *user_data); +static void gst_hlsdemux2_private_sink_on_eos (GstElement * appsink, void* user_data); +static gboolean gst_hlsdemux2_imagebuf_pipe_bus_cb (GstBus *bus, GstMessage *msg, gpointer data); +static gboolean gst_hlsdemux2_set_video_buffer (GstPad * srcpad, GstBuffer * buffer, gpointer user_data); +static gboolean gst_hlsdemux2_done_video_buffer (GstPad * srcpad, GstEvent * event, gpointer user_data); +static gboolean gst_hlsdemux2_check_fast_switch (GstHLSDemux2 *demux); + +static const HLSDemux2_HTTP_error http_errors[] = { + + /* transport errors by libsoup */ + { 1, "Cancelled", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 2, "Cannot resolve hostname", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 3, "Cannot resolve proxy hostname", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 4, "Cannot connect to destination", HLSDEMUX2_HTTP_ERROR_RECOVERABLE, hlsdemux2_HTTP_repeat_request }, + { 5, "Cannot connect to proxy", HLSDEMUX2_HTTP_ERROR_RECOVERABLE, hlsdemux2_HTTP_repeat_request }, + { 6, "SSL handshake failed", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 7, "Connection terminated unexpectedly", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 8, "Message Corrupt", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 9, "Too many redirects", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + + /* Client error */ + { 400, "Bad Request", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 401, "Unauthorized", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 402, "Payment Required", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 403, "Forbidden", HLSDEMUX2_HTTP_ERROR_RECOVERABLE, hlsdemux2_HTTP_repeat_request }, // TODO: Currently taking it as recoverable for testing + { 404, "Not Found", HLSDEMUX2_HTTP_ERROR_RECOVERABLE, hlsdemux2_HTTP_not_found }, + { 405, "Method Not Allowed", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 406, "Not Acceptable", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 407, "Proxy Authentication Required", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 408, "Request Timeout", HLSDEMUX2_HTTP_ERROR_RECOVERABLE, hlsdemux2_HTTP_time_out }, + { 409, "Conflict", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 410, "Gone", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 411, "Length Required", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 412, "Precondition Failed", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 413, "Request Entity Too Large", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 414, "Request-URI Too Long", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 415, "Unsupported Media Type", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 416, "Requested Range Not Satisfiable", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 417, "Expectation Failed", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 418, "Unprocessable Entity", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 419, "Locked", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 420, "Failed Dependency", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + + /* Server error */ + { 500, "Internal Server Error", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 501, "Not Implemented", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 502, "Bad Gateway", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 503, "Service Unavailable", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, // TODO: need to make as recoverable with timout value + { 504, "Gateway Timeout", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 505, "HTTP Version Not Supported", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 506, "Insufficient Storage", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, + { 507, "Not Extended", HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE, NULL }, +}; + + +static void +gst_hlsdemux2_base_init (gpointer g_class) +{ + GstElementClass *element_class= GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&hlsdemux2_videosrc_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&hlsdemux2_audiosrc_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&hlsdemux2_subpicture_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&hlsdemux2_sink_template)); + + gst_element_class_set_details_simple (element_class, + "HLS Demuxer2", + "Demuxer/URIList", + "HTTP Live Streaming (HLS) demuxer", + "Naveen Cherukuri"); +} + +static void +gst_hlsdemux2_class_init (GstHLSDemux2Class * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_hlsdemux2_set_property; + gobject_class->get_property = gst_hlsdemux2_get_property; + gobject_class->dispose = gst_hlsdemux2_dispose; + + g_object_class_install_property (gobject_class, PROP_BITRATE_SWITCH_TOLERANCE, + g_param_spec_float ("bitrate-switch-tolerance", + "Bitrate switch tolerance", + "Tolerance with respect of the fragment duration to switch to " + "a different bitrate if the client is too slow/fast.", + 0, 1, DEFAULT_BITRATE_SWITCH_TOLERANCE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_FORCE_LOWER_BITRATE, + g_param_spec_boolean ("force-low-bitrate", + "forcing lower variant", "forcing lower variant after every few fragments [for debugging purpose only]", + DEFAULT_FORCE_LOWER_BITRATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BLOCKSIZE, + g_param_spec_ulong ("blocksize", "Block size", + "Size in bytes to read per buffer (-1 = default)", 0, G_MAXULONG, + DEFAULT_BLOCKSIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_hlsdemux2_change_state); +} + +static void +gst_hlsdemux2_init (GstHLSDemux2 * demux, GstHLSDemux2Class * klass) +{ + /* sink pad */ + demux->sinkpad = gst_pad_new_from_static_template (&hlsdemux2_sink_template, "sink"); + gst_pad_set_chain_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_hlsdemux2_chain)); + gst_pad_set_event_function (demux->sinkpad, GST_DEBUG_FUNCPTR (gst_hlsdemux2_sink_event)); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + gst_hlsdemux2_private_init (demux); + + /* fragment downloader init */ + gst_hlsdemux2_fragment_downloader_init (demux); + + /* playlist downloader init */ + gst_hlsdemux2_playlist_downloader_init (demux); + + /* key downloader init */ + gst_hlsdemux2_key_downloader_init (demux); + +} + +static void +gst_hlsdemux2_dispose (GObject * obj) +{ + GstHLSDemux2 *demux = GST_HLSDEMUX2 (obj); + + GST_INFO_OBJECT (demux,"entering"); + + gst_hlsdemux2_stop (demux); + + gst_hlsdemux2_private_deinit (demux); + + gst_hlsdemux2_fragment_downloader_deinit (demux); + + gst_hlsdemux2_playlist_downloader_deinit (demux); + + gst_hlsdemux2_key_downloader_deinit (demux); + + GST_INFO_OBJECT (demux,"leaving"); + + G_OBJECT_CLASS (parent_class)->dispose (obj); +} + +static void +gst_hlsdemux2_private_init (GstHLSDemux2 *demux) +{ + demux->is_live = TRUE; + demux->bitrate_switch_tol = DEFAULT_BITRATE_SWITCH_TOLERANCE; + demux->percent = 100; + demux->active_stream_cnt = 0; + demux->download_task = NULL; + demux->pl_update_lock = g_mutex_new (); + demux->pl_update_cond = g_cond_new (); + demux->cancelled = FALSE; + demux->total_cache_duration = DEFAULT_TOTAL_CACHE_DURATION; + demux->target_duration = DEFAULT_TARGET_DURATION; + demux->keyCookie = NULL; + demux->playlistCookie = NULL; + demux->fragCookie = NULL; + demux->lastCookie = NULL; + demux->keyDomain = NULL; + demux->playlistDomain = NULL; + demux->fragDomain = NULL; + demux->lastDomain = NULL; + demux->user_agent = NULL; + demux->buffering_lock = g_mutex_new (); + demux->soup_request_fail_cnt = HLSDEMUX2_SOUP_FAILED_CNT; + demux->force_lower_bitrate = DEFAULT_FORCE_LOWER_BITRATE; + demux->cfrag_dur = 0; + demux->error_posted = FALSE; + demux->playlist_uri = NULL; + demux->frag_uri = NULL; + demux->key_uri = NULL; + demux->blocksize = DEFAULT_BLOCKSIZE; + demux->flushing = FALSE; + demux->ns_start = 0; + demux->cur_audio_fts = GST_CLOCK_TIME_NONE; + demux->is_buffering = TRUE; + demux->buffering_posting_thread = NULL; + demux->post_msg_lock = g_mutex_new (); + demux->post_msg_start = g_cond_new (); + demux->post_msg_exit = g_cond_new (); + demux->stream_config = HLSDEMUX2_SINGLE_VARIANT; + demux->has_image_buffer = FALSE; + demux->prev_image_buffer = NULL; + demux->prev_video_buffer = NULL; + demux->private_stream = NULL; +} + +static void +gst_hlsdemux2_private_deinit (GstHLSDemux2 *demux) +{ + demux->end_of_playlist = FALSE; + + if (demux->pl_update_lock) { + g_mutex_free (demux->pl_update_lock); + demux->pl_update_lock = NULL; + } + + if (demux->pl_update_cond) { + g_cond_free (demux->pl_update_cond); + demux->pl_update_cond = NULL; + } + + if (demux->buffering_lock) { + g_mutex_free (demux->buffering_lock); + demux->buffering_lock = NULL; + } + + if (demux->user_agent) { + g_free (demux->user_agent); + demux->user_agent = NULL; + } + + if (demux->playlist) { + gst_buffer_unref (demux->playlist); + demux->playlist = NULL; + } + + if (demux->fragCookie) { + g_strfreev (demux->fragCookie); + demux->fragCookie = NULL; + } + + if (demux->keyCookie) { + g_strfreev (demux->keyCookie); + demux->keyCookie = NULL; + } + + if (demux->lastCookie) { + g_strfreev (demux->lastCookie); + demux->lastCookie = NULL; + } + + if (demux->playlistCookie) { + g_strfreev (demux->playlistCookie); + demux->playlistCookie = NULL; + } + + if (demux->fragDomain) { + g_free (demux->fragDomain); + demux->fragDomain = NULL; + } + + if (demux->keyDomain) { + g_free (demux->keyDomain); + demux->keyDomain = NULL; + } + + if (demux->lastDomain) { + g_free (demux->lastDomain); + demux->lastDomain = NULL; + } + + if (demux->playlistDomain) { + g_free (demux->playlistDomain); + demux->playlistDomain = NULL; + } + + if (demux->playlist_uri) { + g_free (demux->playlist_uri); + demux->playlist_uri = NULL; + } + + if (demux->key_uri) { + g_free (demux->key_uri); + demux->key_uri = NULL; + } + + if (demux->frag_uri) { + g_free (demux->frag_uri); + demux->frag_uri = NULL; + } + + if (demux->prev_image_buffer) { + gst_buffer_unref (demux->prev_image_buffer); + demux->prev_image_buffer = NULL; + } + + if (demux->prev_video_buffer) { + gst_buffer_unref (demux->prev_video_buffer); + demux->prev_video_buffer = NULL; + } + + if (demux->private_stream) { + g_free (demux->private_stream); + demux->private_stream = NULL; + } + + if (demux->client) { + gst_m3u8_client_free (demux->client); + demux->client = NULL; + } +} + +static void +gst_hlsdemux2_fragment_downloader_init (GstHLSDemux2 *demux) +{ + demux->fdownloader = g_new0 (HLSDemux2FragDownloader, 1); + demux->fdownloader->pipe = NULL; + demux->fdownloader->urisrc = NULL; + demux->fdownloader->queue = NULL; + demux->fdownloader->typefind = NULL; + demux->fdownloader->demuxer = NULL; + demux->fdownloader->sinkbins = NULL; + demux->fdownloader->lock = g_mutex_new (); + demux->fdownloader->cond = g_cond_new (); + demux->fdownloader->is_encrypted = FALSE; + demux->fdownloader->content_size = 0; + demux->fdownloader->get_next_frag = FALSE; + demux->fdownloader->applied_fast_switch = FALSE; + demux->fdownloader->remaining_data = NULL; + demux->fdownloader->remaining_size = 0; + demux->fdownloader->first_buffer = TRUE; + demux->fdownloader->cur_stream_cnt = 0; + demux->fdownloader->force_timestamps = FALSE; + demux->fdownloader->error_rcvd = FALSE; + demux->fdownloader->find_mediaseq = FALSE; + demux->fdownloader->seeked_pos = 0; + demux->fdownloader->cur_running_dur = 0; + demux->fdownloader->download_rate = -1; + demux->fdownloader->download_start_ts = 0; + demux->fdownloader->download_stop_ts = 0; + demux->fdownloader->src_downloaded_size = 0; + demux->fdownloader->queue_downloaded_size = 0; + demux->fdownloader->ndownloaded = 0; + demux->fdownloader->chunk_downloaded_size = 0; + demux->fdownloader->chunk_start_ts = 0; + demux->fdownloader->avg_chunk_drates = g_array_new(FALSE, TRUE, sizeof(guint64)); + demux->fdownloader->avg_frag_drates = g_array_new(FALSE, TRUE, sizeof(guint64)); +} + +static void +gst_hlsdemux2_fragment_downloader_deinit (GstHLSDemux2 *demux) +{ + if (!demux->fdownloader) + return; + + /* free fragment downloader structure */ + if (demux->fdownloader->lock) { + g_mutex_free (demux->fdownloader->lock); + demux->fdownloader->lock = NULL; + } + + if (demux->fdownloader->cond) { + g_cond_free (demux->fdownloader->cond); + demux->fdownloader->cond = NULL; + } + + if (demux->fdownloader->remaining_data) { + g_free (demux->fdownloader->remaining_data); + demux->fdownloader->remaining_data = NULL; + } + + if (demux->fdownloader->avg_chunk_drates) { + g_array_free(demux->fdownloader->avg_chunk_drates, TRUE); + demux->fdownloader->avg_chunk_drates = NULL; + } + + if (demux->fdownloader->avg_frag_drates) { + g_array_free(demux->fdownloader->avg_frag_drates, TRUE); + demux->fdownloader->avg_frag_drates = NULL; + } + + g_free (demux->fdownloader); + demux->fdownloader = NULL; +} + +static void +gst_hlsdemux2_playlist_downloader_init (GstHLSDemux2 *demux) +{ + demux->pldownloader = g_new0 (HLSDemux2PLDownloader, 1); + demux->pldownloader->pipe = NULL; + demux->pldownloader->bus = NULL; + demux->pldownloader->urisrc = NULL; + demux->pldownloader->sink = NULL; + demux->pldownloader->lock = g_mutex_new (); + demux->pldownloader->cond = g_cond_new (); + demux->pldownloader->playlist = NULL; + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; +} + +static void +gst_hlsdemux2_playlist_downloader_deinit (GstHLSDemux2 *demux) +{ + if (!demux->pldownloader) + return; + + /* free playlist downloader structure */ + if (demux->pldownloader->lock) { + g_mutex_free (demux->pldownloader->lock); + demux->pldownloader->lock = NULL; + } + + if (demux->pldownloader->cond) { + g_cond_free (demux->pldownloader->cond); + demux->pldownloader->cond = NULL; + } + + if (demux->pldownloader->playlist){ + gst_buffer_unref (demux->pldownloader->playlist); + demux->pldownloader->playlist = NULL; + } + + g_free (demux->pldownloader); + demux->pldownloader = NULL; +} + +static void +gst_hlsdemux2_key_downloader_init (GstHLSDemux2 *demux) +{ + demux->kdownloader = g_new0 (HLSDemux2KeyDownloader, 1); + demux->kdownloader->pipe = NULL; + demux->kdownloader->bus = NULL; + demux->kdownloader->urisrc = NULL; + demux->kdownloader->sink = NULL; + demux->kdownloader->lock = g_mutex_new (); + demux->kdownloader->cond = g_cond_new (); + demux->kdownloader->key = NULL; + demux->kdownloader->prev_key_uri = NULL; +} + +static void +gst_hlsdemux2_key_downloader_deinit (GstHLSDemux2 *demux) +{ + /* free key file downloader structure */ + if (!demux->kdownloader) + return; + + if (demux->kdownloader->lock) { + g_mutex_free (demux->kdownloader->lock); + demux->kdownloader->lock = NULL; + } + + if (demux->kdownloader->cond) { + g_cond_free (demux->kdownloader->cond); + demux->kdownloader->cond = NULL; + } + + if (demux->kdownloader->key){ + gst_buffer_unref (demux->kdownloader->key); + demux->kdownloader->key = NULL; + } + + if (demux->kdownloader->prev_key_uri) { + g_free (demux->kdownloader->prev_key_uri); + demux->kdownloader->prev_key_uri = NULL; + } + + g_free (demux->kdownloader); + demux->kdownloader = NULL; +} + +static void +gst_hlsdemux2_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstHLSDemux2 *demux = GST_HLSDEMUX2 (object); + + switch (prop_id) { + case PROP_BITRATE_SWITCH_TOLERANCE: + demux->bitrate_switch_tol = g_value_get_float (value); + break; + case PROP_FORCE_LOWER_BITRATE: + demux->force_lower_bitrate = g_value_get_boolean (value); + break; + case PROP_BLOCKSIZE: + demux->blocksize = g_value_get_ulong (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_hlsdemux2_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstHLSDemux2 *demux = GST_HLSDEMUX2 (object); + + switch (prop_id) { + case PROP_BITRATE_SWITCH_TOLERANCE: + g_value_set_float (value, demux->bitrate_switch_tol); + break; + case PROP_FORCE_LOWER_BITRATE: + g_value_set_boolean (value, demux->force_lower_bitrate); + break; + case PROP_BLOCKSIZE: + g_value_set_ulong (value, demux->blocksize); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_hlsdemux2_sink_event (GstPad * pad, GstEvent * event) +{ + GstHLSDemux2 *demux = GST_HLSDEMUX2 (gst_pad_get_parent (pad)); + GstQuery *query = NULL; + gboolean ret = FALSE; + gchar *uri = NULL; + + GST_LOG_OBJECT (demux, "Received event '%s' on sink pad...", GST_EVENT_TYPE_NAME (event)); + + switch (event->type) { + case GST_EVENT_EOS: { + gchar *playlist = NULL; + GError *err = NULL; + + if (demux->playlist == NULL) { + GST_ERROR_OBJECT (demux, "Received EOS without a playlist."); + goto error; + } + + GST_INFO_OBJECT (demux, "Got EOS on the sink pad: playlist file fetched"); + + query = gst_query_new_uri (); + + /* query the location from upstream element for absolute path */ + ret = gst_pad_peer_query (demux->sinkpad, query); + if (ret) { + gst_query_parse_uri (query, &uri); + gst_hlsdemux2_set_location (demux, uri); + g_free (uri); + } else { + GST_ERROR_OBJECT (demux, "failed to query URI from upstream"); + goto error; + } + gst_query_unref (query); + query = NULL; + + /* get cookies from upstream httpsrc */ + gst_hlsdemux2_get_cookies (demux); + + /* get user-agent from upstream httpsrc */ + gst_hlsdemux2_get_user_agent (demux); + + playlist = gst_hlsdemux2_src_buf_to_utf8_playlist (demux->playlist); + demux->playlist = NULL; + + if (playlist == NULL) { + GST_ERROR_OBJECT (demux, "Error validating first playlist."); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); + goto error; + } + + if (!gst_m3u8_client_update (demux->client, playlist)) { + /* In most cases, this will happen if we set a wrong url in the + * source element and we have received the 404 HTML response instead of + * the playlist */ + GST_ERROR_OBJECT (demux, "failed to parse playlist..."); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); + goto error; + } + + /* Loop to download the fragments & updates playlist */ + g_static_rec_mutex_init (&demux->download_lock); + demux->download_task = gst_task_create ((GstTaskFunction) gst_hlsdemux2_fragment_download_loop, demux); + if (NULL == demux->download_task) { + GST_ERROR_OBJECT (demux, "failed to create download task..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to fragment download task"), (NULL)); + goto error; + } + gst_task_set_lock (demux->download_task, &demux->download_lock); + gst_task_start (demux->download_task); + + /* create thread to send dummy data */ + demux->buffering_posting_thread = g_thread_create ( + (GThreadFunc) gst_hlsdemux2_download_monitor_thread, demux, TRUE, &err); + + /* Swallow EOS, we'll push our own */ + gst_event_unref (event); + gst_object_unref (demux); + return TRUE; + } + case GST_EVENT_NEWSEGMENT: + /* Swallow newsegments, we'll push our own */ + gst_event_unref (event); + gst_object_unref (demux); + return TRUE; + default: + gst_object_unref (demux); + break; + } + + return gst_pad_event_default (pad, event); + +error: + gst_hlsdemux2_stop (demux); + gst_event_unref (event); + gst_object_unref (demux); + + if (query) + gst_query_unref (query); + + GST_LOG_OBJECT (demux,"Returning from sink event..."); + return FALSE; +} + +static GstStateChangeReturn + gst_hlsdemux2_change_state (GstElement * element, GstStateChange transition) + { + GstStateChangeReturn ret; + GstHLSDemux2 *demux = GST_HLSDEMUX2 (element); + gboolean bret = FALSE; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_INFO_OBJECT (demux,"PAUSED->PLAYING"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_INFO_OBJECT (demux,"PAUSED->READY before parent"); + gst_hlsdemux2_stop (demux); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_INFO_OBJECT (demux,"PLAYING->PAUSED"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_INFO_OBJECT (demux,"PAUSED->READY after parent"); + if (demux->download_task) { + bret = gst_task_join (demux->download_task); + GST_DEBUG_OBJECT (demux,"Joining download task : %d", bret); + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + GST_INFO_OBJECT (demux,"READY->NULL"); + break; + default: + break; + } + return ret; + } + + + +static GstFlowReturn +gst_hlsdemux2_chain (GstPad * pad, GstBuffer * buf) +{ + GstHLSDemux2 *demux = GST_HLSDEMUX2 (gst_pad_get_parent (pad)); + + if (demux->playlist == NULL) + demux->playlist = buf; + else + demux->playlist = gst_buffer_join (demux->playlist, buf); + gst_object_unref (demux); + + return GST_FLOW_OK; +} + +static gboolean +gst_hlsdemux2_handle_src_event (GstPad * pad, GstEvent * event) +{ + GstHLSDemux2 *hlsdemux2 = NULL; + GstHLSDemux2Stream *stream = NULL; + + stream = (GstHLSDemux2Stream *) (gst_pad_get_element_private (pad)); + hlsdemux2 = stream->parent; + + switch (event->type) { + case GST_EVENT_SEEK: { + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + gint idx = 0; + + if (gst_m3u8_client_is_live (hlsdemux2->client)) { + GST_WARNING_OBJECT (stream->pad, "SEEK is NOT Supported in LIVE"); + return FALSE; + } + + GST_INFO_OBJECT (stream->pad, "received SEEK event.."); + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, + &stop_type, &stop); + + if (format != GST_FORMAT_TIME) { + GST_WARNING_OBJECT (stream->pad, "received seek with unsupported format"); + return FALSE; + } + + // TODO: Validate requested SEEK time is within the duration or not + + hlsdemux2->end_of_playlist = FALSE; // resent end of playlist + + GST_DEBUG_OBJECT (stream->pad, "SEEK event with rate: %f start: %" GST_TIME_FORMAT + " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop)); + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_WARNING_OBJECT (stream->pad, "non-flush seek is not supported yet"); + return FALSE; + } + + hlsdemux2->flushing = TRUE; + + /* signal queue full condition to come out */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream) { + cur_stream->eos = FALSE; // resent stream EOS + if (cur_stream->queue) { + GST_INFO_OBJECT (cur_stream->pad, "signalling stream queue"); + g_cond_signal (cur_stream->queue_full); /* to signal downloader eos blocking */ + g_cond_signal (cur_stream->queue_empty); /* incase push_loop blocked on condition */ + } + } + } + + /* send FLUSH_START event to all source pads */ + if (flags & GST_SEEK_FLAG_FLUSH) { + + GST_INFO_OBJECT (hlsdemux2, "sending flush start on all the stream pads"); + + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream && GST_PAD_TASK(cur_stream->pad)) { + gboolean bret = FALSE; + bret = gst_pad_push_event (cur_stream->pad, gst_event_new_flush_start ()); + GST_DEBUG_OBJECT (cur_stream->pad, "flush_start returned = %d", bret); + } + } + } + + /* wait till all stream pad's task paused */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream && GST_PAD_TASK(cur_stream->pad)) { + GST_INFO_OBJECT (cur_stream->pad, "trying acquire stream lock"); + GST_PAD_STREAM_LOCK (cur_stream->pad); + GST_INFO_OBJECT (cur_stream->pad, "acquired stream lock"); + GST_PAD_STREAM_UNLOCK (cur_stream->pad); + } + } + + /* pause fragment download loop */ + g_mutex_lock (hlsdemux2->kdownloader->lock); + GST_INFO_OBJECT (stream->pad, "Signalling key downloader condition"); + g_cond_signal (hlsdemux2->kdownloader->cond); + g_mutex_unlock (hlsdemux2->kdownloader->lock); + + g_mutex_lock (hlsdemux2->fdownloader->lock); + GST_INFO_OBJECT (stream->pad, "Signalling fragment downloader condition"); + g_cond_signal (hlsdemux2->fdownloader->cond); + g_mutex_unlock (hlsdemux2->fdownloader->lock); + + g_mutex_lock (hlsdemux2->pldownloader->lock); + GST_INFO_OBJECT (stream->pad, "Signalling playlist downloader condition"); + g_cond_signal (hlsdemux2->pldownloader->cond); + g_mutex_unlock (hlsdemux2->pldownloader->lock); + + GST_INFO_OBJECT (stream->pad, "waiting for download task to pause..."); + g_static_rec_mutex_lock (&hlsdemux2->download_lock); + g_static_rec_mutex_unlock (&hlsdemux2->download_lock); + + GST_INFO_OBJECT (hlsdemux2, "Download task paused"); + + /* clear internal stream queues */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream && cur_stream->queue) { + while (!g_queue_is_empty (cur_stream->queue)) { + gpointer data = NULL; + data = g_queue_pop_head (cur_stream->queue); + gst_object_unref (data); + } + g_queue_clear (cur_stream->queue); + cur_stream->cached_duration = 0; + stream->percent = 0; + + while (!g_queue_is_empty (cur_stream->downloader_queue)) { + gpointer data = NULL; + data = g_queue_pop_head (cur_stream->downloader_queue); + gst_object_unref (data); + } + g_queue_clear (cur_stream->downloader_queue); + } + } + +#if 0 /* useful when we want to switch to lowest variant on seek for faster buffering */ + if (gst_m3u8_client_has_variant_playlist (hlsdemux2->client)) { + /* intentionally sending zero download rate, to move to least possible variant */ + gst_hlsdemux2_change_playlist (hlsdemux2, 0, NULL); + } +#endif + + /* send FLUSH_STOP event to all source pads */ + if (flags & GST_SEEK_FLAG_FLUSH) { + + GST_INFO_OBJECT (hlsdemux2, "sending flush stop"); + + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream && GST_PAD_TASK(cur_stream->pad)) { + gboolean bret = FALSE; + bret = gst_pad_push_event (cur_stream->pad, gst_event_new_flush_stop ()); + GST_DEBUG_OBJECT (cur_stream->pad, "flush_stop returned = %d", bret); + } + } + } + + hlsdemux2->flushing = FALSE; + hlsdemux2->fdownloader->find_mediaseq = TRUE; + hlsdemux2->fdownloader->seeked_pos = start; + + /* start all streams loop tasks & fragment download task */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = hlsdemux2->streams[idx]; + + if (cur_stream && GST_PAD_TASK(cur_stream->pad)) { + GST_INFO_OBJECT (cur_stream->pad, "Starting push task"); + cur_stream->need_newsegment = TRUE; + cur_stream->nts = GST_CLOCK_TIME_NONE; /*useful for audio-only seeking */ + cur_stream->cdisc = 0; + cur_stream->fts = GST_CLOCK_TIME_NONE; + cur_stream->valid_fts_rcvd = FALSE; + cur_stream->total_stream_time = 0; + + if (!gst_pad_start_task (cur_stream->pad, (GstTaskFunction) gst_hlsdemux2_push_loop, cur_stream)) { + GST_ERROR_OBJECT (hlsdemux2, "failed to start stream task..."); + GST_ELEMENT_ERROR (hlsdemux2, RESOURCE, FAILED, ("failed to create stream push loop"), (NULL)); + goto error; + } + } + } + + gst_hlsdemux2_apply_disc (hlsdemux2); + + GST_INFO_OBJECT (hlsdemux2, "Starting Download task.."); + + gst_task_start (hlsdemux2->download_task); + + GST_INFO_OBJECT (hlsdemux2, "Successfully configured SEEK..."); + gst_event_unref (event); + return TRUE; + } + default: + break; + } + + return gst_pad_event_default (pad, event); + +error: + GST_ERROR_OBJECT (hlsdemux2, "seeking failed..."); + gst_event_unref (event); + return FALSE; +} + +static gboolean +gst_hlsdemux2_src_query (GstPad *pad, GstQuery * query) +{ + GstHLSDemux2 *hlsdemux2 = NULL; + gboolean ret = FALSE; + GstHLSDemux2Stream *stream = NULL; + + if (query == NULL) + return FALSE; + + stream = (GstHLSDemux2Stream *) (gst_pad_get_element_private (pad)); + hlsdemux2 = stream->parent; + + switch (query->type) { + case GST_QUERY_DURATION:{ + GstClockTime duration = -1; + GstFormat fmt; + + gst_query_parse_duration (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME) { + duration = gst_m3u8_client_get_duration (hlsdemux2->client); + if (GST_CLOCK_TIME_IS_VALID (duration) && duration > 0) { + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + ret = TRUE; + } + } + GST_LOG_OBJECT (hlsdemux2, "GST_QUERY_DURATION returns %s with duration %" + GST_TIME_FORMAT, ret ? "TRUE" : "FALSE", GST_TIME_ARGS (duration)); + break; + } + case GST_QUERY_URI: + if (hlsdemux2->client) { + /* FIXME: Do we answer with the variant playlist, with the current + * playlist or the the uri of the least downlowaded fragment? */ + gst_query_set_uri (query, gst_m3u8_client_get_uri (hlsdemux2->client)); + ret = TRUE; + } + break; + case GST_QUERY_SEEKING:{ + GstFormat fmt; + gint64 stop = -1; + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + GST_INFO_OBJECT (hlsdemux2, "Received GST_QUERY_SEEKING with format %d", + fmt); + if (fmt == GST_FORMAT_TIME) { + GstClockTime duration; + + duration = gst_m3u8_client_get_duration (hlsdemux2->client); + if (GST_CLOCK_TIME_IS_VALID (duration) && duration > 0) + stop = duration; + + gst_query_set_seeking (query, fmt, + !gst_m3u8_client_is_live (hlsdemux2->client), 0, stop); + ret = TRUE; + GST_INFO_OBJECT (hlsdemux2, "GST_QUERY_SEEKING returning with stop : %" + GST_TIME_FORMAT, GST_TIME_ARGS (stop)); + } + break; + } + default: + /* Don't fordward queries upstream because of the special nature of this + * "demuxer", which relies on the upstream element only to be fed with the + * first playlist */ + break; + } + + return ret; +} + +static gchar * +gst_hlsdemux2_uri_get_domain ( GstHLSDemux2 *demux, gchar * uri) +{ + gchar *pointer = uri; + gchar *domain = NULL; + guint flag = 0; + guint counter=0; + + domain = g_malloc0(strlen (uri)); + if (!domain) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + return NULL; + } + + while(*pointer != '\0') { + switch(*pointer) { + case '.': + flag = 1; + break; + case '/': + if(flag==1) + flag = 2; + break; + } + if(flag==1) { + domain[counter]=*pointer; + counter++; + } else if(flag == 2) { + domain[counter]='\0'; + break; + } + pointer++; + } + + GST_DEBUG_OBJECT (demux, "uri = %s and domain = %s", uri, domain); + return domain; +} + +static void +gst_hlsdemux2_get_cookies(GstHLSDemux2 *demux) +{ + GstQuery *cquery; + GstStructure *structure; + gboolean bret = FALSE; + + structure = gst_structure_new ("HTTPCookies", + "cookies", G_TYPE_STRING, NULL, NULL); + + cquery = gst_query_new_application (GST_QUERY_CUSTOM, structure); + + bret = gst_pad_peer_query (demux->sinkpad, cquery); + if (bret) { + const GstStructure *s; + const GValue *value; + + s = gst_query_get_structure (cquery); + value = gst_structure_get_value (s, "cookies"); + demux->playlistCookie = g_strdupv (g_value_get_boxed (value)); + + GST_INFO_OBJECT (demux, "Received playlist cookies from upstream element : %s", demux->playlistCookie ? *(demux->playlistCookie) : NULL); + if(demux->playlistCookie){ + demux->lastCookie = g_strdupv(demux->playlistCookie); + } + } else { + GST_WARNING_OBJECT (demux, "Failed to get cookies"); + } + + gst_query_unref (cquery); +} + +static void +gst_hlsdemux2_get_user_agent(GstHLSDemux2 *demux) +{ + GstQuery *cquery; + GstStructure *structure; + gboolean bret = FALSE; + + structure = gst_structure_new ("HTTPUserAgent", + "user-agent", G_TYPE_STRING, NULL, NULL); + + cquery = gst_query_new_application (GST_QUERY_CUSTOM, structure); + + bret = gst_pad_peer_query (demux->sinkpad, cquery); + if (bret) { + const GstStructure *s; + const GValue *value; + + GST_INFO_OBJECT (demux, "Received user-agent from upstream element..."); + + s = gst_query_get_structure (cquery); + value = gst_structure_get_value (s, "user-agent"); + demux->user_agent = g_strdup (g_value_get_string (value)); + + GST_INFO_OBJECT (demux,"User agent received from query : %s", demux->user_agent); + } else { + GST_WARNING_OBJECT (demux, "Failed to get user-agent"); + } + + gst_query_unref (cquery); +} + +static gboolean +gst_hlsdemux2_create_playlist_download (GstHLSDemux2 *demux, const gchar *playlist_uri) +{ + if (!gst_uri_is_valid (playlist_uri)) { + GST_ERROR_OBJECT (demux, "invalid uri : %s...", playlist_uri == NULL ? "NULL" : playlist_uri); + return FALSE; + } + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (demux,"returning from download playlist due to cancel or flushing.."); + return FALSE; + } + + /* create playlist downloader pipeline */ + demux->pldownloader->pipe = gst_pipeline_new ("playlist-downloader"); + if (!demux->pldownloader->pipe) { + GST_ERROR_OBJECT (demux, "failed to create pipeline"); + return FALSE; + } + + demux->pldownloader->bus = gst_pipeline_get_bus (GST_PIPELINE (demux->pldownloader->pipe)); + gst_bus_set_sync_handler(demux->pldownloader->bus, gst_hlsdemux2_playlist_download_bus_sync_cb, demux); + + gst_object_unref (demux->pldownloader->bus); + + GST_INFO_OBJECT (demux, "Creating source element for the URI : %s", playlist_uri); + + /* creating source element */ + demux->pldownloader->urisrc = gst_element_make_from_uri (GST_URI_SRC, playlist_uri, "playlisturisrc"); + if (!demux->pldownloader->urisrc) { + GST_ERROR_OBJECT (demux, "failed to create urisrc"); + return FALSE; + } + g_object_set (G_OBJECT (demux->pldownloader->urisrc), "timeout", HLSDEMUX2_HTTP_TIMEOUT, NULL); + g_object_set (G_OBJECT (demux->pldownloader->urisrc), "user-agent", demux->user_agent, NULL); + g_object_set (G_OBJECT (demux->pldownloader->urisrc), "ahs-streaming", TRUE, NULL); + + demux->playlistDomain = gst_hlsdemux2_uri_get_domain(demux, playlist_uri); + if(!g_strcmp0(demux->playlistDomain, demux->lastDomain) && demux->lastCookie) { + g_strfreev (demux->playlistCookie); + demux->playlistCookie = g_strdupv (demux->lastCookie); + } + + if (demux->playlistCookie) { + GST_DEBUG_OBJECT (demux, "Setting cookies before PLAYLIST download, before PLAYING: %s", *(demux->playlistCookie)); + /* setting cookies */ + g_object_set (demux->pldownloader->urisrc, "cookies", demux->playlistCookie, NULL); + } + + /* create sink element */ + demux->pldownloader->sink = gst_element_factory_make ("appsink", "playlistsink"); + if (!demux->pldownloader->sink) { + GST_ERROR_OBJECT (demux, "failed to create playlist sink element"); + return FALSE; + } + g_object_set (G_OBJECT (demux->pldownloader->sink), "sync", FALSE, "emit-signals", TRUE, NULL); + g_signal_connect (demux->pldownloader->sink, "new-buffer", G_CALLBACK (gst_hlsdemux2_on_playlist_buffer), demux); + + gst_bin_add_many (GST_BIN (demux->pldownloader->pipe), demux->pldownloader->urisrc, demux->pldownloader->sink, NULL); + + if (!gst_element_link_many (demux->pldownloader->urisrc, demux->pldownloader->sink, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link src & demux elements..."); + return FALSE; + } + + return TRUE; +} + +static void +gst_hlsdemux2_destroy_playlist_download (GstHLSDemux2 * demux) +{ + if (demux->pldownloader->pipe) { + GST_DEBUG_OBJECT (demux, "Shutting down playlist download pipeline"); + gst_element_set_state (demux->pldownloader->pipe, GST_STATE_NULL); + gst_element_get_state (demux->pldownloader->pipe, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref (demux->pldownloader->pipe); + demux->pldownloader->pipe = NULL; + } +} + +static gboolean +gst_hlsdemux2_download_playlist (GstHLSDemux2 *demux, const gchar * uri) +{ + GstStateChangeReturn ret; + + if (demux->playlist_uri) { + g_free (demux->playlist_uri); + } + demux->playlist_uri = g_strdup (uri); + + demux->pldownloader->download_start_ts = gst_util_get_timestamp(); + + if (!gst_hlsdemux2_create_playlist_download (demux, uri)) { + GST_ERROR_OBJECT (demux, "failed to create playlist download pipeline"); + return FALSE; + } + + ret = gst_element_set_state (demux->pldownloader->pipe, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (demux, "set_state failed..."); + return FALSE; + } + + /* schedule the next update of playlist */ + if (demux->is_live) + gst_hlsdemux2_schedule (demux); + + /* wait until: + * - the download succeed (EOS) + * - the download failed (Error message on the fetcher bus) + * - the download was canceled + */ + GST_LOG_OBJECT (demux, "Waiting to fetch the URI"); + g_cond_wait (demux->pldownloader->cond, demux->pldownloader->lock); + GST_LOG_OBJECT (demux, "Recived signal to shutdown..."); + + if (demux->playlistCookie) + g_strfreev (demux->playlistCookie); + + g_object_get (demux->pldownloader->urisrc, "cookies", &demux->playlistCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after PLAYLIST download : %s", demux->playlistCookie ? *(demux->playlistCookie) : NULL); + + if(demux->playlistCookie){ + g_strfreev(demux->lastCookie); + g_free(demux->lastDomain); + demux->lastCookie = g_strdupv(demux->playlistCookie); + demux->lastDomain = g_strdup(demux->playlistDomain); + } + + gst_hlsdemux2_destroy_playlist_download (demux); + + if (demux->cancelled || demux->flushing) + return FALSE; + + return TRUE; +} + + +static gboolean +gst_hlsdemux2_update_playlist (GstHLSDemux2 * demux, gboolean update, gboolean *is_error) +{ + gchar *playlist = NULL; + gboolean updated = FALSE; + const gchar *playlist_uri = gst_m3u8_client_get_current_uri (demux->client); + + *is_error = FALSE; + + if (!gst_m3u8_client_is_playlist_download_needed (demux->client)) { + GST_INFO_OBJECT (demux, "Playlist download is not needed..."); + return TRUE; + } + + g_mutex_lock (demux->pldownloader->lock); + +try_again: + + if (!gst_hlsdemux2_download_playlist (demux, playlist_uri)) { + GST_ERROR_OBJECT (demux, "failed to create playlist download"); + goto error; + } + + if ((demux->pldownloader->playlist == NULL) || (GST_BUFFER_DATA(demux->pldownloader->playlist) == NULL)) { + // TODO: don't exact reason, why eos is coming from soup without data... + GST_ERROR_OBJECT (demux, "Received playlist eos, without playlist data -> TRY AGAIN"); + goto try_again; + } + + /* reset the count */ + demux->soup_request_fail_cnt = HLSDEMUX2_SOUP_FAILED_CNT; + + playlist = gst_hlsdemux2_src_buf_to_utf8_playlist (demux->pldownloader->playlist); + if (!playlist) { + GST_ERROR_OBJECT(demux, "failed to create playlist"); + goto error; + } + demux->pldownloader->playlist = NULL; + + updated = gst_m3u8_client_update (demux->client, playlist); + g_mutex_unlock (demux->pldownloader->lock); + + return updated; + +error: + if (demux->flushing) + *is_error = TRUE; + g_mutex_unlock (demux->pldownloader->lock); + return FALSE; +} + +static gboolean +gst_hlsdemux2_create_key_download (GstHLSDemux2 *demux, const gchar *key_uri) +{ + if (!gst_uri_is_valid (key_uri)) { + GST_ERROR_OBJECT (demux, "invalid uri : %s...", key_uri == NULL ? "NULL" : key_uri); + return FALSE; + } + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (demux,"returning from download key due to cancel or flushing.."); + return FALSE; + } + + demux->kdownloader->pipe = gst_pipeline_new ("keydownloader"); + if (!demux->kdownloader->pipe) { + GST_ERROR_OBJECT (demux, "failed to create pipeline"); + return FALSE; + } + + demux->kdownloader->bus = gst_pipeline_get_bus (GST_PIPELINE (demux->kdownloader->pipe)); + gst_bus_add_watch (demux->kdownloader->bus, (GstBusFunc)gst_hlsdemux2_key_download_bus_cb, demux); + gst_object_unref (demux->kdownloader->bus); + + GST_INFO_OBJECT (demux, "Creating source element for the URI : %s", key_uri); + + demux->kdownloader->urisrc = gst_element_make_from_uri (GST_URI_SRC, key_uri, "keyurisrc"); + if (!demux->kdownloader->urisrc) { + GST_ERROR_OBJECT (demux, "failed to create urisrc"); + return FALSE; + } + + g_object_set (G_OBJECT (demux->kdownloader->urisrc), "timeout", HLSDEMUX2_HTTP_TIMEOUT, NULL); + g_object_set (G_OBJECT (demux->kdownloader->urisrc), "user-agent", demux->user_agent, NULL); + g_object_set (G_OBJECT (demux->kdownloader->urisrc), "ahs-streaming", TRUE, NULL); + + demux->keyDomain = gst_hlsdemux2_uri_get_domain(demux, key_uri); + if(!g_strcmp0(demux->keyDomain, demux->lastDomain) && demux->lastCookie) { + g_strfreev(demux->keyCookie); + demux->keyCookie = g_strdupv(demux->lastCookie); + } + + if (demux->keyCookie) { + GST_DEBUG_OBJECT (demux, "Setting cookies before KEY download goto PLAYING : %s", *(demux->keyCookie)); + g_object_set (demux->kdownloader->urisrc, "cookies", demux->keyCookie, NULL); + } + + demux->kdownloader->sink = gst_element_factory_make ("appsink", "keyfilesink"); + if (!demux->kdownloader->sink) { + GST_ERROR_OBJECT (demux, "failed to create playlist sink element"); + return FALSE; + } + g_object_set (G_OBJECT (demux->kdownloader->sink), "sync", FALSE, "emit-signals", TRUE, NULL); + g_signal_connect (demux->kdownloader->sink, "new-buffer", G_CALLBACK (gst_hlsdemux2_on_key_buffer), demux); + + gst_bin_add_many (GST_BIN (demux->kdownloader->pipe), demux->kdownloader->urisrc, demux->kdownloader->sink, NULL); + + if (!gst_element_link_many (demux->kdownloader->urisrc, demux->kdownloader->sink, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link src & demux elements..."); + return FALSE; + } + + return TRUE; +} + +static void +gst_hlsdemux2_destroy_key_download (GstHLSDemux2 * demux) +{ + if (demux->kdownloader->pipe) { + GST_DEBUG_OBJECT (demux, "Shutting down key download pipeline"); + gst_element_set_state (demux->kdownloader->pipe, GST_STATE_NULL); + gst_element_get_state (demux->kdownloader->pipe, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref (demux->kdownloader->pipe); + demux->kdownloader->pipe = NULL; + } +} + +static gboolean +gst_hlsdemux2_download_key (GstHLSDemux2 *demux, const gchar * uri) +{ + GstStateChangeReturn ret; + + if (demux->key_uri) { + g_free (demux->key_uri); + } + demux->key_uri = g_strdup (uri); + + g_mutex_lock (demux->kdownloader->lock); + + if (!gst_hlsdemux2_create_key_download (demux, uri)) { + GST_ERROR_OBJECT (demux, "failed to create key download pipeline"); + return FALSE; + } + + ret = gst_element_set_state (demux->kdownloader->pipe, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (demux, "set_state failed..."); + return FALSE; + } + + /* wait until: + * - the download succeed (EOS) + * - the download failed (Error message on the fetcher bus) + * - the download was canceled + */ + GST_DEBUG_OBJECT (demux, "Waiting to fetch the key URI"); + g_cond_wait (demux->kdownloader->cond, demux->kdownloader->lock); + GST_DEBUG_OBJECT (demux, "Recived signal to shutdown key downloader..."); + g_mutex_unlock (demux->kdownloader->lock); + + if (demux->keyCookie) { + g_object_get (demux->kdownloader->urisrc, "cookies", &demux->keyCookie, NULL); + } + + GST_DEBUG_OBJECT (demux, "Got cookies after KEY download : %s", demux->keyCookie ? *(demux->keyCookie) : NULL); + + if(demux->keyCookie){ + g_strfreev(demux->lastCookie); + g_free(demux->lastDomain); + demux->lastCookie = g_strdupv(demux->keyCookie); + demux->lastDomain = g_strdup(demux->keyDomain); + } + + gst_hlsdemux2_destroy_key_download (demux); + + if (demux->cancelled || demux->flushing) + return FALSE; + + return TRUE; +} + +static void +gst_hlsdemux2_have_type_cb (GstElement * typefind, guint probability, + GstCaps * caps, GstHLSDemux2 * demux) +{ + GstStructure *structure = NULL; + + structure = gst_caps_get_structure (caps, 0); + + GST_DEBUG_OBJECT (demux, "typefind found caps %" GST_PTR_FORMAT, caps); + + if (gst_structure_has_name (structure, "video/mpegts")) { + /* found ts fragment */ + demux->fdownloader->demuxer = gst_element_factory_make ("mpegtsdemux", "tsdemux"); + if (!demux->fdownloader->demuxer) { + GST_ERROR_OBJECT (demux, "failed to create mpegtsdemux element"); + GST_ELEMENT_ERROR (demux, CORE, MISSING_PLUGIN, ("failed to create mpegtsdemuxer element"), (NULL)); + return; + } + g_signal_connect (demux->fdownloader->demuxer, "pad-added", G_CALLBACK (gst_hlsdemux2_new_pad_added), demux); + + gst_bin_add (GST_BIN (demux->fdownloader->pipe), demux->fdownloader->demuxer); + + /* set queue element to PLAYING state */ + gst_element_set_state (demux->fdownloader->demuxer, GST_STATE_PLAYING); + + if (!gst_element_link (demux->fdownloader->typefind, demux->fdownloader->demuxer)) { + GST_ERROR_OBJECT (demux, "failed to link src and demux elements..."); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("failed to link typefind & demuxer"), (NULL)); + return; + } + } else if (gst_structure_has_name (structure, "application/x-id3")) { + /* found ts fragment */ + demux->fdownloader->demuxer = gst_element_factory_make ("id3demux", "id3demuxer"); + if (!demux->fdownloader->demuxer) { + GST_ERROR_OBJECT (demux, "failed to create mpegtsdemux element"); + GST_ELEMENT_ERROR (demux, CORE, MISSING_PLUGIN, ("failed to create id3demuxer element"), (NULL)); + return; + } + g_signal_connect (demux->fdownloader->demuxer, "pad-added", G_CALLBACK (gst_hlsdemux2_new_pad_added), demux); + + gst_bin_add (GST_BIN (demux->fdownloader->pipe), demux->fdownloader->demuxer); + + /* set queue element to PLAYING state */ + gst_element_set_state (demux->fdownloader->demuxer, GST_STATE_PLAYING); + + if (!gst_element_link_many (demux->fdownloader->typefind, demux->fdownloader->demuxer, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link src and demux elements..."); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("failed to link typefind & demuxer"), (NULL)); + return; + } + + /* as the fragment does not contain in container format, force the timestamps based on fragment duration */ + demux->fdownloader->force_timestamps = TRUE; + } +} + +static gboolean +gst_hlsdemux2_download_monitor_thread (GstHLSDemux2 *demux) +{ + GTimeVal post_msg_update = {0, }; + + g_mutex_lock (demux->post_msg_lock); + GST_INFO_OBJECT (demux, "waiting for streaming to start to post message..."); + g_cond_wait (demux->post_msg_start, demux->post_msg_lock); + GST_INFO_OBJECT (demux, "Start posting messages..."); + g_mutex_unlock (demux->post_msg_lock); + + while (TRUE) { + gboolean do_post = FALSE; + guint64 percent = 0; + guint idx = 0; + guint64 total_percent = 0; + gboolean bret = FALSE; + + if (demux->cancelled) { + GST_WARNING_OBJECT (demux, "returning msg posting thread..."); + return TRUE; + } + + /* schedule the next update using the target duration field of the + * playlist */ + post_msg_update.tv_sec = 0; + post_msg_update.tv_usec = 0; + + g_get_current_time (&post_msg_update); + g_time_val_add (&post_msg_update, 200000); // posts buffering msg after every 0.5 sec + + /* verify fast switch from here */ + g_mutex_lock (demux->fdownloader->lock); + if (demux->fdownloader->ndownloaded + && !demux->fdownloader->applied_fast_switch && demux->fdownloader->download_start_ts) { + gst_hlsdemux2_check_fast_switch (demux); + } + g_mutex_unlock (demux->fdownloader->lock); + + /* calculate avg percent of streams' percentages */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *stream = demux->streams[idx]; + if (stream ) { + guint64 stream_percent = 0; + + stream_percent = ((stream->cached_duration * 100) / (demux->total_cache_duration / DEFAULT_NUM_FRAGMENTS_CACHE)); + GST_DEBUG_OBJECT (stream->pad, "stream percent = %"G_GUINT64_FORMAT, stream_percent); + total_percent += stream_percent; + } + } + percent = total_percent / demux->active_stream_cnt; + + if (demux->is_buffering) { + do_post = TRUE; + if (percent >= 100) + demux->is_buffering = FALSE; + } else { + if (percent < 10) { + /* if buffering drops below 10% lets start posting msgs */ + demux->is_buffering = TRUE; + do_post = TRUE; + } + } + + if (do_post) { + GstMessage *message; + + if (percent > 100) { + //GST_LOG_OBJECT (demux, " exceeded percent = %d", (gint) percent); + percent = 100; + } + + if (demux->end_of_playlist) { + GST_INFO_OBJECT (demux, "end of the playlist reached... always post 100%"); + percent = 100; + } + + if (percent != demux->percent) { + g_mutex_lock (demux->buffering_lock); + demux->percent = percent; + g_mutex_unlock (demux->buffering_lock); + + GST_LOG_OBJECT (demux, "buffering %d percent", (gint) percent); + + /* posting buffering to internal bus, which will take average & post to main bus */ + message = gst_message_new_buffering (GST_OBJECT_CAST (demux), (gint) demux->percent); + if (!gst_element_post_message (GST_ELEMENT_CAST (demux), message)) { + GST_WARNING_OBJECT (demux, "failed to post buffering msg..."); + } + } + } + + g_mutex_lock (demux->post_msg_lock); + /* block until the next scheduled update or the exit signal */ + bret = g_cond_timed_wait (demux->post_msg_exit, demux->post_msg_lock, &post_msg_update); + g_mutex_unlock (demux->post_msg_lock); + + if (bret) { + GST_WARNING_OBJECT (demux, "signalled to exit posting msgs..."); + break; + } + } + + return TRUE; +} + +static gboolean +gst_hlsdemux2_fragurisrc_event_handler (GstPad * pad, GstEvent *event, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GTimeVal time = {0, }; + + GST_INFO_OBJECT (demux, "received EOS from fragment downloader's httpsrc"); + + if (demux->fdownloader->error_rcvd) { + GST_WARNING_OBJECT (demux, "received EOS event after ERROR.. discard this"); + demux->fdownloader->src_downloaded_size = 0; + demux->fdownloader->download_stop_ts = demux->fdownloader->download_start_ts = 0; + return FALSE; + } + + /* download rate calculation : note down stop time*/ + g_get_current_time (&time); + demux->fdownloader->download_stop_ts = gst_util_get_timestamp(); + + GST_INFO_OBJECT (demux, "download stop_ts = %"G_GUINT64_FORMAT, demux->fdownloader->download_stop_ts); + GST_INFO_OBJECT (demux, "time taken to download current fragment = %"G_GUINT64_FORMAT, demux->fdownloader->download_stop_ts - demux->fdownloader->download_start_ts); + GST_INFO_OBJECT (demux, "fragment downloaded size = %"G_GUINT64_FORMAT" and content_size = %"G_GUINT64_FORMAT, + demux->fdownloader->src_downloaded_size, demux->fdownloader->content_size); + + if (demux->fdownloader->src_downloaded_size && (demux->fdownloader->src_downloaded_size == demux->fdownloader->content_size)) { + guint64 download_rate = 0; + guint i = 0; + guint64 dsum = 0; + + /* calculate current fragment download rate */ + download_rate = (demux->fdownloader->src_downloaded_size * 8 * GST_SECOND) / + (demux->fdownloader->download_stop_ts - demux->fdownloader->download_start_ts); + + if (demux->fdownloader->avg_frag_drates->len == HLSDEMUX2_MAX_N_PAST_FRAG_DOWNLOADRATES) { + /* removing starting element */ + g_array_remove_index (demux->fdownloader->avg_frag_drates, 0); + GST_DEBUG_OBJECT (demux, "removing index from download rates"); + } + g_array_append_val (demux->fdownloader->avg_frag_drates, download_rate); + + for (i = 0 ; i < demux->fdownloader->avg_frag_drates->len; i++) { + dsum += g_array_index (demux->fdownloader->avg_frag_drates, guint64, i); + } + + demux->fdownloader->download_rate = dsum / demux->fdownloader->avg_frag_drates->len; + + GST_INFO_OBJECT (demux, " >>>> DOWNLOAD RATE = %"G_GUINT64_FORMAT, demux->fdownloader->download_rate); + } else { + GST_WARNING_OBJECT (demux, "something wrong better not to calculate download rate with this and discard EOS..."); + demux->fdownloader->src_downloaded_size = 0; + demux->fdownloader->download_stop_ts = demux->fdownloader->download_start_ts = 0; + return FALSE; + } + + demux->fdownloader->src_downloaded_size = 0; + demux->fdownloader->download_stop_ts = demux->fdownloader->download_start_ts = 0; + } + + return TRUE; +} + +static gboolean +gst_hlsdemux2_check_fast_switch (GstHLSDemux2 *demux) +{ + GstClockTime elapsed_time = 0; + guint i = 0; + guint64 download_rate = 0; + + elapsed_time = gst_util_get_timestamp(); + + if (gst_m3u8_client_has_variant_playlist (demux->client) && + ((elapsed_time - demux->fdownloader->download_start_ts) > + (HLSDEMUX2_FAST_CHECK_WARNING_TIME_FACTOR * demux->target_duration))) { + guint64 drate_sum = 0; + GList *previous_variant, *current_variant; + gint old_bandwidth, new_bandwidth; + + GST_INFO_OBJECT (demux, "Doing fast switch using array len %d...", demux->fdownloader->avg_chunk_drates->len); + + for (i = 0 ; i < demux->fdownloader->avg_chunk_drates->len; i++) { + drate_sum += g_array_index (demux->fdownloader->avg_chunk_drates, guint64, i); + } + + /* 0.9 factor is for compensating for connection time*/ + download_rate = (drate_sum * 0.9) / demux->fdownloader->avg_chunk_drates->len; + + GST_INFO_OBJECT (demux, "average chunk download rate = %"G_GUINT64_FORMAT, download_rate); + + if ((elapsed_time - demux->fdownloader->download_start_ts) > + (HLSDEMUX2_FAST_CHECK_CRITICAL_TIME_FACTOR * demux->target_duration)) { + GST_WARNING_OBJECT (demux, "Reached FAST_SWITCH critical point...do fast_switch"); + goto fast_switch; + } + + previous_variant = demux->client->main->current_variant; + current_variant = gst_m3u8_client_get_playlist_for_bitrate (demux->client, download_rate); + + old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth; + new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; + + GST_INFO_OBJECT (demux, "variant's bandwidth got using average chunk download rate is %d", new_bandwidth); + + /* Don't do anything else if the playlist is the same */ + if (new_bandwidth == old_bandwidth) { + GST_WARNING_OBJECT (demux, "Either BW is recovering or NO LOWER variant available than current, DON'T DO FAST_SWITCH..."); + return TRUE; + } + + GST_WARNING_OBJECT (demux, "Going to do fast_switch due to BW problem..."); + goto fast_switch; + } + + return TRUE; + +fast_switch: + + demux->fdownloader->download_rate = download_rate; + + /* flush previous fragment downloads history */ + for (i = 3; i ; --i) { + g_array_remove_index (demux->fdownloader->avg_frag_drates, i - 1); + } + + GST_INFO_OBJECT (demux, "in fast download rate : len %d...", demux->fdownloader->avg_frag_drates->len); + g_array_append_val (demux->fdownloader->avg_frag_drates, demux->fdownloader->download_rate); + + GST_INFO_OBJECT (demux, " >>>> DOWNLOAD RATE in fast switch = %"G_GUINT64_FORMAT, demux->fdownloader->download_rate); + + demux->fdownloader->applied_fast_switch = TRUE; + + /* close current fragment */ + g_cond_signal (demux->fdownloader->cond); + + return FALSE; + +} + +static gboolean +gst_hlsdemux2_fragurisrc_buffer_handler (GstPad * pad, GstBuffer *buffer, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GstClockTime elapsed_ts = 0; + guint64 chunk_drate = 0; + + /* if block_switch already enabled no need to cross check. IMP check*/ + if (demux->fdownloader->applied_fast_switch) { + GST_WARNING_OBJECT (demux, "already applied fast_switch.. drop current buffer"); + return FALSE; + } + + demux->fdownloader->src_downloaded_size += GST_BUFFER_SIZE(buffer); + + if (demux->fdownloader->first_buffer) { + /* get the content size for doing last buffer decryption & for vaidating complete download happend or not */ + GST_INFO_OBJECT (demux, "just received first buffer..."); + g_object_get (demux->fdownloader->urisrc, "content-size", &demux->fdownloader->content_size, NULL); + GST_INFO_OBJECT (demux, "content size = %"G_GUINT64_FORMAT, demux->fdownloader->content_size); + demux->fdownloader->first_buffer = FALSE; + demux->fdownloader->chunk_start_ts = gst_util_get_timestamp(); + demux->fdownloader->chunk_downloaded_size = 0; + } + + + demux->fdownloader->chunk_downloaded_size += GST_BUFFER_SIZE(buffer); + elapsed_ts = gst_util_get_timestamp(); + + if ((elapsed_ts - demux->fdownloader->chunk_start_ts) > HLSDEMUX2_CHUNK_TIME_DURATION) { + GST_DEBUG_OBJECT (demux, "chunk downloaded time = %"GST_TIME_FORMAT, + GST_TIME_ARGS(elapsed_ts - demux->fdownloader->chunk_start_ts)); + + chunk_drate = (demux->fdownloader->chunk_downloaded_size * 8 * GST_SECOND) / (elapsed_ts - demux->fdownloader->chunk_start_ts); + demux->fdownloader->chunk_start_ts = elapsed_ts; + demux->fdownloader->chunk_downloaded_size = 0; + + GST_DEBUG_OBJECT(demux, "CHUNK DOWNLOAD RATE = %"G_GUINT64_FORMAT, chunk_drate); + + if (demux->fdownloader->avg_chunk_drates->len == HLSDEMUX2_NUM_CHUNK_DOWNLOADS_FAST_SWITCH) { + /* removing starting element */ + g_array_remove_index (demux->fdownloader->avg_chunk_drates, 0); + } + g_array_append_val (demux->fdownloader->avg_chunk_drates, chunk_drate); + } + + GST_LOG_OBJECT (demux, "fragment downloaded size at urisrc = %"G_GUINT64_FORMAT, demux->fdownloader->src_downloaded_size); + + return TRUE; +} + +static gboolean +gst_hlsdemux2_queue_buffer_handler (GstPad *pad, GstBuffer *buffer, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + unsigned char *encry_data = NULL; + unsigned char *outdata = NULL; + unsigned char *remained_data = NULL; + unsigned char* total_data = NULL; + + demux->fdownloader->queue_downloaded_size += GST_BUFFER_SIZE(buffer); + + GST_DEBUG_OBJECT (demux, "total downloaded size = %"G_GUINT64_FORMAT, demux->fdownloader->queue_downloaded_size); + + if (demux->fdownloader->is_encrypted) { + guint encry_data_size = 0; + unsigned char *indata = GST_BUFFER_DATA (buffer); + guint insize = GST_BUFFER_SIZE (buffer); + gint out_len = 0; + + if (demux->fdownloader->remaining_data && demux->fdownloader->remaining_size) { + GST_LOG_OBJECT (demux, "remain size = %d..", demux->fdownloader->remaining_size); + + encry_data = (unsigned char *) malloc (insize + demux->fdownloader->remaining_size); + if (!encry_data) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + goto error; + } + + /* copy remaining data in previous iteration */ + memcpy (encry_data, demux->fdownloader->remaining_data, demux->fdownloader->remaining_size); + /* copy input encrypted data of current iteration */ + memcpy (encry_data + demux->fdownloader->remaining_size, indata, insize); + + encry_data_size = insize + demux->fdownloader->remaining_size; + + free (demux->fdownloader->remaining_data); + demux->fdownloader->remaining_data = NULL; + demux->fdownloader->remaining_size = 0; + } else { + encry_data_size = insize; + encry_data = indata; + } + + demux->fdownloader->remaining_size = encry_data_size % GST_HLS_DEMUX_AES_BLOCK_SIZE; + + if (demux->fdownloader->remaining_size) { + GST_LOG_OBJECT (demux, "Remining in current iteration = %d\n", demux->fdownloader->remaining_size); + demux->fdownloader->remaining_data = (gchar *) malloc (demux->fdownloader->remaining_size); + if ( demux->fdownloader->remaining_data == NULL) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + goto error; + } + + /* copy remained portion to buffer */ + memcpy (demux->fdownloader->remaining_data, + encry_data + encry_data_size - demux->fdownloader->remaining_size, + demux->fdownloader->remaining_size); + } + + out_len = encry_data_size - demux->fdownloader->remaining_size + GST_HLS_DEMUX_AES_BLOCK_SIZE; + outdata = (unsigned char *) malloc (out_len); + if (!outdata) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + goto error; + } + + if (!gst_m3u8_client_decrypt_update (demux->client, outdata, &out_len, + encry_data, encry_data_size - demux->fdownloader->remaining_size)) { + GST_ERROR_OBJECT (demux, "failed to decrypt...\n"); + GST_ELEMENT_ERROR (demux, STREAM, FAILED, ("failed to decrypt"), (NULL)); + goto error; + } + + if (encry_data != indata) { + g_free (encry_data); + encry_data = NULL; + } + + g_free (GST_BUFFER_MALLOCDATA(buffer)); + + /* decrypt the final buffer */ + if (demux->fdownloader->queue_downloaded_size == demux->fdownloader->content_size) { + gint remained_size = 0; + gint total_len = 0; + + GST_LOG_OBJECT (demux, "remained data = %p & remained size = %d", demux->fdownloader->remaining_data, demux->fdownloader->remaining_size); + + remained_data = (unsigned char *) malloc (GST_HLS_DEMUX_AES_BLOCK_SIZE); + if (!remained_data) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + goto error; + } + gst_m3u8_client_decrypt_final (demux->client, remained_data, &remained_size); + GST_LOG_OBJECT (demux,"remained size = %d\n", remained_size); + + if (remained_size) { + total_len = out_len + remained_size; + + total_data = (unsigned char *) malloc (total_len); + if (!total_data) { + GST_ERROR_OBJECT (demux, "failed to allocate memory...\n"); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + goto error; + } + + memcpy (total_data, outdata, out_len); + memcpy (total_data+out_len, remained_data, remained_size); + + GST_BUFFER_MALLOCDATA(buffer) = GST_BUFFER_DATA(buffer) = total_data; + GST_BUFFER_SIZE(buffer) = total_len; + g_free (remained_data); + g_free (outdata); + } else { + GST_BUFFER_MALLOCDATA(buffer) = GST_BUFFER_DATA(buffer) = outdata; + GST_BUFFER_SIZE(buffer) = out_len; + g_free (remained_data); + } + + if (!gst_m3u8_client_decrypt_deinit (demux->client)) { + GST_WARNING_OBJECT (demux, "decryption cleanup failed..."); + } + } else { + GST_BUFFER_MALLOCDATA(buffer) = GST_BUFFER_DATA(buffer) = outdata; + GST_BUFFER_SIZE(buffer) = out_len; + } + } + + return TRUE; + +error: + if (encry_data) + free (encry_data); + + if (outdata) + free (outdata); + + if (remained_data) + free (remained_data); + + return FALSE; +} + +static gboolean +gst_hlsdemux2_create_fragment_download (GstHLSDemux2 * demux, const gchar * uri) +{ + GstBus *bus = NULL; + GstPad *srcpad = NULL; + + if (!gst_uri_is_valid (uri)) { + GST_ERROR_OBJECT (demux, "invalid uri : %s...", uri == NULL ? "NULL" : uri); + return FALSE; + } + + /* re-initialize fragment variables */ + demux->fdownloader->first_buffer = TRUE; + demux->fdownloader->cur_stream_cnt = 0; + demux->fdownloader->error_rcvd = FALSE; + + demux->fdownloader->pipe = gst_pipeline_new ("frag-fdownloader"); + if (!demux->fdownloader->pipe) { + GST_ERROR_OBJECT (demux, "failed to create pipeline"); + return FALSE; + } + + bus = gst_pipeline_get_bus (GST_PIPELINE (demux->fdownloader->pipe)); + gst_bus_add_watch (bus, (GstBusFunc)gst_hlsdemux2_fragment_download_bus_cb, demux); + gst_object_unref (bus); + + GST_INFO_OBJECT (demux, "Creating source element for the URI : %s", uri); + + demux->fdownloader->urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, "fragurisrc"); + if (!demux->fdownloader->urisrc) { + GST_ERROR_OBJECT (demux, "failed to create urisrc"); + return FALSE; + } + + g_object_set (G_OBJECT (demux->fdownloader->urisrc), "timeout", HLSDEMUX2_HTTP_TIMEOUT, NULL); + g_object_set (G_OBJECT (demux->fdownloader->urisrc), "user-agent", demux->user_agent, NULL); + g_object_set (G_OBJECT (demux->fdownloader->urisrc), "ahs-streaming", TRUE, NULL); + g_object_set (G_OBJECT (demux->fdownloader->urisrc), "blocksize", DEFAULT_BLOCKSIZE, NULL); + + demux->fragDomain = gst_hlsdemux2_uri_get_domain(demux, uri); + if(!g_strcmp0 (demux->fragDomain, demux->lastDomain) && demux->lastCookie) { + g_strfreev(demux->fragCookie); + demux->fragCookie = g_strdupv (demux->lastCookie); + } + + if (demux->fragCookie) { + GST_DEBUG_OBJECT (demux, "Setting cookies before FRAG download goto PLAYING: %s", *(demux->fragCookie)); + g_object_set (demux->fdownloader->urisrc, "cookies", demux->fragCookie, NULL); + } + + srcpad = gst_element_get_static_pad (demux->fdownloader->urisrc, "src"); + if (!srcpad) { + GST_ERROR_OBJECT (demux, "failed to get source pad from urisrc..."); + return FALSE; + } + + gst_pad_add_event_probe (srcpad, G_CALLBACK (gst_hlsdemux2_fragurisrc_event_handler), demux); + gst_pad_add_buffer_probe (srcpad, G_CALLBACK (gst_hlsdemux2_fragurisrc_buffer_handler), demux); + + gst_object_unref (srcpad); + + demux->fdownloader->queue = gst_element_factory_make ("queue2", "frag_queue"); + if (!demux->fdownloader->queue) { + GST_ERROR_OBJECT (demux, "failed to create queue2"); + return FALSE; + } + + g_object_set (G_OBJECT (demux->fdownloader->queue), + "max-size-buffers", 0, + "max-size-bytes", 0, + "max-size-time", 0 * GST_SECOND, NULL); + + srcpad = gst_element_get_static_pad (demux->fdownloader->queue, "src"); + if (!srcpad) { + GST_ERROR_OBJECT (demux, "failed to get source pad from fragment queue..."); + return FALSE; + } + + gst_pad_add_buffer_probe (srcpad, G_CALLBACK (gst_hlsdemux2_queue_buffer_handler), demux); + + demux->fdownloader->typefind = gst_element_factory_make ("typefind", "typefinder"); + if (!demux->fdownloader->typefind) { + GST_ERROR_OBJECT (demux, "failed to create typefind element"); + gst_object_unref (srcpad); + return FALSE; + } + + gst_bin_add_many (GST_BIN (demux->fdownloader->pipe), + demux->fdownloader->urisrc, demux->fdownloader->queue, demux->fdownloader->typefind, NULL); + + if (!gst_element_link_many (demux->fdownloader->urisrc, + demux->fdownloader->queue, demux->fdownloader->typefind, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link src, queue & typefind elements..."); + gst_object_unref (srcpad); + return FALSE; + } + + /* typefind callback to determine file type */ + g_signal_connect (demux->fdownloader->typefind, "have-type", G_CALLBACK (gst_hlsdemux2_have_type_cb), demux); + + gst_object_unref (srcpad); + + return TRUE; +} + +static void +gst_hlsdemux2_destroy_fragment_download (GstHLSDemux2 * demux) +{ + if (demux->fdownloader->pipe) { + GST_DEBUG_OBJECT (demux, "completely closing fragment downloader pipeline"); + gst_element_set_state (demux->fdownloader->pipe, GST_STATE_NULL); + gst_element_get_state (demux->fdownloader->pipe, NULL, NULL, GST_CLOCK_TIME_NONE); + GST_DEBUG_OBJECT (demux, "completely closed fragment downloader pipeline"); + gst_object_unref (demux->fdownloader->pipe); + demux->has_image_buffer = FALSE; + demux->fdownloader->pipe = NULL; + demux->fdownloader->cur_stream_cnt = 0; + g_list_free (demux->fdownloader->sinkbins); // all sink elements will be unreffed when pipeline is unreffed + demux->fdownloader->sinkbins = 0; + } +} + +static gboolean +gst_hlsdemux2_download_fragment (GstHLSDemux2 *demux, const gchar * uri) +{ + GstStateChangeReturn ret; + + g_mutex_lock (demux->fdownloader->lock); + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (demux,"returning from download fragment due to cancel or flushing.."); + g_mutex_unlock (demux->fdownloader->lock); + return FALSE; + } + + if (demux->frag_uri) { + g_free (demux->frag_uri); + } + demux->frag_uri = g_strdup (uri); + + demux->fdownloader->get_next_frag = FALSE; + + if (!gst_hlsdemux2_create_fragment_download (demux, uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + g_mutex_unlock (demux->fdownloader->lock); + return FALSE; + } + + /* download rate calculation : note down start time*/ + demux->fdownloader->download_start_ts = gst_util_get_timestamp(); + GST_INFO_OBJECT (demux, "download start_ts = %"G_GUINT64_FORMAT, demux->fdownloader->download_start_ts); + + ret = gst_element_set_state (demux->fdownloader->pipe, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (demux, "set_state failed..."); + g_mutex_unlock (demux->fdownloader->lock); + return FALSE; + } + gst_element_get_state (demux->fdownloader->pipe, NULL, NULL, -1); + + /* wait until: + * - the download succeed (EOS) + * - the download failed (Error message on the fetcher bus) + * - the download was canceled + */ + GST_DEBUG_OBJECT (demux, "Waiting to fetch the URI"); + g_cond_wait (demux->fdownloader->cond, demux->fdownloader->lock); + GST_INFO_OBJECT (demux, "Recived signal to shutdown..."); + + if (demux->fragCookie) + g_strfreev (demux->fragCookie); + + g_object_get (demux->fdownloader->urisrc, "cookies", &demux->fragCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after FRAG download : %s", demux->fragCookie ? *(demux->fragCookie) : NULL); + + if (demux->fdownloader->applied_fast_switch) { + if (demux->fdownloader->src_downloaded_size == demux->fdownloader->content_size) { + /* to handle case, when both fast_switch & bus_callback signalled at same moment */ + GST_WARNING_OBJECT (demux, "ignoring fast_switch as fragment download is completed..."); + demux->fdownloader->applied_fast_switch = FALSE; + } else { + GST_WARNING_OBJECT (demux, "decrement the media sequence by one, due to fast_switch"); + gst_m3u8_client_decrement_sequence (demux->client); + } + } + + if (demux->cancelled || demux->flushing || + demux->fdownloader->get_next_frag || demux->fdownloader->applied_fast_switch) { + GstBuffer *buf = NULL; + guint idx = 0; + + /* clear stream queues if we already downloaded some data */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *stream = demux->streams[idx]; + if (stream ) { + while (!g_queue_is_empty (stream->downloader_queue)) { + buf = g_queue_pop_head (stream->downloader_queue); + gst_buffer_unref (buf); + } + GST_LOG_OBJECT (stream->pad, "cleared stream download queue..."); + g_queue_clear (stream->downloader_queue); + } + } + } + + if(demux->fragCookie){ + g_strfreev(demux->lastCookie); + g_free(demux->lastDomain); + demux->lastCookie = g_strdupv(demux->fragCookie); + demux->lastDomain = g_strdup(demux->fragDomain); + } + + if(demux->active_stream_cnt == 1 && !demux->private_stream) { + gchar *image_header = NULL; + GValue codec_type = G_VALUE_INIT; + GValue image_data = G_VALUE_INIT; + GstMessage * tag_message = NULL; + struct stat stat_results; + gint iread = 0; + GstBuffer *image_buffer = NULL; + GstTagList * tag_list = gst_tag_list_new(); + int dummy_fp = -1; + + GST_INFO_OBJECT (demux, "Going to show fixed image"); + dummy_fp = open (PREDEFINED_IMAGE_FRAME_LOCATION, O_RDONLY); + if (dummy_fp < 0) { + GST_WARNING_OBJECT (demux, "failed to open fixed image file : %s...", PREDEFINED_IMAGE_FRAME_LOCATION); + goto skip_posting_image; + } + + GST_LOG_OBJECT (demux, "opened fixed image file %s successfully...", PREDEFINED_IMAGE_FRAME_LOCATION); + + if (fstat (dummy_fp, &stat_results) < 0) { + GST_WARNING_OBJECT (demux, "failed to get stats of a file..."); + close (dummy_fp); + goto skip_posting_image; + } + + GST_LOG_OBJECT (demux, "size of the dummy file = %d", stat_results.st_size); + + image_buffer = gst_buffer_new_and_alloc (stat_results.st_size); + if (!image_buffer) { + GST_ERROR_OBJECT (demux, "failed to allocate memory..."); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + close (dummy_fp); + goto error; + } + + iread = read (dummy_fp, GST_BUFFER_DATA (image_buffer), stat_results.st_size); + + GST_LOG_OBJECT (demux, "read size = %d successfully", iread); + + close (dummy_fp); + + /* check whether it is same as previous image */ + if (demux->prev_image_buffer && + (GST_BUFFER_SIZE(demux->prev_image_buffer) == GST_BUFFER_SIZE(image_buffer))) { + if (!memcmp (GST_BUFFER_DATA(demux->prev_image_buffer), GST_BUFFER_DATA(image_buffer), GST_BUFFER_SIZE(image_buffer))) { + GST_INFO_OBJECT (demux, "current & previous embedded images are same..no need to post image again"); + gst_buffer_unref (image_buffer); + goto skip_posting_image; + } + } + + if (demux->prev_image_buffer) { + gst_buffer_unref (demux->prev_image_buffer); + } + demux->prev_image_buffer = gst_buffer_copy(image_buffer); + + g_value_init (&codec_type, G_TYPE_STRING); + g_value_init (&image_data, GST_TYPE_BUFFER); + gst_value_set_buffer(&image_data, image_buffer); + + image_header = g_strndup((gchar *) image_buffer->data, 8); + + if((image_header[0] == 0xFF) && (image_header[1] == 0xD8)) { + GST_INFO_OBJECT(demux, "Found JPEG image header"); + g_value_set_static_string (&codec_type, "image/jpeg"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } else if (!g_strcmp0(image_header, "\211PNG\015\012\032\012")) { + GST_INFO_OBJECT(demux, "Found PNG image header"); + g_value_set_static_string (&codec_type, "image/png"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } else { + g_value_set_static_string (&codec_type, "image/unknown"); + GST_INFO_OBJECT(demux, "Unknown image header"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } + + gst_tag_list_add_value (tag_list, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, &image_data); + + GST_INFO_OBJECT(demux, "Set value : %s", g_value_get_string (&codec_type)); + g_value_unset(&codec_type); + + tag_message = gst_message_new_tag (GST_OBJECT_CAST (demux), tag_list); + if(!gst_element_post_message (GST_ELEMENT_CAST (demux), tag_message)) { + GST_ERROR_OBJECT (demux, "failed to post fixed image tag"); + } + + GST_INFO_OBJECT (demux, "successfully posted image tag..."); + gst_buffer_unref (image_buffer); + g_free (image_header); + } + +skip_posting_image: + + gst_hlsdemux2_destroy_fragment_download (demux); + + if (demux->fdownloader->remaining_data) { + free (demux->fdownloader->remaining_data); + demux->fdownloader->remaining_data = NULL; + } + + demux->fdownloader->remaining_size = 0; + demux->fdownloader->src_downloaded_size = 0; + demux->fdownloader->queue_downloaded_size = 0; + demux->fdownloader->download_stop_ts = demux->fdownloader->download_start_ts = 0; + demux->fdownloader->applied_fast_switch = FALSE; + demux->fdownloader->content_size = 0; + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (demux, "returning false due to flushing/cancelled"); + goto error; + } else if (demux->fdownloader->get_next_frag || demux->fdownloader->applied_fast_switch) { + GST_INFO_OBJECT (demux, "Requesting next fragment due to error or same sequence number from different variant"); + goto exit; + } + + demux->fdownloader->ndownloaded++; + + GST_INFO_OBJECT (demux, "number of fragments downloaded = %d", demux->fdownloader->ndownloaded); + + /* reset the count */ + demux->soup_request_fail_cnt = HLSDEMUX2_SOUP_FAILED_CNT; + +exit: + if (demux->private_stream) { + g_free (demux->private_stream); + demux->private_stream = NULL; + } + g_mutex_unlock (demux->fdownloader->lock); + + return TRUE; + +error: + if (demux->private_stream) { + g_free (demux->private_stream); + demux->private_stream = NULL; + } + g_mutex_unlock (demux->fdownloader->lock); + + return FALSE; +} + +static gboolean +gst_hlsdemux2_change_playlist (GstHLSDemux2 * demux, guint max_bitrate, gboolean *is_switched) +{ + GList *previous_variant, *current_variant; + gint old_bandwidth, new_bandwidth; + + previous_variant = demux->client->main->current_variant; + current_variant = gst_m3u8_client_get_playlist_for_bitrate (demux->client, max_bitrate); + + old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth; + new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; + + /* Don't do anything else if the playlist is the same */ + if (new_bandwidth == old_bandwidth) { + if (is_switched) + *is_switched = FALSE; + return TRUE; + } + + GST_M3U8_CLIENT_LOCK (demux->client); + demux->client->main->current_variant = current_variant; + GST_M3U8_CLIENT_UNLOCK (demux->client); + + gst_m3u8_client_set_current (demux->client, current_variant->data); + + GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" + " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); + + gst_hlsdemux2_apply_disc(demux); + + if (is_switched) + *is_switched = TRUE; + + return TRUE; +} + +static gboolean +gst_hlsdemux2_get_next_fragment (GstHLSDemux2 * demux, gboolean *is_error) +{ + const gchar *next_fragment_uri = NULL;; + gchar *next_fragment_key_uri = NULL; + gchar *iv = NULL; + GstClockTime duration; + GstClockTime timestamp; + gboolean discont = FALSE; + gboolean bret = TRUE; + GstClockTime lookup_time = 0; + guint i = 0; + GstHLSDemux2Stream *stream = NULL; + + if (!demux->is_live) { + for (i = 0; i< HLSDEMUX2_STREAM_NUM; i++) { + stream = demux->streams[i]; + if (stream) { + lookup_time = stream->lts + stream->frame_duration + (0.5 * GST_SECOND); + GST_INFO_OBJECT (stream->pad, "Lookup time = %"GST_TIME_FORMAT, GST_TIME_ARGS(lookup_time)); + stream->frame_duration = 0; + if (stream->type == HLSDEMUX2_STREAM_AUDIO) + break; + } + } + } + + if (!gst_m3u8_client_get_next_fragment (demux->client, lookup_time, + &discont, &next_fragment_uri, &duration, ×tamp, &next_fragment_key_uri, &iv)) { + + GST_INFO_OBJECT (demux, "This playlist doesn't contain more fragments"); + + if (demux->is_live && (demux->client->update_failed_count < DEFAULT_FAILED_COUNT)) { + GST_WARNING_OBJECT (demux, "Could not get next fragment, try again after updating playlist..."); + *is_error = FALSE; + return FALSE; + } else { + goto end_of_list; + } + } + + demux->cfrag_dur = duration; + demux->fdownloader->cur_running_dur += duration; + + GST_LOG_OBJECT (demux, "current running duration = %"GST_TIME_FORMAT, GST_TIME_ARGS(demux->fdownloader->cur_running_dur)); + + GST_DEBUG_OBJECT (demux, "Fetching next fragment = %s & key uri = %s", next_fragment_uri, next_fragment_key_uri); + + /* when hls requests next fragment due to some error like NOT_FOUND, then also we need to set apply disc */ + if (discont || demux->fdownloader->get_next_frag) + gst_hlsdemux2_apply_disc (demux); + + if (next_fragment_key_uri) { + /* download key data & initialize decryption with key data & IV */ + GST_INFO_OBJECT (demux, "Fetching next fragment key %s", next_fragment_key_uri); + + if (demux->kdownloader->prev_key_uri) { + if (strcmp (demux->kdownloader->prev_key_uri, next_fragment_key_uri)) { + /* if previous & current key uris are different, download new one */ + if (demux->kdownloader->key) { + gst_buffer_unref (demux->kdownloader->key); + demux->kdownloader->key = NULL; + } + + if (demux->kdownloader->prev_key_uri) + g_free (demux->kdownloader->prev_key_uri); + + demux->kdownloader->prev_key_uri = g_strdup(next_fragment_key_uri); + + /* download the key data as there is a change */ + if (!gst_hlsdemux2_download_key (demux, next_fragment_key_uri)) { + GST_ERROR_OBJECT (demux, "failed to download key..."); + goto error; + } + } else { + GST_INFO_OBJECT (demux, "already having the key...no need to download again"); + } + } else { + if (demux->kdownloader->key) { + gst_buffer_unref (demux->kdownloader->key); + demux->kdownloader->key = NULL; + } + + demux->kdownloader->prev_key_uri = g_strdup(next_fragment_key_uri); + + if (!gst_hlsdemux2_download_key (demux, next_fragment_key_uri)) { + GST_ERROR_OBJECT (demux, "failed to download key..."); + goto error; + } + /* reset the count */ + demux->soup_request_fail_cnt = HLSDEMUX2_SOUP_FAILED_CNT; + } + + if (!demux->kdownloader->key || !GST_BUFFER_DATA (demux->kdownloader->key)) { + GST_ERROR_OBJECT (demux, "eos received without key content..."); + goto error; + } + + if (!gst_m3u8_client_decrypt_init (demux->client, GST_BUFFER_DATA (demux->kdownloader->key), iv)) { + GST_ERROR_OBJECT (demux, "failed to initialize AES decryption..."); + goto error; + } + + demux->fdownloader->is_encrypted = TRUE; + } + + if (!gst_hlsdemux2_download_fragment (demux, next_fragment_uri)) { + GST_ERROR_OBJECT (demux, "failed to download fragment..."); + goto error; + } + +exit: + if (next_fragment_uri) + g_free ((gpointer)next_fragment_uri); + if (next_fragment_key_uri) + g_free ((gpointer)next_fragment_key_uri); + if (iv) + g_free ((gpointer)iv); + + return bret; + +error: + if (!demux->flushing) + *is_error = TRUE; + bret = FALSE; + goto exit; + + end_of_list: + GST_INFO_OBJECT (demux, "Reached end of playlist, sending EOS"); + + gst_hlsdemux2_push_eos (demux); + + demux->end_of_playlist = TRUE; + bret = FALSE; + goto exit; +} + + +static gboolean +gst_hlsdemux2_switch_playlist (GstHLSDemux2 * demux, gboolean *is_switched) +{ + GST_M3U8_CLIENT_LOCK (demux->client); + if (!demux->client->main->lists) { + /* not a variant to switch */ + GST_M3U8_CLIENT_UNLOCK (demux->client); + GST_INFO_OBJECT (demux, "not a variant to switch..."); + return TRUE; + } + GST_M3U8_CLIENT_UNLOCK (demux->client); + + if (demux->force_lower_bitrate && (demux->fdownloader->ndownloaded % FORCE_LOW_BITRATE_AFTER_CNT == 0)) { + demux->fdownloader->download_rate = 0; // Using some temp lowest value + GST_WARNING_OBJECT (demux, "resetting to lowest one bitrate = %"G_GUINT64_FORMAT, demux->fdownloader->download_rate); + } + + return gst_hlsdemux2_change_playlist (demux, demux->fdownloader->download_rate, is_switched); +} + +static void +gst_hlsdemux2_apply_disc (GstHLSDemux2 * demux) +{ + int i = 0; + GstHLSDemux2Stream *stream = NULL; + + for (i = 0; i< HLSDEMUX2_STREAM_NUM; i++) { + stream = demux->streams[i]; + + if (stream) { + stream->apply_disc = TRUE; + GST_INFO_OBJECT (stream->pad, "apply discontinuity..."); + } + } +} + +static void +gst_hlsdemux2_fragment_download_loop (GstHLSDemux2 * demux) +{ + gboolean is_error = FALSE; + gboolean bret = FALSE; + GTimeVal tmp_update = {0, }; + GTimeVal current; + guint64 current_time = 0; + guint64 nextupdate_time = 0; + + /* if variant playlist, get current subplaylist first */ + if (gst_m3u8_client_has_variant_playlist(demux->client)) { + gboolean is_error = FALSE; + + if (!gst_hlsdemux2_update_playlist (demux, FALSE, &is_error)) { + GST_ERROR_OBJECT (demux, "failed to update playlist. uri : %s", gst_m3u8_client_get_current_uri(demux->client)); + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, ("Could not update the playlist"), (NULL)); + goto exit; + } + } else if (demux->is_live) { + /* single variant live.. so schedule for starting */ + if (demux->is_live) + gst_hlsdemux2_schedule (demux); + } + + if (demux->fdownloader->find_mediaseq) { + GList *walk; + GstClockTime current_pos, target_pos; + gint current_sequence; + GstM3U8MediaFile *file; + guint i = 0; + GstHLSDemux2Stream *stream = NULL; + + GST_M3U8_CLIENT_LOCK (demux->client); + file = GST_M3U8_MEDIA_FILE (demux->client->current->files->data); + current_sequence = file->sequence; + current_pos = 0 ; + target_pos = demux->fdownloader->seeked_pos; + for (walk = demux->client->current->files; walk; walk = walk->next) { + file = walk->data; + + current_sequence = file->sequence; + if (current_pos <= target_pos && + target_pos < current_pos + file->duration) { + break; + } + current_pos += file->duration; + } + GST_M3U8_CLIENT_UNLOCK (demux->client); + + if (walk == NULL) { + GST_WARNING_OBJECT (demux, "Could not find seeked fragment"); + } + GST_M3U8_CLIENT_LOCK (demux->client); + GST_INFO_OBJECT (demux, "seeking to sequence %d", current_sequence); + demux->client->sequence = current_sequence; + GST_M3U8_CLIENT_UNLOCK (demux->client); + demux->ns_start = demux->fdownloader->cur_running_dur = current_pos; // NON-accurate seek + + for (i = 0; i< HLSDEMUX2_STREAM_NUM; i++) { + stream = demux->streams[i]; +#ifdef LATEST_AV_SYNC + if (stream) { + stream->lts = stream->total_stream_time = current_pos; + GST_INFO_OBJECT (stream->pad, "Changed stream->lts to %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->lts)); + } +#else + if (stream && stream->type == HLSDEMUX2_STREAM_AUDIO) { + stream->lts = current_pos; + } +#endif + } + + demux->fdownloader->find_mediaseq = FALSE; + } + + demux->target_duration = gst_m3u8_client_get_target_duration(demux->client); + demux->total_cache_duration = DEFAULT_NUM_FRAGMENTS_CACHE * demux->target_duration; + demux->is_live = gst_m3u8_client_is_live(demux->client); + + GST_INFO_OBJECT (demux, "Total cache duration = %"GST_TIME_FORMAT, GST_TIME_ARGS(demux->total_cache_duration)); + + while (TRUE) { + + if (demux->cancelled) + goto exit; + + /* get the next fragement */ + bret = gst_hlsdemux2_get_next_fragment (demux, &is_error); + + if (!bret) { + if (is_error) { + GST_ERROR_OBJECT (demux, "error in getting next fragment"); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("could not download fragment"), (NULL)); + goto exit; + } else { + if (!demux->end_of_playlist) { + if (demux->flushing) { + GST_WARNING_OBJECT (demux, "SEEK is in progress.. pause the task"); + goto exit; + } else if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not update fragment & playlist update failed count = %d", + demux->client->update_failed_count); + } else { + GST_ERROR_OBJECT (demux, "Could not get next fragment.."); + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, ("Could not get next fragment..."), (NULL)); + goto exit; + } + } else { + GST_INFO_OBJECT (demux, "end of fragment download, doing pause"); + goto exit; + } + } + } else { + /* reset the update failed count */ + demux->client->update_failed_count = 0; + } + + if (demux->cancelled) + goto exit; + + if (demux->is_live) { + /* get the current time */ + current.tv_sec = current.tv_usec = 0; + g_get_current_time (¤t); + + current_time = ((guint64)current.tv_sec * (guint64)1000000) + (guint64)current.tv_usec; + nextupdate_time = ((guint64)demux->next_update.tv_sec * (guint64)1000000) + (guint64)demux->next_update.tv_usec; + } + + if (demux->is_live && ((current_time > nextupdate_time) || !bret)) { + gboolean is_switched = FALSE; + + /* try to switch to another bitrate if needed */ + // TODO: take care of return value + gst_hlsdemux2_switch_playlist (demux, &is_switched); + + g_mutex_lock (demux->pl_update_lock); + + /* block until the next scheduled update or the exit signal */ + bret = g_cond_timed_wait (demux->pl_update_cond, demux->pl_update_lock, &demux->next_update); + + g_mutex_unlock (demux->pl_update_lock); + + GST_DEBUG_OBJECT (demux, "playlist update wait is completed. reason : %s", bret ? "Received signal" : "time-out"); + + tmp_update.tv_sec = 0; + tmp_update.tv_usec = 0; + + g_get_current_time (&tmp_update); + + if (bret) { + GST_DEBUG_OBJECT (demux, "Sombody signalled manifest waiting... going to exit and diff = %ld", + ((demux->next_update.tv_sec * 1000000)+ demux->next_update.tv_usec) - ((tmp_update.tv_sec * 1000000)+ tmp_update.tv_usec)); + goto exit; + } else if (demux->cancelled) { + GST_WARNING_OBJECT (demux, "closing is in progress..exit now"); + goto exit; + } + + if (!gst_hlsdemux2_update_playlist (demux, FALSE, &is_error)) { + if (is_error) { + GST_ERROR_OBJECT (demux, "failed to update playlist..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to update playlist"), (NULL)); + goto exit; + } else { + if (demux->flushing) { + GST_WARNING_OBJECT (demux, "SEEK is in progress.. pause the task"); + goto exit; + } else if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not update playlist & playlist update failed count = %d", + demux->client->update_failed_count); + continue; + } else { + GST_ERROR_OBJECT (demux, "Could not update playlist.."); + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, ("Could not update playlist..."), (NULL)); + goto exit; + } + } + } + + } else { + gboolean is_switched = FALSE; + + /* try to switch to another bitrate if needed */ + // TODO: take care of return value + + /* when in recovery mode, do not obey download rates */ + if (demux->pldownloader->recovery_mode == HLSDEMUX2_NO_RECOVERY) + gst_hlsdemux2_switch_playlist (demux, &is_switched); + + if (is_switched) { + if (!gst_hlsdemux2_update_playlist (demux, FALSE, &is_error)) { + if (is_error) { + GST_ERROR_OBJECT (demux, "failed to update playlist..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to update playlist"), (NULL)); + goto exit; + } else { + if (demux->flushing) { + GST_WARNING_OBJECT (demux, "SEEK is in progress.. pause the task"); + goto exit; + } else if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not update playlist & playlist update failed count = %d", + demux->client->update_failed_count); + continue; + } else { + GST_ERROR_OBJECT (demux, "Could not update playlist.."); + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, ("Could not update playlist..."), (NULL)); + goto exit; + } + } + } + } else if (demux->is_live) { + GstClockTime current_time = gst_util_get_timestamp(); + + if ((current_time - demux->pldownloader->download_start_ts) < + (PLAYLIST_REFRESH_TIMEOUT - (HLSDEMUX2_FAST_CHECK_CRITICAL_TIME_FACTOR * demux->target_duration) - HLSDEMUX2_OVERHEAD)) { + GST_INFO_OBJECT (demux, "Eligible to download next fragment w/o playlist update : " + "elapsed time since last playlist request = %"GST_TIME_FORMAT" and max_time_limit = %"GST_TIME_FORMAT, + GST_TIME_ARGS(current_time - demux->pldownloader->download_start_ts), + GST_TIME_ARGS(PLAYLIST_REFRESH_TIMEOUT - (HLSDEMUX2_FAST_CHECK_CRITICAL_TIME_FACTOR * demux->target_duration) - HLSDEMUX2_OVERHEAD)); + } else { + + GST_INFO_OBJECT (demux, "NOT Eligible to download next fragment w/o playlist update : " + "elapsed time since previous request = %"GST_TIME_FORMAT" and max_time_limit = %"GST_TIME_FORMAT, + GST_TIME_ARGS(current_time - demux->pldownloader->download_start_ts), + GST_TIME_ARGS(PLAYLIST_REFRESH_TIMEOUT - (HLSDEMUX2_FAST_CHECK_CRITICAL_TIME_FACTOR * demux->target_duration) - HLSDEMUX2_OVERHEAD)); + + /* we are not eligible to download, next fragment.. So wait till next playlist update */ + g_mutex_lock (demux->pl_update_lock); + /* block until the next scheduled update or the exit signal */ + bret = g_cond_timed_wait (demux->pl_update_cond, demux->pl_update_lock, &demux->next_update); + g_mutex_unlock (demux->pl_update_lock); + + GST_DEBUG_OBJECT (demux, "playlist update wait is completed. reason : %s", bret ? "Received signal" : "time-out"); + + tmp_update.tv_sec = 0; + tmp_update.tv_usec = 0; + + g_get_current_time (&tmp_update); + + if (bret) { + GST_DEBUG_OBJECT (demux, "Sombody signalled manifest waiting... going to exit and diff = %ld", + ((demux->next_update.tv_sec * 1000000)+ demux->next_update.tv_usec) - ((tmp_update.tv_sec * 1000000)+ tmp_update.tv_usec)); + goto exit; + } else if (demux->cancelled) { + GST_WARNING_OBJECT (demux, "closing is in progress..exit now"); + goto exit; + } + + if (!gst_hlsdemux2_update_playlist (demux, FALSE, &is_error)) { + if (is_error) { + GST_ERROR_OBJECT (demux, "failed to update playlist..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to update playlist"), (NULL)); + goto exit; + } else { + if (demux->flushing) { + GST_WARNING_OBJECT (demux, "SEEK is in progress.. pause the task"); + goto exit; + } else if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not update playlist & playlist update failed count = %d", + demux->client->update_failed_count); + continue; + } else { + GST_ERROR_OBJECT (demux, "Could not update playlist.."); + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, ("Could not update playlist..."), (NULL)); + goto exit; + } + } + } + } + } + } + } + +exit: + GST_WARNING_OBJECT (demux, "Going to PAUSE download task from self"); + gst_task_pause (demux->download_task); + return; +} + +static void +gst_hlsdemux2_calculate_popped_duration (GstHLSDemux2 *demux, GstHLSDemux2Stream *stream, + GstBuffer *outbuf) +{ + gpointer data = NULL; + gint i = g_queue_get_length (stream->queue); + + /* get the buffer with valid timestamp from tail of the queue */ + while (i > 1) { + data = g_queue_peek_nth (stream->queue, (i - 1)); + if (data == NULL) { + return; + } + + if (GST_IS_BUFFER ((GstBuffer *)data)) { + if (GST_BUFFER_TIMESTAMP_IS_VALID(data)) { + /* found valid timestamp & stop searching */ + GST_LOG_OBJECT (stream->pad, "data found buffer with valid ts = %"GST_TIME_FORMAT " len = %d & i = %d", + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(data)), g_queue_get_length (stream->queue), i); + break; + } + } + i--; + } + + if (data == NULL) { + return; + } + + if (GST_BUFFER_TIMESTAMP (outbuf) <= GST_BUFFER_TIMESTAMP ((GstBuffer *)data)) { + stream->cached_duration = GST_BUFFER_TIMESTAMP ((GstBuffer *)data) - GST_BUFFER_TIMESTAMP (outbuf); + GST_DEBUG_OBJECT (stream->pad, "cache duration in popping : %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->cached_duration)); + } else { + GST_ERROR_OBJECT (stream->pad, "Wrong order.. not calculating"); + } + + return; +} + +static void +gst_hlsdemux2_push_loop (GstHLSDemux2Stream *stream) +{ + GstHLSDemux2 *demux = stream->parent; + GstFlowReturn fret = GST_FLOW_OK; + gpointer data = NULL; + + // TODO: need to take care of EOS handling.... + + if (demux->cancelled || demux->flushing) + goto error; + + g_mutex_lock (stream->queue_lock); + + if (g_queue_is_empty (stream->queue)) { + GST_LOG_OBJECT (stream->pad,"queue is empty wait till, some buffers are available..."); + g_cond_wait (stream->queue_empty, stream->queue_lock); + GST_LOG_OBJECT (stream->pad,"Received queue empty signal..."); + } + + if (demux->cancelled || demux->flushing) { + GST_ERROR_OBJECT (stream->pad, "Shutting down push loop"); + g_mutex_unlock (stream->queue_lock); + goto error; + } + + if (g_queue_is_empty (stream->queue)) { + GST_ERROR_OBJECT (stream->pad, "Queue empty undesired...."); + g_mutex_unlock (stream->queue_lock); + goto error; + } + + data = g_queue_pop_head (stream->queue); + if (!data) { + GST_ERROR_OBJECT (stream->pad, "Received null data..."); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Unhandled GstObjectType"), (NULL)); + g_mutex_unlock (stream->queue_lock); + goto error; + } + + /* Calculate duration only when + * 1. popped obj is a buffer and + * 2. buffer has valid ts and + * 3. queue length > 1 + */ + + if (GST_IS_BUFFER(data) && + GST_BUFFER_TIMESTAMP_IS_VALID(data) ) { + + /* calculate buffering duration */ + gst_hlsdemux2_calculate_popped_duration (demux, stream, data); + + if (stream->cached_duration < 0) { + GST_WARNING_OBJECT (stream->pad, "Wrong cached duration in push_loop...\n"); + stream->cached_duration = 0; + } + } + + g_mutex_unlock (stream->queue_lock); + + if (GST_IS_EVENT(data)) { + GstEvent *event = (GstEvent *)data; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: { + GstFormat format; + gboolean update; + gdouble rate, arate; + gint64 start, stop, pos; + gst_event_parse_new_segment_full(event, &update, &rate, &arate, &format, &start, &stop, &pos); + GST_INFO_OBJECT (stream->pad, "Pushing newsegment to downstream : rate = %0.3f, start = %"GST_TIME_FORMAT + ", stop = %"GST_TIME_FORMAT, rate, GST_TIME_ARGS(start), GST_TIME_ARGS(stop)); + } + break; + case GST_EVENT_EOS: + GST_INFO_OBJECT (stream->pad, "going to push eos event to downstream"); + stream->eos = TRUE; + break; + default: + GST_ERROR_OBJECT (stream->pad, "not handled event...still pushing event"); + break; + } + if (!gst_pad_push_event (stream->pad, event)) { + GST_ERROR_OBJECT (demux, "failed to push newsegment event"); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("failed to push '%s' event", GST_EVENT_TYPE_NAME(event)), (NULL)); + goto error; + } + + GST_INFO_OBJECT (stream->pad, "Pushed '%s' event...", GST_EVENT_TYPE_NAME(event)); + + if (stream->eos) { + //gst_element_post_message (GST_ELEMENT_CAST (stream->sink), gst_message_new_eos (GST_OBJECT_CAST(stream->sink))); + GST_INFO_OBJECT (stream->pad, "Pausing the task"); + goto error; + } + } else if (GST_IS_BUFFER(data)) { + GstCaps *temp_caps = NULL; + g_cond_signal (stream->queue_full); + + GST_DEBUG_OBJECT (stream->pad, "Pushing buffer : size = %d, ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_BUFFER_SIZE(data), GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(data)), GST_TIME_ARGS(GST_BUFFER_DURATION(data))); + + temp_caps = gst_buffer_get_caps((GstBuffer *)data); + temp_caps = gst_caps_make_writable (temp_caps); + gst_caps_set_simple (temp_caps,"hls_streaming", G_TYPE_BOOLEAN, TRUE, NULL); + gst_buffer_set_caps((GstBuffer *)data, temp_caps); + gst_caps_unref(temp_caps); + + /* push data to downstream*/ + fret = gst_pad_push (stream->pad, data); + if (fret != GST_FLOW_OK) { + GST_ERROR_OBJECT (stream->pad, "failed to push data, reason : %s", gst_flow_get_name (fret)); + goto error; + } + } else { + GST_ERROR_OBJECT (stream->pad, "unhandled object type..still pushing"); + //GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Unhandled GstObjectType"), (NULL)); + //return; + gst_object_unref (data); + } + + if (demux->cancelled || demux->flushing) { + goto error; + } + + return; + +error: + { + GST_WARNING_OBJECT (stream->pad, "Pausing the push task..."); + + if (fret < GST_FLOW_UNEXPECTED) { + GST_ERROR_OBJECT (stream->pad, "Crtical error in push loop...."); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("failed to push. reason - %s", gst_flow_get_name (fret)), (NULL)); + } + + gst_pad_pause_task (stream->pad); + return; + } +} + +// TODO: need to review change_playlist API + +static gboolean +gst_hlsdemux2_schedule (GstHLSDemux2 * demux) +{ + gfloat update_factor; + gint count; + GstClockTime last_frag_duration; + + /* As defined in §6.3.4. Reloading the Playlist file: + * "If the client reloads a Playlist file and finds that it has not + * changed then it MUST wait for a period of time before retrying. The + * minimum delay is a multiple of the target duration. This multiple is + * 0.5 for the first attempt, 1.5 for the second, and 3.0 thereafter." + */ + count = demux->client->update_failed_count; + if (count < 3) + update_factor = update_interval_factor[count]; + else + update_factor = update_interval_factor[3]; + + /* schedule the next update using the target duration field of the + * playlist */ + demux->next_update.tv_sec = 0; + demux->next_update.tv_usec = 0; + + last_frag_duration = gst_m3u8_client_get_last_fragment_duration (demux->client); + + GST_DEBUG_OBJECT (demux, "last fragment duration = %"GST_TIME_FORMAT" and next update = %"GST_TIME_FORMAT, + GST_TIME_ARGS(last_frag_duration), GST_TIME_ARGS(GST_TIME_AS_USECONDS(last_frag_duration) * 1000)); + + g_get_current_time (&demux->next_update); + + //g_time_val_add (&demux->next_update, last_frag_duration / GST_SECOND * G_USEC_PER_SEC * update_factor); + g_time_val_add (&demux->next_update, GST_TIME_AS_USECONDS(last_frag_duration)* update_factor); + + return TRUE; +} + +static void +gst_hlsdemux2_new_pad_added (GstElement *element, GstPad *srcpad, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GstPad *sinkpad = NULL; + GstCaps *caps = NULL; + HLSDemux2SinkBin *sinkbin = NULL; + gchar *demuxer_name = NULL; + gchar *src_pad_name = NULL; + + GST_INFO_OBJECT (demux, "received the src_pad '%s' from mpeg2ts demuxer...", GST_PAD_NAME(srcpad)); + + if (strstr (GST_PAD_NAME(srcpad), "private")) { + /* handle private image TAG */ + gst_hlsdemux2_handle_private_pad (demux, srcpad); + return; + } + + caps = GST_PAD_CAPS (srcpad); + demuxer_name = GST_ELEMENT_NAME (element); + + sinkbin = gst_hlsdemux2_create_stream (demux, demuxer_name, caps); + if (!sinkbin) { + return; + } + + sinkpad = gst_element_get_pad(GST_ELEMENT(sinkbin->sinkbin), "sink"); + if (!sinkpad) { + GST_ERROR_OBJECT (demux, "failed to get sinkpad from element - %s", GST_PAD_NAME(sinkbin->sinkbin)); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("could not get sink pad"), (NULL)); + return; + } + + /* link demuxer srcpad & sink element's sink pad */ + if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK) { + GST_ERROR_OBJECT (demux, "failed to link pad '%s' & sink's sink pad", GST_PAD_NAME(srcpad)); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("Could not link pads.."), (NULL)); + gst_object_unref(sinkpad); + return; + } + + if (sinkbin->parser) { + GST_INFO_OBJECT (demux, "linking parser"); + if (!gst_element_link_many (sinkbin->queue, sinkbin->parser, sinkbin->sink, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link sink bin elements"); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("Could not link pads.."), (NULL)); + gst_object_unref(sinkpad); + return; + } + } else { + if (!gst_element_link_many (sinkbin->queue, sinkbin->sink, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link sink bin elements"); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("Could not link pads.."), (NULL)); + gst_object_unref(sinkpad); + return; + } + } + demux->fdownloader->cur_stream_cnt++; + + src_pad_name = gst_pad_get_name(srcpad); + GST_INFO_OBJECT (demux, "succesfully linked pad (%s) with stream pad and current stream cnt = %d", + src_pad_name, demux->fdownloader->cur_stream_cnt); + g_free(src_pad_name); + src_pad_name = NULL; + + gst_element_set_state (GST_ELEMENT(sinkbin->sinkbin), GST_STATE_PLAYING); + + gst_object_unref (sinkpad); +} + +static gboolean +gst_hlsdemux2_check_stream_type_exist (GstHLSDemux2 *demux, HLSDEMUX2_STREAM_TYPE stream_type) +{ + int i = 0; + GstHLSDemux2Stream *stream = NULL; + + for (i = 0; i< HLSDEMUX2_STREAM_NUM; i++) { + stream = demux->streams[i]; + + if (stream && (stream->type == stream_type)) { + GST_INFO_OBJECT (demux, "stream type '%d' is already present", stream_type); + return TRUE; + } + } + + GST_INFO_OBJECT (demux, "received new stream type [%d]", stream_type); + + return FALSE; +} + +static HLSDemux2SinkBin * +gst_hlsdemux2_create_stream (GstHLSDemux2 *demux, gchar *demuxer_name, GstCaps *caps) +{ + GstHLSDemux2Stream *stream = NULL; + GstPad *sinkpad = NULL; + GstPad *linkpad = NULL; + HLSDEMUX2_STREAM_TYPE stream_type; + gchar *element_name = NULL; + gboolean stream_exist = FALSE; + gulong sink_probe; + HLSDemux2SinkBin *bin = NULL; + GstStructure *structure = NULL; + gchar *mime = NULL; + gchar *stream_name = NULL; + + stream = g_new0 (GstHLSDemux2Stream, 1); + + structure = gst_caps_get_structure (caps, 0); + + mime = gst_structure_get_name (structure); + + if (strstr (mime, "video")) { + stream_type = HLSDEMUX2_STREAM_VIDEO; + GST_DEBUG_OBJECT (demux, "Its a Multi-variant, Audio only stream.."); + demux->stream_config = HLSDEMUX2_MULTI_VARIANT; + stream_name = g_strdup ("video"); + } else if (strstr (mime, "audio")) { + stream_type = HLSDEMUX2_STREAM_AUDIO; + stream_name = g_strdup ("audio"); + } else if (strstr (mime, "subpicture")) { + stream_type = HLSDEMUX2_STREAM_TEXT; + stream_name = g_strdup ("subpicture"); + } else if (strstr (mime, "private")) { + stream_type = HLSDEMUX2_STREAM_PRIVATE; + stream_name = g_strdup ("private"); + } else { + GST_ELEMENT_ERROR (demux, CORE, PAD, ("Received unknown named pad"), (NULL)); + goto error; + } + + stream_exist = gst_hlsdemux2_check_stream_type_exist (demux, stream_type); + + if (!stream_exist) { + stream = g_new0 (GstHLSDemux2Stream, 1); + gst_hlsdemux2_stream_init (demux, stream, stream_type, stream_name, caps); + + if (!stream->is_linked) { + goto error; + } + } else { + int i = 0; + + for (i = 0; i< HLSDEMUX2_STREAM_NUM; i++) { + stream = demux->streams[i]; + if (stream && (stream->type == stream_type)) { + GST_INFO_OBJECT (demux, "found the stream - '%d'", stream->type); + break; + } + } + } + + if (!stream) + goto error; + + bin = g_new0 (HLSDemux2SinkBin, 1); + + element_name = g_strdup_printf("%s-%s", stream_name, "bin"); + bin->sinkbin = GST_BIN(gst_bin_new (element_name)); + if (!bin->sinkbin) { + GST_ERROR_OBJECT (demux, "failed to create sink bin - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("failed to create bin - %s", element_name), (NULL)); + goto error; + } + g_free (element_name); + element_name = NULL; + + /* create queue element */ + element_name = g_strdup_printf("%s-%s", stream_name, "queue"); + bin->queue = gst_element_factory_make ("queue", element_name); + if (!bin->queue) { + GST_ERROR_OBJECT (demux, "failed to create queue element - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("failed to create element - %s", element_name), (NULL)); + goto error; + } + g_free (element_name); + element_name = NULL; + + if ((stream->type == HLSDEMUX2_STREAM_AUDIO) && + g_strstr_len(demuxer_name, strlen(demuxer_name), "id3")) { + /* create parser element */ + + GST_INFO_OBJECT (stream->pad, "demuxer is id3demuxer, so create audio parser..."); + + element_name = g_strdup_printf("%s-%s", stream_name, "parse"); + bin->parser = gst_element_factory_make ("aacparse", element_name); + if (!bin->parser) { + GST_ERROR_OBJECT (demux, "failed to create parser element - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("failed to create element - %s", element_name), (NULL)); + goto error; + } + g_free (element_name); + element_name = NULL; + gst_bin_add (GST_BIN (bin->sinkbin), bin->parser); + } + + /* create sink element */ + element_name = g_strdup_printf("%s-%s", stream_name, "sink"); + bin->sink = gst_element_factory_make ("appsink", element_name); + if (!bin->sink) { + GST_ERROR_OBJECT (demux, "failed to create sink element - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("failed to create element - %s", element_name), (NULL)); + goto error; + } + + g_object_set (G_OBJECT (bin->sink), "sync", FALSE, "emit-signals", TRUE, NULL); + g_signal_connect (bin->sink, "new-buffer", G_CALLBACK (gst_hlsdemux2_downloader_new_buffer), stream); + g_signal_connect (bin->sink, "eos", G_CALLBACK (gst_hlsdemux2_downloader_eos), stream); + + gst_bin_add_many (GST_BIN (bin->sinkbin), bin->queue, bin->sink, NULL); + + gst_bin_add (GST_BIN (demux->fdownloader->pipe), GST_ELEMENT(bin->sinkbin)); + + /* set queue element to PLAYING state */ + gst_element_set_state (GST_ELEMENT(bin->sinkbin), GST_STATE_READY); + + sinkpad = gst_element_get_pad(bin->sink, "sink"); + if (!sinkpad) { + GST_ERROR_OBJECT (demux, "failed to get sinkpad from element - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("Count not get sink pad"), (NULL)); + goto error; + } + g_free (element_name); + element_name = NULL; + + /* adding probe to get new segment events */ + sink_probe = gst_pad_add_event_probe (sinkpad, + G_CALLBACK (gst_hlsdemux2_sink_event_handler), stream); + + if (!stream_exist) { + /* add stream to stream_type list */ + demux->streams[stream_type] = stream; + demux->active_stream_cnt++; + GST_INFO_OBJECT (demux, "number of active streams = %d", demux->active_stream_cnt); + } + + linkpad = gst_element_get_pad(bin->queue, "sink"); + if (!linkpad) { + element_name = gst_element_get_name (bin->queue); + GST_ERROR_OBJECT (demux, "failed to get sinkpad from element - %s", element_name); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("could not get sink pad"), (NULL)); + goto error; + } + + gst_pad_set_active(linkpad,TRUE); + + gst_element_add_pad (GST_ELEMENT(bin->sinkbin), gst_ghost_pad_new ("sink", linkpad)); + + GST_INFO_OBJECT (stream->pad, "successfully created stream..."); + + demux->fdownloader->sinkbins = g_list_append (demux->fdownloader->sinkbins, bin); + + g_cond_signal (demux->post_msg_start); + + g_free (stream_name); + + return bin; + +error: + g_free (stream); + if (element_name) + g_free (element_name); + + if (stream_name) + g_free (stream_name); + + return NULL; +} + +static gboolean +gst_hlsdemux2_private_image_to_vid(gpointer user_data,GstBuffer *image_buffer) +{ + GstHLSDemux2PvtStream *pvt_stream = (GstHLSDemux2PvtStream *) user_data; + GstHLSDemux2 *demux = (GstHLSDemux2 *) pvt_stream->parent; + GstPad *pad, *srcpad; + GstElement *appsrc, *appsink, *dec, *videorate, *capsfilter, *enc; + GstCaps *scale_caps, *image_caps; + GstBus * bus; + GstFlowReturn fret = GST_FLOW_OK; + + gchar *image_header = g_strndup((gchar *) image_buffer->data, 8); + if(!((image_header[0] == 0xFF) && (image_header[1] == 0xD8))) { + GST_WARNING_OBJECT(demux, "Not a valid JPEG image header : %d %d",image_header[0],image_header[1]); + goto error; + } + + GST_DEBUG_OBJECT(demux, "IMAGE DATA [%d,%d]",pvt_stream->image_buffer->data[0],pvt_stream->image_buffer->data[1]); + + GST_INFO_OBJECT (demux, "Creating jpeg->h264 conversion pipeline"); + + pvt_stream->convert_pipe = gst_pipeline_new ("jpegdec-pipe"); + if (!pvt_stream->convert_pipe) { + GST_ERROR_OBJECT (demux, "failed to create pipeline"); + goto error; + } + + bus = gst_pipeline_get_bus (GST_PIPELINE (pvt_stream->convert_pipe)); + gst_bus_add_watch (bus, (GstBusFunc)gst_hlsdemux2_imagebuf_pipe_bus_cb, user_data); + gst_object_unref (bus); + + appsrc = gst_element_factory_make ("appsrc", "imgbuf-src"); + if (!appsrc) { + GST_ERROR_OBJECT (demux, "failed to create appSrc element"); + goto error; + } + + appsink = gst_element_factory_make ("appsink", "imgbuf-sink"); + if (!appsink) { + GST_ERROR_OBJECT (demux, "failed to create appSink element"); + goto error; + } + + dec = gst_element_factory_make ("jpegdec", "jpeg-decoder"); + if (!dec) { + GST_ERROR_OBJECT (demux, "failed to create jpeg-decoder element"); + goto error; + } + + videorate = gst_element_factory_make ("videorate", "video-rate"); + if (!videorate) { + GST_ERROR_OBJECT (demux, "failed to create ffmpeg-converter element"); + goto error; + } + + capsfilter = gst_element_factory_make ("capsfilter", "caps-filter"); + if (!capsfilter) { + GST_ERROR_OBJECT (demux, "failed to create caps-filter element"); + goto error; + } + + scale_caps = gst_caps_new_simple ("video/x-raw-yuv", + "width" , G_TYPE_INT, 480, + "height", G_TYPE_INT, 270, + "framerate",GST_TYPE_FRACTION,30,1, + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('I','4','2','0'), + NULL); + g_object_set (capsfilter, "caps", scale_caps, NULL); + + enc = gst_element_factory_make ("savsenc_h264", "h264-encoder"); + if (!enc) { + GST_ERROR_OBJECT (demux, "failed to create h264-encoder element"); + goto error; + } + + image_caps = gst_caps_new_simple ("image/jpeg", + "framerate", GST_TYPE_FRACTION, 30, 1, + "width", G_TYPE_INT, 480, + "height", G_TYPE_INT, 270, + NULL); + gst_buffer_set_caps (image_buffer, image_caps); + + gst_bin_add_many (GST_BIN (pvt_stream->convert_pipe), appsrc, dec, videorate, capsfilter, enc, appsink, NULL); + if (!gst_element_link_many ( appsrc, dec, videorate, capsfilter, enc, appsink, NULL)) { + GST_ERROR_OBJECT (demux, "failed to link elements..."); + goto error; + } + + pad = gst_element_get_static_pad(appsink,"sink"); + gst_pad_add_buffer_probe (pad,G_CALLBACK (gst_hlsdemux2_set_video_buffer), user_data); + gst_pad_add_event_probe (pad,G_CALLBACK (gst_hlsdemux2_done_video_buffer), user_data); + gst_element_set_state (pvt_stream->convert_pipe, GST_STATE_PLAYING); + + fret = gst_app_src_push_buffer ((GstAppSrc *)appsrc, pvt_stream->image_buffer); + if(fret != GST_FLOW_OK){ + GST_ERROR_OBJECT (demux, "Push Failed; Error: %s[%d]", gst_flow_get_name(fret),fret); + goto error; + } + + fret = gst_app_src_end_of_stream ((GstAppSrc *)appsrc); + if(fret != GST_FLOW_OK){ + GST_ERROR_OBJECT (demux, "Push EOS Failed; Error: %s[%d]", gst_flow_get_name(fret),fret); + goto error; + } + g_free(image_header); + image_header = NULL; + return TRUE; + + error: + GST_ERROR_OBJECT (demux, "ERROR ERROR ERROR: tut tut tut "); + g_free(image_header); + image_header = NULL; + return FALSE; +} + +static gboolean +gst_hlsdemux2_push_dummy_data (GstHLSDemux2Stream *stream) +{ + GstHLSDemux2 *demux = stream->parent; + struct stat stat_results; + GstBuffer *buf = NULL; + gint iread = 0; + gint64 percent = 0; + GstCaps *caps = NULL; + GstBuffer *pushbuf = NULL; + GstClockTime stop = GST_CLOCK_TIME_NONE; + GstClockTime next_ts = GST_CLOCK_TIME_NONE; + gboolean first_frame = TRUE; + gint n = 0; + gboolean convert_success = TRUE; + GstHLSDemux2Stream *cur_stream = NULL; + int dummy_fp = -1; + GstClockTime cdisc = 0; + + GST_INFO_OBJECT (demux, "START dummy video data thread"); + + /* Read dummy frame */ + if(demux->has_image_buffer) { + GstHLSDemux2PvtStream *pvt_stream = demux->private_stream; + + g_mutex_lock (pvt_stream->img_load_lock); + if(pvt_stream->got_img_buffer == FALSE) + g_cond_wait(pvt_stream->img_load_cond, pvt_stream->img_load_lock); + g_mutex_unlock (pvt_stream->img_load_lock); + + GST_LOG_OBJECT (demux, "prev_image_buffer = %p and image_buffer = %p", + demux->prev_image_buffer, pvt_stream->image_buffer); + + /* check whether it is same as previous image */ + if (demux->prev_video_buffer && demux->prev_image_buffer && + (GST_BUFFER_SIZE(demux->prev_image_buffer) == GST_BUFFER_SIZE(pvt_stream->image_buffer))) { + if (!memcmp (GST_BUFFER_DATA(demux->prev_image_buffer), GST_BUFFER_DATA(pvt_stream->image_buffer), + GST_BUFFER_SIZE(pvt_stream->image_buffer))) { + GST_INFO_OBJECT (demux, "current & previous embedded images are same..no need to prepare video buffer"); + gst_buffer_unref(pvt_stream->image_buffer); + pvt_stream->image_buffer = NULL; + buf = gst_buffer_copy(demux->prev_video_buffer); + goto start_push; + } + } + + if (demux->prev_image_buffer) { + gst_buffer_unref (demux->prev_image_buffer); + } + demux->prev_image_buffer = gst_buffer_copy(pvt_stream->image_buffer); + + g_mutex_lock (pvt_stream->convert_lock); + convert_success = gst_hlsdemux2_private_image_to_vid(pvt_stream, pvt_stream->image_buffer); + if(convert_success) + g_cond_wait(pvt_stream->convert_cond, pvt_stream->convert_lock); + g_mutex_unlock (pvt_stream->convert_lock); + + if(pvt_stream->convert_pipe){ + pvt_stream->convert_pipe = NULL; + gst_object_unref(pvt_stream->convert_pipe); + } + + if (demux->prev_video_buffer) { + gst_buffer_unref (demux->prev_video_buffer); + demux->prev_video_buffer = NULL; + } + + if(convert_success){ + buf = pvt_stream->video_buffer; + pvt_stream->video_buffer = NULL; + demux->prev_video_buffer = gst_buffer_copy(buf); + GST_DEBUG_OBJECT (demux, "Set Image from buffer - success [%d,%d]", buf->data[0], buf->data[1]); + GST_INFO_OBJECT (demux, "Going to show embedded image"); + } + } + + //load black frame if error occured while creating video buffer. + if(demux->has_image_buffer == FALSE || convert_success == FALSE) + { + GST_INFO_OBJECT (demux, "Going to show fixed image"); + dummy_fp = open (PREDEFINED_VIDEO_FRAME_LOCATION, O_RDONLY); + if (dummy_fp < 0) { + GST_ERROR_OBJECT (stream->pad, "failed to open dummy data file : %s...", PREDEFINED_VIDEO_FRAME_LOCATION); + GST_ELEMENT_ERROR (demux, RESOURCE, OPEN_READ, ("Failed open file '%s' for reading. reason : %s",PREDEFINED_VIDEO_FRAME_LOCATION ,g_strerror(errno)), (NULL)); + goto quit; + } + + GST_LOG_OBJECT (stream->pad, "opened dummy video file %s succefully...", PREDEFINED_VIDEO_FRAME_LOCATION); + + if (fstat (dummy_fp, &stat_results) < 0) { + GST_ERROR_OBJECT (stream->pad, "failed to get stats of a file..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("Failed get stats. reason : %s", g_strerror(errno)), (NULL)); + close (dummy_fp); + goto quit; + } + + GST_LOG_OBJECT (stream->pad, "size of the dummy file = %d\n", stat_results.st_size); + + buf = gst_buffer_new_and_alloc (stat_results.st_size); + if (!buf) { + GST_ERROR_OBJECT (stream->pad, "failed to allocate memory..."); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + close (dummy_fp); + goto quit; + } + + iread = read (dummy_fp, GST_BUFFER_DATA (buf), stat_results.st_size); + + close (dummy_fp); + } + +start_push: + + for (n = 0; n < HLSDEMUX2_STREAM_NUM; n++) { + cur_stream = demux->streams[n]; + + if (cur_stream && (stream != cur_stream)) { + cdisc = cur_stream->cdisc; + } + } + + stream->nts = stop = stream->fts + demux->cfrag_dur; + stream->total_disc += cdisc; + stream->cdisc = cdisc; + next_ts = stream->fts; + + GST_INFO_OBJECT (stream->pad, "Dummy video start_ts = %"GST_TIME_FORMAT" and stop_ts = %"GST_TIME_FORMAT, + GST_TIME_ARGS(stream->fts), GST_TIME_ARGS(stop)); + + /* set caps on buffer */ + caps = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "nal", + NULL); + + gst_buffer_set_caps (buf, caps); + + if (stream->need_newsegment) { + GST_INFO_OBJECT (stream->pad, "need to send new segment event based on audio"); + g_queue_push_tail (stream->queue, (demux->streams[HLSDEMUX2_STREAM_AUDIO])->newsegment); + stream->need_newsegment = FALSE; + } + + while (TRUE) { + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (stream->pad, "Stopping dummy push task..."); + goto quit; + } + + g_mutex_lock (stream->queue_lock); + + pushbuf = gst_buffer_copy (buf); + gst_buffer_set_caps (pushbuf, caps); + GST_BUFFER_TIMESTAMP (pushbuf) = stream->lts = next_ts; + GST_BUFFER_DURATION (pushbuf) = GST_CLOCK_TIME_NONE; + + if (first_frame) { + GST_BUFFER_FLAG_SET (pushbuf, GST_BUFFER_FLAG_DISCONT); + first_frame = FALSE; + } + + GST_LOG_OBJECT (stream->pad, "Pushing dummy buffer : ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (pushbuf))); + + /* calculate cached duration */ + gst_hlsdemux2_calculate_pushed_duration (demux, stream, pushbuf); + + if (stream->cached_duration >= 0) { + + percent = (stream->cached_duration * 100) / demux->total_cache_duration; + GST_LOG_OBJECT (stream->pad, "percent done = %"G_GINT64_FORMAT, percent); + + if (percent > 100) { + guint64 overall_percent = 0; + + g_mutex_lock (demux->buffering_lock); + overall_percent = demux->percent; + g_mutex_unlock (demux->buffering_lock); + + if (overall_percent < 100) { /* otherwise, may cause blocking while buffering*/ + GST_DEBUG_OBJECT (stream->pad, "@@@@@@@@@@@ queue should not go to wait now @@@@@@@@"); + } else { + /* update buffering & wait if space is not available */ + GST_DEBUG_OBJECT (stream->pad, "Reached more than 100 percent, queue full & wait till free"); + g_cond_wait(stream->queue_full, stream->queue_lock); + GST_DEBUG_OBJECT (stream->pad,"Received signal to add more data..."); + } + } + } + + g_cond_signal (stream->queue_empty); + + g_mutex_unlock (stream->queue_lock); + + /* calculate next timestamp */ + next_ts += HLS_DEFAULT_FRAME_DURATION; + + if (next_ts > stop) { + GST_DEBUG_OBJECT (stream->pad, "Reached Endof the fragment ....\n\n"); + break; + } + } + + if (demux->end_of_playlist) { + /* end of playlist, push EOS to queue */ + GST_INFO_OBJECT (stream->pad, "pushing EOS event to stream's queue"); + g_queue_push_tail (stream->queue, gst_event_new_eos ()); + } + +#ifdef LATEST_AV_SYNC + if (demux->is_live) { + stream->total_stream_time += (stream->lts - stream->fts); + } else { + stream->total_stream_time += (stream->lts - stream->fts + HLS_DEFAULT_FRAME_DURATION); + stream->total_stream_time = stream->nts; + } +#else + stream->total_stream_time += (stream->lts - stream->fts); +#endif + + GST_DEBUG_OBJECT (stream->pad, "---------------- TS VALUES ----------------"); + GST_DEBUG_OBJECT (stream->pad, "valid start ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->fts)); + GST_DEBUG_OBJECT (stream->pad, "valid end ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->lts)); + GST_DEBUG_OBJECT (stream->pad, "fragment duration received = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->lts - stream->fts)); + GST_DEBUG_OBJECT (stream->pad, "total stream time = %"GST_TIME_FORMAT,GST_TIME_ARGS(stream->total_stream_time)); + GST_DEBUG_OBJECT (stream->pad, "total disc time = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->total_disc)); + GST_DEBUG_OBJECT (stream->pad, "next expected start ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->nts)); + GST_DEBUG_OBJECT (stream->pad, "---------------- DUMP VALUES END----------------"); + + stream->apply_disc = FALSE; + stream->fts = GST_CLOCK_TIME_NONE; + stream->valid_fts_rcvd = FALSE; + stream->prev_nts = stream->nts; + +quit: + { + GST_INFO_OBJECT (stream->pad, "Stopping dummy data thread..."); + gst_buffer_unref (buf); + stream->dummy_data_thread = NULL; + return TRUE; + } + +} + +static gboolean +gst_hlsdemux2_sink_event_handler (GstPad * pad, GstEvent * event, gpointer data) +{ + GstHLSDemux2Stream *stream = (GstHLSDemux2Stream *)data; + GstHLSDemux2 *demux = stream->parent; + + if ((GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) && stream->need_newsegment) { + GstEvent *newsegment = NULL; + GstFormat format; + gboolean update; + gdouble rate, arate; + gint64 start, stop, pos; + + newsegment = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, demux->ns_start, -1, demux->ns_start); + + GST_INFO_OBJECT (stream->pad, "Received NEWSEGMENT event..."); + + g_mutex_lock (stream->queue_lock); + + gst_event_parse_new_segment_full(event, &update, &rate, &arate, &format, &start, &stop, &pos); + + if (!GST_CLOCK_TIME_IS_VALID(stream->base_ts)) { + GST_INFO_OBJECT (stream->pad, "storing stream's base timestamp = %"GST_TIME_FORMAT, GST_TIME_ARGS(start)); + stream->base_ts = start; + } + + g_queue_push_tail (stream->queue, newsegment); + + GST_INFO_OBJECT (stream->pad, "Pushed new segment event with start = %"GST_TIME_FORMAT" to queue..", + GST_TIME_ARGS(demux->ns_start)); + + g_mutex_unlock (stream->queue_lock); + + stream->need_newsegment = FALSE; + stream->prev_nts = demux->ns_start; + + if (stream->newsegment) + gst_event_unref (stream->newsegment); + + stream->newsegment = gst_event_copy (newsegment); + } + return TRUE; +} + +static gboolean +hlsdemux2_HTTP_repeat_request (GstHLSDemux2 *demux, gchar *element_name) +{ + if (g_strrstr(element_name, "fragurisrc")) { + guint idx = 0; + GstBuffer *buf = NULL; + + /* clear stream queues if we already downloaded some data */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *stream = demux->streams[idx]; + if (stream ) { + while (!g_queue_is_empty (stream->downloader_queue)) { + buf = g_queue_pop_head (stream->downloader_queue); + gst_buffer_unref (buf); + } + GST_LOG_OBJECT (stream->pad, "cleared stream download queue..."); + g_queue_clear (stream->downloader_queue); + } + } + + /* request the same fragment again */ + if (demux->fragCookie) + g_strfreev (demux->fragCookie); + + g_object_get (demux->fdownloader->urisrc, "cookies", &demux->fragCookie, NULL); + GST_DEBUG_OBJECT (demux, "Got cookies after FRAGMENT download : %s", demux->fragCookie ? *(demux->fragCookie) : NULL); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download fragment AGAIN: %s", demux->frag_uri); + + gst_hlsdemux2_destroy_fragment_download (demux); + + if (!gst_hlsdemux2_create_fragment_download (demux, demux->frag_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + /* download rate calculation : note down start time*/ + demux->fdownloader->download_start_ts = gst_util_get_timestamp(); + + gst_element_set_state (demux->fdownloader->pipe, GST_STATE_PLAYING); + + return TRUE; + } else if (g_strrstr(element_name, "playlisturisrc")) { + + if (demux->playlistCookie) + g_strfreev (demux->playlistCookie); + + g_object_get (demux->pldownloader->urisrc, "cookies", &demux->playlistCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after PLAYLIST download : %s", demux->playlistCookie ? *(demux->playlistCookie) : NULL); + + gst_hlsdemux2_destroy_playlist_download (demux); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download playlist AGAIN: %s", demux->playlist_uri); + if (!gst_hlsdemux2_create_playlist_download (demux, demux->playlist_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + gst_element_set_state (demux->pldownloader->pipe, GST_STATE_PLAYING); + return TRUE; + } else if (g_strrstr(element_name, "keyurisrc")) { + + if (demux->keyCookie) + g_strfreev (demux->keyCookie); + + g_object_get (demux->kdownloader->urisrc, "cookies", &demux->keyCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after KEY download : %s", demux->keyCookie ? *(demux->keyCookie) : NULL); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download key AGAIN: %s", demux->key_uri); + + gst_hlsdemux2_destroy_key_download (demux); + + if (!gst_hlsdemux2_create_key_download (demux, demux->key_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + gst_element_set_state (demux->kdownloader->pipe, GST_STATE_PLAYING); + return TRUE; + } + + return FALSE; +} + + +static gboolean +hlsdemux2_HTTP_time_out (GstHLSDemux2 *demux, gchar *element_name) +{ + if (g_strrstr(element_name, "fragurisrc")) { + /* as it is because of timeout, there is no point in requesting the same fragment again, so request next one */ + GST_INFO_OBJECT (demux, "signalling fragment downloader to get next fragment..."); + demux->fdownloader->get_next_frag = TRUE; + g_cond_signal (demux->fdownloader->cond); + return TRUE; + } else if (g_strrstr(element_name, "playlisturisrc")) { + + if (demux->playlistCookie) + g_strfreev (demux->playlistCookie); + + g_object_get (demux->pldownloader->urisrc, "cookies", &demux->playlistCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after PLAYLIST download : %s", demux->playlistCookie ? *(demux->playlistCookie) : NULL); + + gst_hlsdemux2_destroy_playlist_download (demux); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download playlist AGAIN: %s", demux->playlist_uri); + if (!gst_hlsdemux2_create_playlist_download (demux, demux->playlist_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + gst_element_set_state (demux->pldownloader->pipe, GST_STATE_PLAYING); + return TRUE; + } else if (g_strrstr(element_name, "keyurisrc")) { + + if (demux->keyCookie) + g_strfreev (demux->keyCookie); + + g_object_get (demux->kdownloader->urisrc, "cookies", &demux->keyCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after KEY download : %s", demux->keyCookie ? *(demux->keyCookie) : NULL); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download key AGAIN: %s", demux->key_uri); + + gst_hlsdemux2_destroy_key_download (demux); + + if (!gst_hlsdemux2_create_key_download (demux, demux->key_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + gst_element_set_state (demux->kdownloader->pipe, GST_STATE_PLAYING); + return TRUE; + } + + return FALSE; +} + +static gboolean +hlsdemux2_HTTP_not_found(GstHLSDemux2 *demux, gchar *element_name) +{ + if (g_strrstr(element_name, "fragurisrc")) { + if (demux->is_live) { + /* request next fragment url */ + GST_INFO_OBJECT (demux, "signalling fragment downloader to get next fragment..."); + demux->fdownloader->get_next_frag = TRUE; + g_cond_signal (demux->fdownloader->cond); + return TRUE; + } else { + +#if 0 // In future enable + GList *next_variant = NULL; + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_NO_RECOVERY) + demux->pldownloader->recovery_mode = HLSDEMUX2_DOWNWARD_RECOVERY; + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_DOWNWARD_RECOVERY) { + /* get next available lower variant */ + next_variant = gst_m3u8_client_get_next_lower_bw_playlist (demux->client); + if (!next_variant) { + GST_WARNING_OBJECT (demux, "no next lower variants.. Go Upward"); + demux->pldownloader->recovery_mode = HLSDEMUX2_UPWARD_RECOVERY; + } + } + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_UPWARD_RECOVERY) { + /* get next available lower variant */ + next_variant = gst_m3u8_client_get_next_higher_bw_playlist (demux->client); + if (!next_variant) { + GST_ERROR_OBJECT (demux, "no next higher variants.. need to exit"); + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; + return FALSE; + } + } + + /* change variant */ + GST_M3U8_CLIENT_LOCK (demux->client); + demux->client->main->current_variant = next_variant; + GST_M3U8_CLIENT_UNLOCK (demux->client); + + gst_m3u8_client_set_current (demux->client, next_variant->data); + + demux->fdownloader->get_next_frag = TRUE; + g_cond_signal (demux->fdownloader->cond); + + return TRUE; +#else + return FALSE; +#endif + } + } else if (g_strrstr(element_name, "playlisturisrc")) { + GList *next_variant = NULL; + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_NO_RECOVERY) + demux->pldownloader->recovery_mode = HLSDEMUX2_DOWNWARD_RECOVERY; + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_DOWNWARD_RECOVERY) { + /* get next available lower variant */ + next_variant = gst_m3u8_client_get_next_lower_bw_playlist (demux->client); + if (!next_variant) { + GST_WARNING_OBJECT (demux, "no next lower variants.. Go Upward"); + demux->pldownloader->recovery_mode = HLSDEMUX2_UPWARD_RECOVERY; + } + } + + if (demux->pldownloader->recovery_mode == HLSDEMUX2_UPWARD_RECOVERY) { + /* get next available lower variant */ + next_variant = gst_m3u8_client_get_next_higher_bw_playlist (demux->client); + if (!next_variant) { + GST_ERROR_OBJECT (demux, "no next higher variants.. need to exit"); + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; + return FALSE; + } + } + + /* change variant */ + GST_M3U8_CLIENT_LOCK (demux->client); + demux->client->main->current_variant = next_variant; + GST_M3U8_CLIENT_UNLOCK (demux->client); + + gst_m3u8_client_set_current (demux->client, next_variant->data); + + /* get new playlist uri */ + demux->playlist_uri = g_strdup (gst_m3u8_client_get_current_uri (demux->client)); + + if (demux->playlistCookie) + g_strfreev (demux->playlistCookie); + + g_object_get (demux->pldownloader->urisrc, "cookies", &demux->playlistCookie, NULL); + + GST_DEBUG_OBJECT (demux, "Got cookies after PLAYLIST download : %s", demux->playlistCookie ? *(demux->playlistCookie) : NULL); + + gst_hlsdemux2_destroy_playlist_download (demux); + + GST_INFO_OBJECT (demux, "========>>>>>Going to download recovery playlist : %s", demux->playlist_uri); + if (!gst_hlsdemux2_create_playlist_download (demux, demux->playlist_uri)) { + GST_ERROR_OBJECT (demux, "failed to create download pipeline"); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("Failed to create pipeline"), (NULL)); + return FALSE; + } + + gst_element_set_state (demux->pldownloader->pipe, GST_STATE_PLAYING); + + return TRUE; + + } else if (g_strrstr(element_name, "keyurisrc")) { + /* treating it as non-recoverable error */ + return FALSE; + } + + return FALSE; +} + +static gboolean +gst_hlsdemux2_handle_HTTP_error (GstHLSDemux2 *demux, GError *error, gchar *element_name) { + guint i = 0; + guint n_http_errors = sizeof (http_errors) / sizeof (http_errors[0]); + + if(!error) + return HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE; + for (i = 0; i < n_http_errors; i++) { + if (G_LIKELY (!strncmp(error->message, http_errors[i].error_phrase, strlen(error->message)))) { + if (http_errors[i].handle_error) { + return http_errors[i].handle_error (demux, element_name); + } + return http_errors[i].error_type; + } + } + return HLSDEMUX2_HTTP_ERROR_NONRECOVERABLE; +} + +static gboolean +gst_hlsdemux2_fragment_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GError *error = NULL; + gchar* debug = NULL; + gboolean bret = TRUE; + GstMessage *err_msg = NULL; + gchar *ele_name = gst_element_get_name (GST_MESSAGE_SRC (msg)); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + GST_INFO_OBJECT (demux, "received EOS on download pipe from '%s'..", ele_name); + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; + g_cond_signal(demux->fdownloader->cond); + break; + } + case GST_MESSAGE_ERROR: { + GST_ERROR_OBJECT (demux, "Error from %s\n", ele_name); + + gst_message_parse_error( msg, &error, &debug ); + + demux->fdownloader->error_rcvd = TRUE; + + if (error) + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: error->message = %s and error->code = %d", error->message); + + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: debug = %s", debug); + + if (g_strrstr(ele_name, "fragurisrc") && !demux->cancelled && demux->soup_request_fail_cnt) { + + /* flush the error msgs pending.. as we are going to create new pipeline */ + gst_bus_set_flushing (bus, TRUE); + + bret = gst_hlsdemux2_handle_HTTP_error (demux, error, ele_name); + if (!bret) + goto post_error; + else { + demux->soup_request_fail_cnt--; + GST_WARNING_OBJECT (demux, "HTTP error count remaining = %d", demux->soup_request_fail_cnt); + goto exit; + } + } + +post_error: + + GST_ERROR_OBJECT (demux, "posting error from fragment download callback"); + + err_msg = gst_message_new_error (GST_OBJECT(demux), error, debug); + if (!gst_element_post_message (GST_ELEMENT(demux), err_msg)) { + GST_ERROR_OBJECT (demux, "failed to post error"); + bret = FALSE; + goto exit; + } + gst_hlsdemux2_stop (demux); + break; + } + case GST_MESSAGE_WARNING: { + gst_message_parse_warning(msg, &error, &debug); + GST_WARNING_OBJECT(demux, "warning : %s\n", error->message); + GST_WARNING_OBJECT(demux, "debug : %s\n", debug); + break; + } + case GST_MESSAGE_ELEMENT: { + const GstStructure *s = gst_message_get_structure (msg); + + if (gst_structure_has_name (s, "cookies")) { + const GValue *value; + gchar **cookies = NULL; + gchar *cookie = NULL; + value = gst_structure_get_value (s, "cookies"); + cookie = g_strdup_value_contents(value); + + cookies = &cookie; + GST_ERROR_OBJECT (demux, "received cookies from soup : %s", *cookies); + } + break; + } + default : { + break; + } + } + +exit: + if (debug) + g_free( debug); + + if (error) + g_error_free( error); + + if (!bret) + gst_hlsdemux2_stop (demux); + + g_free (ele_name); + + return bret; +} + +static GstBusSyncReply +gst_hlsdemux2_playlist_download_bus_sync_cb (GstBus * bus, GstMessage *msg, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GstBusSyncReply reply = GST_BUS_DROP; + + GError *error = NULL; + gchar *debug = NULL; + gboolean bret = TRUE; + GstMessage *err_msg = NULL; + gchar *ele_name = gst_element_get_name (GST_MESSAGE_SRC (msg)); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + GST_DEBUG_OBJECT (demux, "received EOS on playlist download pipe.."); + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; + g_mutex_lock (demux->pldownloader->lock); + g_cond_broadcast (demux->pldownloader->cond); + g_mutex_unlock (demux->pldownloader->lock); + break; + } + case GST_MESSAGE_ERROR: { + GST_ERROR_OBJECT (demux, "Error from %s element", ele_name); + + gst_message_parse_error( msg, &error, &debug ); + if (error) + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: error= %s", error->message); + + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: debug = %s", debug); + + if (g_strrstr(ele_name, "playlisturisrc") && !demux->cancelled && demux->soup_request_fail_cnt) { + + /* flush the error msgs pending.. as we are going to create new pipeline */ + gst_bus_set_flushing (bus, TRUE); + + bret = gst_hlsdemux2_handle_HTTP_error (demux, error, ele_name); + if (!bret) + goto post_error; + else { + demux->soup_request_fail_cnt--; + GST_WARNING_OBJECT (demux, "HTTP error count remaining = %d", demux->soup_request_fail_cnt); + goto exit; + } + } + +post_error: + + GST_ERROR_OBJECT (demux, "posting error from playlist callback..."); + + err_msg = gst_message_new_error (GST_OBJECT(demux), error, debug); + if (!gst_element_post_message (GST_ELEMENT(demux), err_msg)) { + GST_ERROR_OBJECT (demux, "failed to post error"); + bret = FALSE; + goto exit; + } + gst_hlsdemux2_stop (demux); + break; + } + case GST_MESSAGE_WARNING: { + gst_message_parse_warning(msg, &error, &debug); + if (error) + GST_WARNING_OBJECT(demux, "warning : %s\n", error->message); + GST_WARNING_OBJECT(demux, "debug : %s\n", debug); + break; + } + case GST_MESSAGE_ELEMENT: { + const GstStructure *s = gst_message_get_structure (msg); + + if (gst_structure_has_name (s, "cookies")) { + const GValue *value; + gchar **cookies = NULL; + gchar *cookie = NULL; + value = gst_structure_get_value (s, "cookies"); + cookie = g_strdup_value_contents(value); + + cookies = &cookie; + GST_ERROR_OBJECT (demux, "received cookies from soup : %s", *cookies); + } + break; + } + default : { + //GST_LOG_OBJECT(demux, "unhandled message : %s\n", gst_message_type_get_name (GST_MESSAGE_TYPE (msg))); + break; + } + } + +exit: + if (debug) + g_free( debug); + + if (error) + g_error_free( error); + + if (!bret) + gst_hlsdemux2_stop (demux); + + g_free (ele_name); + + return reply; +} + +static gboolean +gst_hlsdemux2_key_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GError *error = NULL; + gchar* debug = NULL; + gboolean bret = TRUE; + GstMessage *err_msg = NULL; + gchar *ele_name = gst_element_get_name (GST_MESSAGE_SRC (msg)); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + GST_DEBUG_OBJECT (demux, "received EOS on key download pipe.."); + demux->pldownloader->recovery_mode = HLSDEMUX2_NO_RECOVERY; + g_mutex_lock (demux->kdownloader->lock); + g_cond_signal (demux->kdownloader->cond); + g_mutex_unlock (demux->kdownloader->lock); + break; + } + case GST_MESSAGE_ERROR: { + GST_INFO_OBJECT (demux, "Error from %s element...", ele_name); + + gst_message_parse_error(msg, &error, &debug); + + if (error) + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: error= %s", error->message); + + GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: debug = %s", debug); + + if (g_strrstr(ele_name, "keyurisrc") && !demux->cancelled && demux->soup_request_fail_cnt) { + + /* flush the error msgs pending.. as we are going to create new pipeline */ + gst_bus_set_flushing (bus, TRUE); + + bret = gst_hlsdemux2_handle_HTTP_error (demux, error, ele_name); + if (!bret) + goto post_error; + else { + demux->soup_request_fail_cnt--; + GST_WARNING_OBJECT (demux, "HTTP error count remaining = %d", demux->soup_request_fail_cnt); + goto exit; + } + } + +post_error: + GST_ERROR_OBJECT (demux, "Error posting from key downloader..."); + err_msg = gst_message_new_error (GST_OBJECT(demux), error, debug); + + if (!gst_element_post_message (GST_ELEMENT(demux), err_msg)) { + GST_ERROR_OBJECT (demux, "failed to post error"); + gst_hlsdemux2_stop (demux); + bret = FALSE; + goto exit; + } + gst_hlsdemux2_stop (demux); + break; + } + case GST_MESSAGE_WARNING: { + gst_message_parse_warning(msg, &error, &debug); + if (error) + GST_WARNING_OBJECT(demux, "warning : %s\n", error->message); + GST_WARNING_OBJECT(demux, "debug : %s\n", debug); + break; + } + case GST_MESSAGE_ELEMENT: { + const GstStructure *s = gst_message_get_structure (msg); + + if (gst_structure_has_name (s, "cookies")) { + const GValue *value; + gchar **cookies = NULL; + gchar *cookie = NULL; + value = gst_structure_get_value (s, "cookies"); + cookie = g_strdup_value_contents(value); + + cookies = &cookie; + GST_ERROR_OBJECT (demux, "received cookies from soup : %s", *cookies); + } + break; + } + default : { + break; + } + } + +exit: + + if (debug) + g_free( debug); + + if (error) + g_error_free( error); + + if (!bret) + gst_hlsdemux2_stop (demux); + + g_free (ele_name); + + return bret; +} + +static void +gst_hlsdemux2_calculate_pushed_duration (GstHLSDemux2 *demux, GstHLSDemux2Stream *stream, + GstBuffer *inbuf) +{ + gint len = 0; + int qidx = 0; + gpointer data = NULL; + + // TODO: for all timestamps -1 case, we need to add max-bytes also + + g_queue_push_tail (stream->queue, inbuf); + + len = g_queue_get_length (stream->queue); + + /* peek the head buffer having valid timestamp to calculate cached duration */ + while (qidx < len) { + data = g_queue_peek_nth (stream->queue, qidx); + + if (GST_IS_BUFFER (data)) { + if (GST_BUFFER_TIMESTAMP_IS_VALID(data)) { + GST_LOG_OBJECT (stream->pad, "data found buffer with valid ts = %"GST_TIME_FORMAT " len = %d & qidx = %d", + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(data)), len, qidx); + break; + } + } + qidx++; + } + if(GST_IS_BUFFER (data)) { + if (GST_BUFFER_TIMESTAMP (inbuf) >= GST_BUFFER_TIMESTAMP ((GstBuffer *)data)) { + stream->cached_duration = GST_BUFFER_TIMESTAMP (inbuf) - GST_BUFFER_TIMESTAMP ((GstBuffer *)data); + GST_LOG_OBJECT (stream->pad, "len = %d, cached duration = %"GST_TIME_FORMAT, + g_queue_get_length (stream->queue), GST_TIME_ARGS(stream->cached_duration)); + } else { + GST_WARNING_OBJECT (stream->pad, "Wrong order.. not calculating"); + } + } + return; +} + +static gboolean +gst_hlsdemux2_alter_timestamps (GstHLSDemux2Stream *stream, GstBuffer *inbuf) +{ + GstHLSDemux2 *demux = stream->parent; + GstClockTime cts = 0; + + /* set discontinuity only when there is real fragment discontinuity */ + if (GST_BUFFER_IS_DISCONT(inbuf)) { + if (!stream->apply_disc) { + GST_BUFFER_FLAG_UNSET (inbuf, GST_BUFFER_FLAG_DISCONT); + GST_INFO_OBJECT (stream->pad, "unsetting discontinuity flag..."); + } + } + + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (inbuf))) { + if (GST_BUFFER_TIMESTAMP (inbuf) >= stream->base_ts) { + cts = GST_BUFFER_TIMESTAMP (inbuf) - stream->base_ts; + } else { + /* this buffer should be dropped at sink */ + GST_WARNING_OBJECT (stream->pad, "input timestamp [%"GST_TIME_FORMAT"] is less than base_ts [%"GST_TIME_FORMAT"]", + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (inbuf)), GST_TIME_ARGS(stream->base_ts)); + if (stream->type == HLSDEMUX2_STREAM_AUDIO) { + return FALSE; + } else { + GST_BUFFER_TIMESTAMP (inbuf) = GST_CLOCK_TIME_NONE; + return TRUE; + } + } + } else { + /* this buffer should be dropped at sink */ + GST_LOG_OBJECT (stream->pad, "invalid input timestamp (i.e. GST_CLOCK_TIME_NONE)"); +#ifdef LATEST_AV_SYNC + if (!demux->is_live && (stream->type == HLSDEMUX2_STREAM_AUDIO) && !stream->valid_fts_rcvd){ + GST_WARNING_OBJECT (stream->pad, "dropping invalid ts buffer, due to no valid first ts yet..."); + return FALSE; + } +#endif + return TRUE; + } + + /* handle discontinuity in stream */ + if(GST_CLOCK_TIME_IS_VALID (stream->nts) && (GST_CLOCK_DIFF(stream->nts,cts) > (2 * GST_SECOND)) && stream->apply_disc) { + GST_INFO_OBJECT (stream->pad, "cts = %"GST_TIME_FORMAT" and prev_next_ts = %"GST_TIME_FORMAT, + GST_TIME_ARGS(cts), GST_TIME_ARGS(stream->nts)); + GST_INFO_OBJECT (stream->pad,"Received disc = %"GST_TIME_FORMAT, GST_TIME_ARGS(cts - stream->nts)); + stream->cdisc = cts - stream->nts; + stream->total_disc += (cts - stream->nts); + GST_INFO_OBJECT (stream->pad, "total disc = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->total_disc)); + stream->apply_disc = FALSE; + } + + cts -= stream->cdisc; + + /* get the max last ts, required because of frame reordering */ + stream->lts = cts > stream->lts ? cts : stream->lts; + + GST_DEBUG_OBJECT (stream->pad, "modifying buffer timestamp : %"GST_TIME_FORMAT" -> %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (inbuf)), GST_TIME_ARGS(cts)); + +#ifndef LATEST_AV_SYNC + /* store first valid timestamp of a fragment */ + if (stream->fts == GST_CLOCK_TIME_NONE) { + stream->fts = cts; + stream->nts = stream->fts + demux->cfrag_dur; + GST_INFO_OBJECT (stream->pad, "storing first ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->fts)); + if (demux->is_live && stream->type == HLSDEMUX2_STREAM_AUDIO) + demux->cur_audio_fts = cts; + } +#endif + +#ifdef LATEST_AV_SYNC + /* 10/31/2013: dont want to disturb live case at this moment */ + if (!stream->valid_fts_rcvd) { + if (stream->type == HLSDEMUX2_STREAM_AUDIO) { + if (!demux->is_live && (GST_CLOCK_DIFF(cts, stream->prev_nts) > (10 * GST_MSECOND))) { + GST_WARNING_OBJECT (stream->pad, "already seen audio timestamps[%"GST_TIME_FORMAT"]" + " and previous_nts = %"GST_TIME_FORMAT"...drop this", + GST_TIME_ARGS(cts), GST_TIME_ARGS(stream->prev_nts)); + return FALSE; + } else { + GST_INFO_OBJECT (stream->pad, "recevied valid audio first ts = %"GST_TIME_FORMAT, + GST_TIME_ARGS (cts)); + demux->cur_audio_fts = cts; + } + } + + stream->valid_fts_rcvd = TRUE; + stream->fts = cts; + stream->nts = stream->fts + demux->cfrag_dur; // Approximate nts + + GST_INFO_OBJECT (stream->pad, "storing first ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->fts)); + } else if (!stream->frame_duration) { + stream->frame_duration = cts - stream->fts; + GST_DEBUG_OBJECT (stream->pad, "frame duration = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->frame_duration)); + } + +#else + + /* 10/31/2013: dont want to disturb live case at this moment */ + if (!demux->is_live && stream->type == HLSDEMUX2_STREAM_AUDIO) { + if (!stream->valid_fts_rcvd) { + if (GST_CLOCK_DIFF(cts, stream->prev_nts) > 0) { + GST_WARNING_OBJECT (stream->pad, "already seen audio timestamps[%"GST_TIME_FORMAT"]" + " and previous_nts = %"GST_TIME_FORMAT"...drop this", + GST_TIME_ARGS(cts), GST_TIME_ARGS(stream->prev_nts)); + return FALSE; + } else { + GST_INFO_OBJECT (stream->pad, "recevied valid audio first ts = %"GST_TIME_FORMAT, + GST_TIME_ARGS (cts)); + demux->cur_audio_fts = cts; + stream->valid_fts_rcvd = TRUE; + } + } else if (!stream->frame_duration) { + stream->frame_duration = cts - demux->cur_audio_fts; + GST_DEBUG_OBJECT (demux, "audio frame duration = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->frame_duration)); + } + } +#endif + + GST_BUFFER_TIMESTAMP (inbuf) = cts; + + return TRUE; +} + +static void +gst_hlsdemux2_downloader_new_buffer (GstElement *appsink, void *user_data) +{ + GstHLSDemux2Stream *stream = (GstHLSDemux2Stream *)user_data; + GstHLSDemux2 *demux = stream->parent; + GstBuffer *inbuf = NULL; + gboolean bret = FALSE; + + if (demux->cancelled ) { + return; + } + + inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); + if (!inbuf) { + GST_WARNING_OBJECT (demux, "Input buffer not available..."); + return; + } + + GST_LOG_OBJECT (stream->pad, "Received buffer with size = %d and ts = %"GST_TIME_FORMAT, + GST_BUFFER_SIZE(inbuf), GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf))); + + if (demux->fdownloader->force_timestamps) { + GST_BUFFER_TIMESTAMP(inbuf) += (stream->prev_nts + stream->base_ts); + GST_DEBUG_OBJECT (stream->pad, "forced timestamp = %"GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf))); + } + + /* alter the timestamps */ + bret = gst_hlsdemux2_alter_timestamps (stream, inbuf); + if (!bret) { + gst_buffer_unref (inbuf); + return; + } + + /* store buffer in queue */ + g_queue_push_tail (stream->downloader_queue, inbuf); + + return; +} + +static gboolean +gst_hlsdemux2_imagebuf_pipe_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GstHLSDemux2PvtStream *stream = (GstHLSDemux2PvtStream *)data; + GstHLSDemux2 *demux = (GstHLSDemux2 *)stream->parent; + gboolean bret = TRUE; + gchar *ele_name = gst_element_get_name (GST_MESSAGE_SRC (msg)); + + GST_INFO_OBJECT(demux, "Message on BUS : %s", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + GST_INFO_OBJECT (demux, "received EOS on IMAGE pipe from '%s'.. Closing pipeline", ele_name); + g_cond_signal (stream->convert_cond); + gst_object_unref (stream->convert_pipe); + break; + } + case GST_MESSAGE_ERROR: { + GST_ERROR_OBJECT (demux, "Error Image Pipe from %s\n", ele_name); + bret = FALSE; + break; + } + default:{ + GST_DEBUG_OBJECT (demux, "Message on Image Pipe : %s from %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg)), ele_name); + bret = FALSE; + break; + } + } + g_free(ele_name); + ele_name = NULL; + return bret; +} + +static gboolean gst_hlsdemux2_done_video_buffer (GstPad * srcpad, GstEvent * event, gpointer user_data) +{ + GstHLSDemux2PvtStream *stream = (GstHLSDemux2PvtStream *)user_data; + GstHLSDemux2 *demux = (GstHLSDemux2 *)stream->parent; + GST_LOG_OBJECT(demux,"EVENT %s on pad : %s having caps : %"GST_PTR_FORMAT,GST_EVENT_TYPE_NAME (event), GST_PAD_NAME(srcpad), srcpad->caps); + if(event->type == GST_EVENT_EOS){ + GST_DEBUG_OBJECT(demux,"EVENT : %s Size : %d Pushing to demux video buffer [%d,%d]", + GST_EVENT_TYPE_NAME (event),GST_BUFFER_SIZE(stream->video_buffer), + stream->video_buffer->data[0],stream->video_buffer->data[1]); + g_cond_signal (stream->convert_cond); + } + return TRUE; +} + +static gboolean gst_hlsdemux2_set_video_buffer (GstPad * srcpad, GstBuffer * buffer, gpointer user_data) +{ + GstHLSDemux2PvtStream *pvt_stream = (GstHLSDemux2PvtStream *)user_data; + GstHLSDemux2 *demux = (GstHLSDemux2 *)pvt_stream->parent; + GST_LOG_OBJECT(demux,"BUFFER size:%d on pad having caps : %"GST_PTR_FORMAT,GST_BUFFER_SIZE(buffer), srcpad->caps); + GST_LOG_OBJECT(demux,"BUFFER DATA : %d %d",buffer->data[0],buffer->data[1]); + pvt_stream->video_buffer = buffer; + return TRUE; +} + +static void +gst_hlsdemux2_downloader_eos (GstElement * appsink, void* user_data) +{ + GstHLSDemux2Stream *stream = (GstHLSDemux2Stream *)user_data; + GstHLSDemux2 *demux = stream->parent; + GstBuffer *buf = NULL; + gint64 percent = 0; + + /* push all data to queue specific to this stream */ + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (demux, "returning due to cancelled / flushing"); + return; + } + + if (g_queue_is_empty (stream->downloader_queue)) { + GST_WARNING_OBJECT (demux, "EOS received without any buffer in downloader queue"); + return; + } + + if (demux->fdownloader->force_timestamps) { + demux->cur_audio_fts = stream->prev_nts; + } + + if (demux->fdownloader->cur_stream_cnt < demux->active_stream_cnt) { + gint idx = 0; + + GST_INFO_OBJECT (demux, "need to send dummy data.. check for the stream"); + + /* signal queue full condition to come out */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = demux->streams[idx]; + GError *error = NULL; + + if (cur_stream) { + if (g_queue_is_empty (cur_stream->downloader_queue)) { + GST_INFO_OBJECT (cur_stream->pad, "enable sending dummy data"); + + /* take first ts of audio as cur_stream fts */ + cur_stream->fts = demux->cur_audio_fts; + + /* create thread to send dummy data */ + cur_stream->dummy_data_thread = g_thread_create ((GThreadFunc) gst_hlsdemux2_push_dummy_data, + cur_stream, TRUE, &error); + } + } + } + } + + while (!g_queue_is_empty (stream->downloader_queue)) { + + if (demux->cancelled || demux->flushing) { + GST_WARNING_OBJECT (stream->pad, "on cancel/flushing stopping pushing"); + break; + } + + buf = (GstBuffer *) g_queue_pop_head (stream->downloader_queue); + + GST_DEBUG_OBJECT (stream->pad, "input buffer timestamp : %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (buf))); + + g_mutex_lock (stream->queue_lock); + + if (!GST_BUFFER_TIMESTAMP_IS_VALID(buf)){ + g_queue_push_tail (stream->queue, buf); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + continue; + } + + GST_LOG_OBJECT (stream->pad, "buffer size = %d, ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_BUFFER_SIZE(buf), GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(buf)), GST_TIME_ARGS(GST_BUFFER_DURATION(buf))); + + /* calculate cached duration */ + gst_hlsdemux2_calculate_pushed_duration (demux, stream, buf); + + if (stream->cached_duration < 0) + stream->cached_duration = 0; + + percent = (stream->cached_duration * 100) / demux->total_cache_duration; + GST_LOG_OBJECT (stream->pad, "percent done = %"G_GINT64_FORMAT, (gint)percent, percent); + + if (percent > 100) { + guint64 overall_percent = 0; + + g_mutex_lock (demux->buffering_lock); + overall_percent = demux->percent; + g_mutex_unlock (demux->buffering_lock); + + if (overall_percent < 100) { /* otherwise, may cause blocking while buffering*/ + if (percent > 400) { + GST_ERROR_OBJECT (stream->pad,"having worrest buffering.. exiting"); + GST_ELEMENT_ERROR (demux, STREAM, FAILED, ("wrong buffering.. check implementation"), (NULL)); + } + GST_INFO_OBJECT (stream->pad, "@@@@@@ queue should not go to wait now @@@@@@@@"); + } else { + /* update buffering & wait if space is not available */ + GST_LOG_OBJECT (stream->pad, "Reached more than 100 percent, queue full & wait till free"); + g_cond_wait(stream->queue_full, stream->queue_lock); + GST_LOG_OBJECT (stream->pad,"Received signal to add more data..."); + } + } + + g_cond_signal (stream->queue_empty); + + g_mutex_unlock (stream->queue_lock); + } + + /* wait for dummy threads finish their processing */ + if (demux->fdownloader->cur_stream_cnt < demux->active_stream_cnt) { + gint idx = 0; + + GST_INFO_OBJECT (demux, "need to close dummy threads"); + + /* signal queue full condition to come out */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *cur_stream = demux->streams[idx]; + + if (cur_stream && cur_stream->dummy_data_thread) { + GST_INFO_OBJECT (stream->pad, "waiting for dummy push thread to finish..."); + g_thread_join(cur_stream->dummy_data_thread); + cur_stream->dummy_data_thread = NULL; + GST_INFO_OBJECT (stream->pad, "COMPLETED DUMMY PUSH..."); + } + } + } + +#ifdef LATEST_AV_SYNC + if (demux->is_live) { + stream->total_stream_time += (stream->lts - stream->fts); + } else { + stream->total_stream_time += (stream->lts - stream->fts + stream->frame_duration); + stream->nts = stream->total_stream_time; + } +#else + stream->total_stream_time += (stream->lts - stream->fts); +#endif + + GST_DEBUG_OBJECT (stream->pad, "---------------- TS VALUES ----------------"); + GST_DEBUG_OBJECT (stream->pad, "valid start ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->fts)); + GST_DEBUG_OBJECT (stream->pad, "valid end ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->lts)); + GST_DEBUG_OBJECT (stream->pad, "fragment duration received = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->lts - stream->fts)); + GST_DEBUG_OBJECT (stream->pad, "total stream time = %"GST_TIME_FORMAT,GST_TIME_ARGS(stream->total_stream_time)); + GST_DEBUG_OBJECT (stream->pad, "total disc time = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->total_disc)); + GST_DEBUG_OBJECT (stream->pad, "next expected start ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(stream->nts)); + GST_DEBUG_OBJECT (stream->pad, "---------------- DUMP VALUES END----------------"); + + stream->apply_disc = FALSE; + stream->fts = GST_CLOCK_TIME_NONE; + stream->valid_fts_rcvd = FALSE; + stream->prev_nts = stream->nts; + demux->fdownloader->force_timestamps = FALSE; + return; +} + +static void +gst_hlsdemux2_push_eos (GstHLSDemux2 *demux) +{ + guint idx = 0; + + /* signal queue full condition to come out */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + GstHLSDemux2Stream *stream = demux->streams[idx]; + + if (stream) { + g_mutex_lock (stream->queue_lock); + /* end of playlist, push EOS to queue */ + GST_INFO_OBJECT (stream->pad, "pushing EOS event to stream's queue"); + g_queue_push_tail (stream->queue, gst_event_new_eos ()); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + } + } +} + +static void +gst_hlsdemux2_on_playlist_buffer (GstElement *appsink, void *data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GstBuffer *inbuf = NULL; + + inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); + if (!inbuf) { + GST_WARNING_OBJECT (demux, "Input buffer not available..."); + return; + } + + if (demux->pldownloader->playlist == NULL) { + demux->pldownloader->playlist = gst_buffer_copy (inbuf); + gst_buffer_unref (inbuf); + } else { + GstBuffer *buffer = NULL; + guint size = GST_BUFFER_SIZE(demux->pldownloader->playlist) + GST_BUFFER_SIZE(inbuf); + + buffer = gst_buffer_new_and_alloc (size); + if (!buffer) { + GST_ERROR_OBJECT (demux, "failed allocate memory..."); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + gst_buffer_unref (inbuf); + gst_buffer_unref (demux->pldownloader->playlist); + return; + } + + /*copy existing playlist buffer */ + memcpy (GST_BUFFER_DATA(buffer), GST_BUFFER_DATA(demux->pldownloader->playlist), GST_BUFFER_SIZE(demux->pldownloader->playlist)); + + /*copy new playlist buffer */ + memcpy (GST_BUFFER_DATA(buffer) + GST_BUFFER_SIZE(demux->pldownloader->playlist), + GST_BUFFER_DATA(inbuf), GST_BUFFER_SIZE(inbuf)); + + gst_buffer_unref (inbuf); + gst_buffer_unref (demux->pldownloader->playlist); + demux->pldownloader->playlist = buffer; + } + + return; +} + +static void +gst_hlsdemux2_on_key_buffer (GstElement *appsink, void *data) +{ + GstHLSDemux2 *demux = (GstHLSDemux2 *)data; + GstBuffer *inbuf = NULL; + + inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); + if (!inbuf) { + GST_WARNING_OBJECT (demux, "Input buffer not available..."); + return; + } + + if (demux->kdownloader->key == NULL) { + demux->kdownloader->key = gst_buffer_copy (inbuf); + gst_buffer_unref (inbuf); + } else { + GstBuffer *buffer = NULL; + guint size = GST_BUFFER_SIZE(demux->kdownloader->key) + GST_BUFFER_SIZE(inbuf); + + buffer = gst_buffer_new_and_alloc (size); + if (!buffer) { + GST_ERROR_OBJECT (demux, "failed allocate memory..."); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("can't allocate memory"), (NULL)); + gst_buffer_unref (inbuf); + gst_buffer_unref (demux->kdownloader->key); + return; + } + + /*copy existing playlist buffer */ + memcpy (GST_BUFFER_DATA(buffer), GST_BUFFER_DATA(demux->kdownloader->key), GST_BUFFER_SIZE(demux->kdownloader->key)); + + /*copy new playlist buffer */ + memcpy (GST_BUFFER_DATA(buffer) + GST_BUFFER_SIZE(demux->kdownloader->key), + GST_BUFFER_DATA(inbuf), GST_BUFFER_SIZE(inbuf)); + + gst_buffer_unref (inbuf); + gst_buffer_unref (demux->kdownloader->key); + demux->kdownloader->key = buffer; + } + + return; +} + +static void +gst_hlsdemux2_stop (GstHLSDemux2 *demux) +{ + GST_INFO_OBJECT (demux, "entering..."); + + demux->cancelled = TRUE; + + g_cond_signal (demux->post_msg_start); + g_cond_signal (demux->post_msg_exit); + + /* special case: for exiting playlist downloader created from sink event */ + if (demux->pldownloader && demux->pldownloader->cond) + g_cond_broadcast (demux->pldownloader->cond); + + while (demux->download_task && (GST_TASK_STATE (demux->download_task) != GST_TASK_STOPPED)) { + int idx = 0; + GstHLSDemux2Stream *stream = NULL; + + GST_INFO_OBJECT (demux, "Closing Download task..."); + + /* signal queue full conditions */ + for (idx = 0; idx < HLSDEMUX2_STREAM_NUM; idx++) { + stream = demux->streams[idx]; + + if (stream && GST_PAD_TASK(stream->pad)) { + gst_pad_push_event (stream->pad, gst_event_new_flush_start ()); + g_cond_broadcast(stream->queue_full); + g_cond_broadcast(stream->queue_empty); + } + } + + if (demux->pl_update_cond) + g_cond_broadcast (demux->pl_update_cond); + + if (demux->fdownloader && demux->fdownloader->cond) + g_cond_broadcast (demux->fdownloader->cond); + if (demux->kdownloader && demux->kdownloader->cond) + g_cond_broadcast (demux->kdownloader->cond); + if (demux->pldownloader && demux->pldownloader->cond) + g_cond_broadcast (demux->pldownloader->cond); + + GST_INFO_OBJECT (demux, "Waiting to acquire download lock"); + g_static_rec_mutex_lock (&demux->download_lock); + g_static_rec_mutex_unlock (&demux->download_lock); + + GST_INFO_OBJECT (demux, "Waiting download task JOIN"); + gst_task_join (demux->download_task); + gst_object_unref (demux->download_task); + demux->download_task = NULL; + + GST_INFO_OBJECT (demux, "Completed closing of download task..."); + + g_thread_join(demux->buffering_posting_thread); + GST_INFO_OBJECT (demux, "Completed closing of download monitor thread..."); + + if (demux->fdownloader && demux->fdownloader->pipe) { + GST_WARNING_OBJECT (demux, "Still fragment download exist"); + gst_hlsdemux2_destroy_fragment_download (demux); + } + if (demux->pldownloader && demux->pldownloader->pipe) { + GST_WARNING_OBJECT (demux, "Still playlist download exist"); + gst_hlsdemux2_destroy_playlist_download (demux); + } + if (demux->kdownloader && demux->kdownloader->pipe) { + GST_WARNING_OBJECT (demux, "Still key download exist"); + gst_hlsdemux2_destroy_key_download (demux); + } + } + + gst_hlsdemux2_stop_stream (demux); + + GST_INFO_OBJECT (demux, "leaving..."); +} + +static void +gst_hlsdemux2_stop_stream (GstHLSDemux2 *demux) +{ + int n = 0; + GstHLSDemux2Stream *stream = NULL; + gchar *pad_name = NULL; + + GST_INFO_OBJECT (demux, "entering"); + + for (n = 0; n < HLSDEMUX2_STREAM_NUM; n++) { + stream = demux->streams[n]; + + if (stream && GST_PAD_TASK(stream->pad)) { + pad_name = gst_pad_get_name (stream->pad); + GST_INFO_OBJECT (stream->pad, "stopping pad task : %s", pad_name); + gst_pad_stop_task (stream->pad); + GST_INFO_OBJECT (stream->pad, "stopped pad task : %s", pad_name); + g_free(pad_name); + pad_name = NULL; + } + } + + for (n = 0; n < HLSDEMUX2_STREAM_NUM; n++) { + if (demux->streams[n]) { + gst_hlsdemux2_stream_deinit(demux->streams[n], demux); + demux->streams[n] = NULL; + } + } + demux->active_stream_cnt = 0; + GST_INFO_OBJECT (demux, "leaving"); +} + +static void +gst_hlsdemux2_stream_init (GstHLSDemux2 *demux, GstHLSDemux2Stream *stream, HLSDEMUX2_STREAM_TYPE stream_type, gchar *name, GstCaps *src_caps) +{ + stream->queue = g_queue_new (); + stream->queue_full = g_cond_new (); + stream->queue_empty = g_cond_new (); + stream->queue_lock = g_mutex_new (); + stream->parent = demux; + stream->type = stream_type; + stream->percent = 100; + stream->eos = FALSE; + stream->cached_duration = 0; + stream->lts = 0; + stream->dummy_data_thread = NULL; + stream->base_ts = GST_CLOCK_TIME_NONE; + stream->fts = GST_CLOCK_TIME_NONE; + stream->valid_fts_rcvd = FALSE; + stream->total_stream_time = 0; + stream->total_disc = 0; + stream->cdisc = 0; + stream->nts = GST_CLOCK_TIME_NONE; + stream->prev_nts = 0; + stream->downloader_queue = g_queue_new(); + stream->need_newsegment = TRUE; + stream->newsegment = NULL; + stream->frame_duration = 0; + + if (stream_type == HLSDEMUX2_STREAM_VIDEO) { + stream->pad = gst_pad_new_from_static_template (&hlsdemux2_videosrc_template, name); + GST_INFO_OBJECT (demux, "Its a Multi-variant stream.."); + demux->stream_config = HLSDEMUX2_MULTI_VARIANT; + } else if (stream_type == HLSDEMUX2_STREAM_AUDIO) { + stream->pad = gst_pad_new_from_static_template (&hlsdemux2_audiosrc_template, name); + } else if (stream_type == HLSDEMUX2_STREAM_TEXT) { + stream->pad = gst_pad_new_from_static_template (&hlsdemux2_subpicture_template, name); + } else if (stream_type == HLSDEMUX2_STREAM_PRIVATE) { + stream->pad = gst_pad_new_from_static_template (&hlsdemux2_private_template, name); + } + + GST_INFO_OBJECT (demux, "======>>>> created hlsdemux2 source pad : %s", name); + + GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream; + + gst_pad_use_fixed_caps (stream->pad); + gst_pad_set_event_function (stream->pad, gst_hlsdemux2_handle_src_event); + gst_pad_set_query_function (stream->pad, gst_hlsdemux2_src_query); + gst_pad_set_caps (stream->pad, src_caps); + + GST_DEBUG_OBJECT (demux, "adding pad %s %p to demux %p", GST_OBJECT_NAME (stream->pad), stream->pad, demux); + gst_pad_set_active (stream->pad, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (demux), stream->pad); + + if (!gst_pad_is_linked (stream->pad)) { + GST_WARNING_OBJECT (stream->pad, "'%s' pad is not linked...", GST_OBJECT_NAME (stream->pad)); + stream->is_linked = FALSE; + return; + } + stream->is_linked = TRUE; + + if (!gst_pad_start_task (stream->pad, (GstTaskFunction) gst_hlsdemux2_push_loop, stream)) { + GST_ERROR_OBJECT (demux, "failed to create stream task..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to create stream push loop"), (NULL)); + return; + } + +} + +static void +gst_hlsdemux2_stream_deinit (GstHLSDemux2Stream *stream, gpointer data) +{ + GstHLSDemux2 * demux = (GstHLSDemux2 *)data; + if (stream) { + + if (stream->queue) { + while (!g_queue_is_empty(stream->queue)) { + gst_buffer_unref (g_queue_pop_head (stream->queue)); + } + g_queue_free (stream->queue); + stream->queue = NULL; + } + + if (stream->pad) { + gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad); + stream->pad = NULL; + } + + if (stream->queue_lock) { + g_mutex_free (stream->queue_lock); + stream->queue_lock = NULL; + } + if (stream->queue_full) { + g_cond_free (stream->queue_full); + stream->queue_full = NULL; + } + if (stream->queue_empty) { + g_cond_free (stream->queue_empty); + stream->queue_empty= NULL; + } + g_free (stream); + } + +} + +static gboolean +gst_hlsdemux2_set_location (GstHLSDemux2 * demux, const gchar * uri) +{ + if (demux->client) + gst_m3u8_client_free (demux->client); + demux->client = gst_m3u8_client_new (uri); + GST_INFO_OBJECT (demux, "Changed location: %s", uri); + return TRUE; +} + +static gchar * +gst_hlsdemux2_src_buf_to_utf8_playlist (GstBuffer * buf) +{ + gint size; + gchar *data; + gchar *playlist; + + data = (gchar *) GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + +#if 0 /* giving error some times, so removing*/ + if (!g_utf8_validate (data, size, NULL)) + goto validate_error; +#endif + + /* alloc size + 1 to end with a null character */ + playlist = g_malloc0 (size + 1); + if (!playlist) { + GST_ERROR ("failed to create memory..."); + gst_buffer_unref (buf); + return NULL; + } + + memcpy (playlist, data, size); + playlist[size] = '\0'; + + gst_buffer_unref (buf); + return playlist; +#if 0 +validate_error: + gst_buffer_unref (buf); + GST_ERROR ("failed to validate playlist"); + return NULL; +#endif +} + +static void +gst_hlsdemux2_handle_private_pad (GstHLSDemux2 *demux, GstPad *srcpad) +{ + GstHLSDemux2PvtStream *pvt_stream = NULL; + gchar *sinkname = NULL; + GstPad *sinkpad = NULL; + + pvt_stream = g_new0 (GstHLSDemux2PvtStream, 1); + + demux->private_stream = pvt_stream; + pvt_stream->parent = demux; + pvt_stream->type = HLSDEMUX2_STREAM_PRIVATE; + pvt_stream->image_buffer = NULL; + pvt_stream->got_img_buffer = FALSE; + pvt_stream->video_buffer = NULL; + pvt_stream->id3_buffer = NULL; + pvt_stream->convert_lock = g_mutex_new (); + pvt_stream->convert_cond = g_cond_new (); + pvt_stream->sink = NULL; + pvt_stream->img_load_lock = g_mutex_new (); + pvt_stream->img_load_cond = g_cond_new (); + + /* create sink element */ + sinkname = g_strdup_printf("%s-%s", GST_PAD_NAME(srcpad) , "sink"); + pvt_stream->sink = gst_element_factory_make ("appsink", NULL); + if (!pvt_stream->sink) { + GST_ERROR_OBJECT (demux, "failed to create sink element - %s", sinkname); + GST_ELEMENT_ERROR (demux, CORE, FAILED, ("failed to create element - %s", sinkname), (NULL)); + g_free(sinkname); + sinkname = NULL; + return; + } + + /* set sink element to PLAYING state */ + gst_bin_add (GST_BIN (demux->fdownloader->pipe), pvt_stream->sink); + gst_element_set_state (pvt_stream->sink, GST_STATE_READY); + g_object_set (G_OBJECT (pvt_stream->sink), "sync", FALSE, "emit-signals", TRUE, NULL); + + sinkpad = gst_element_get_pad(pvt_stream->sink, "sink"); + if (!sinkpad) { + GST_ERROR_OBJECT (demux, "failed to get sinkpad from element - %s", sinkname); + GST_ELEMENT_ERROR (demux, CORE, PAD, ("Count not get sink pad"), (NULL)); + g_free(sinkname); + sinkname = NULL; + return; + } + + if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK) { + GST_ERROR_OBJECT (demux, "failed to link sink bin elements"); + GST_ELEMENT_ERROR (demux, CORE, NEGOTIATION, ("Could not link pads.."), (NULL)); + gst_object_unref(sinkpad); + } + + gst_element_set_state (pvt_stream->sink, GST_STATE_PLAYING); + + g_signal_connect (pvt_stream->sink, "new-buffer", G_CALLBACK (gst_hlsdemux2_private_sink_on_new_buffer), pvt_stream); + g_signal_connect (pvt_stream->sink, "eos", G_CALLBACK (gst_hlsdemux2_private_sink_on_eos), pvt_stream); + + gst_object_unref(sinkpad); + + GST_INFO_OBJECT (demux, "successfully created private pad bin..."); + + g_free(sinkname); + sinkname = NULL; + return; +} + +static void +gst_hlsdemux2_private_sink_on_new_buffer (GstElement *appsink, void *user_data) +{ + GstHLSDemux2PvtStream *pvt_stream = (GstHLSDemux2PvtStream *)user_data; + GstHLSDemux2 *demux = pvt_stream->parent; + GstBuffer *inbuf = NULL; + + inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); + if (!inbuf) { + GST_ERROR_OBJECT (demux, "Input buffer not available...."); + return; + } + + GST_LOG_OBJECT (demux, "Image buffer received of size = %d", GST_BUFFER_SIZE (inbuf)); + if(pvt_stream->id3_buffer==NULL) + pvt_stream->id3_buffer = gst_buffer_copy(inbuf); + else + pvt_stream->id3_buffer = gst_buffer_join (pvt_stream->id3_buffer, inbuf); + return; +} + +static void +gst_hlsdemux2_private_sink_on_eos (GstElement * appsink, void* user_data) +{ + GstHLSDemux2PvtStream *pvt_stream = (GstHLSDemux2PvtStream *)user_data; + GstHLSDemux2 *demux = pvt_stream->parent; + GstBuffer *image_buffer = NULL; + GstTagList *tag_list = gst_tag_list_from_id3v2_tag(pvt_stream->id3_buffer); + + GST_INFO_OBJECT (demux, "Processing image buffer"); + + if(!gst_tag_list_get_buffer (tag_list, GST_TAG_IMAGE, &image_buffer)) { + GST_ERROR_OBJECT(demux, "Failed to obtain image data."); + return; + } + + if(demux->stream_config == HLSDEMUX2_SINGLE_VARIANT) { + gchar *image_header = NULL; + GValue codec_type = G_VALUE_INIT; + GstMessage * tag_message = NULL; + + /* check whether it is same as previous image */ + if (demux->prev_image_buffer && + (GST_BUFFER_SIZE(demux->prev_image_buffer) == GST_BUFFER_SIZE(image_buffer))) { + if (!memcmp (GST_BUFFER_DATA(demux->prev_image_buffer), GST_BUFFER_DATA(image_buffer), GST_BUFFER_SIZE(image_buffer))) { + GST_INFO_OBJECT (demux, "current & previous embedded images are same..no need to post image again"); + gst_buffer_unref (image_buffer); + return; + } + } + + if (demux->prev_image_buffer) { + gst_buffer_unref (demux->prev_image_buffer); + } + demux->prev_image_buffer = gst_buffer_copy(image_buffer); + + image_header = g_strndup((gchar *) image_buffer->data, 8); + + g_value_init (&codec_type, G_TYPE_STRING); + + if((image_header[0] == 0xFF) && (image_header[1] == 0xD8)) { + GST_INFO_OBJECT(demux, "Found JPEG image header"); + g_value_set_static_string (&codec_type, "image/jpeg"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } else if (!g_strcmp0(image_header, "\211PNG\015\012\032\012")) { + GST_INFO_OBJECT(demux, "Found PNG image header"); + g_value_set_static_string (&codec_type, "image/png"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } else { + g_value_set_static_string (&codec_type, "image/unknown"); + GST_INFO_OBJECT(demux, "Unknown image header"); + gst_tag_list_add_value(tag_list, GST_TAG_MERGE_APPEND, GST_TAG_CODEC, &codec_type); + } + + GST_INFO_OBJECT(demux, "Set value : %s", g_value_get_string (&codec_type)); + g_value_unset(&codec_type); + + tag_message = gst_message_new_tag (GST_OBJECT_CAST (demux), tag_list); + if(!gst_element_post_message (GST_ELEMENT_CAST (demux), tag_message)) { + GST_ERROR_OBJECT (demux, "failed to post image tag"); + gst_buffer_unref (image_buffer); + g_free (image_header); + return; + } + + GST_INFO_OBJECT (demux, "successfully posted image tag..."); + gst_buffer_unref (image_buffer); + g_free (image_header); + return; + } + + GST_INFO_OBJECT(demux, "image header : %d,%d", image_buffer->data[0],image_buffer->data[1]); + + pvt_stream->image_buffer = gst_buffer_copy(image_buffer); + gst_buffer_unref (image_buffer); + pvt_stream->got_img_buffer = TRUE; + + g_cond_signal (pvt_stream->img_load_cond); + demux->has_image_buffer = TRUE; + +} + +static gboolean +hlsdemux2_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "hlsdemux2", GST_RANK_PRIMARY + 1, GST_TYPE_HLSDEMUX2) || FALSE) + return FALSE; + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "hlsdemux2", + "HLS demux - 2 plugin", + hlsdemux2_init, + VERSION, + "LGPL", + PACKAGE_NAME, + "http://www.samsung.com/") + diff --git a/hlsdemux2/src/gsthlsdemux2.h b/hlsdemux2/src/gsthlsdemux2.h new file mode 100755 index 0000000..15269a4 --- /dev/null +++ b/hlsdemux2/src/gsthlsdemux2.h @@ -0,0 +1,297 @@ +/* + * hlsdemux2 + * + * Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: JongHyuk Choi , Naveen Cherukuri + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_HLSDEMUX2_H__ +#define __GST_HLSDEMUX2_H__ + +#include +#include +#include +#include +#include "m3u8.h" + +G_BEGIN_DECLS +#define GST_TYPE_HLSDEMUX2 (gst_hlsdemux2_get_type()) +#define GST_HLSDEMUX2(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_HLSDEMUX2, GstHLSDemux2)) +#define GST_HLSDEMUX2_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLSDEMUX2,GstHLSDemux2Class)) +#define GST_IS_HLSDEMUX2(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLSDEMUX2)) +#define GST_IS_HLSDEMUX2_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLSDEMUX2)) + +typedef struct _GstHLSDemux2 GstHLSDemux2; +typedef struct _GstHLSDemux2Class GstHLSDemux2Class; +typedef struct _GstHLSDemux2Stream GstHLSDemux2Stream; +typedef struct _GstHLSDemux2PvtStream GstHLSDemux2PvtStream; + +#define GST_HLS_DEMUX_AES_BLOCK_SIZE 16 + +#define ON_ERROR_REQUEST_AGAIN // Orange server giving soup errors. so, request for fail count times + +#define HLSDEMUX2_CHUNK_TIME_DURATION (0.5 * GST_SECOND) +#define HLSDEMUX2_NUM_CHUNK_DOWNLOADS_FAST_SWITCH 4 +#define HLSDEMUX2_MAX_N_PAST_FRAG_DOWNLOADRATES 3 + +#define HLSDEMUX2_FAST_CHECK_CRITICAL_TIME_FACTOR 1.2 +#define HLSDEMUX2_FAST_CHECK_WARNING_TIME_FACTOR 1 + +#ifdef USING_AVG_PAST_DOWNLOADRATES +#define HLSDEMUX2_MAX_N_PAST_DOWNLOADRATES 3 +#endif + + +#define PLAYLIST_REFRESH_TIMEOUT (20 * GST_SECOND) + +#define HLSDEMUX2_OVERHEAD (1 * GST_SECOND) + +#define LATEST_AV_SYNC + +typedef enum +{ + HLSDEMUX2_STREAM_VIDEO = 0, + HLSDEMUX2_STREAM_AUDIO, + HLSDEMUX2_STREAM_TEXT, + HLSDEMUX2_STREAM_PRIVATE, + HLSDEMUX2_STREAM_NUM, +}HLSDEMUX2_STREAM_TYPE; + +typedef enum +{ + HLSDEMUX2_DOWNWARD_RECOVERY = 0, + HLSDEMUX2_UPWARD_RECOVERY, + HLSDEMUX2_NO_RECOVERY, +}HLSDEMUX2_RECOVERY_MODE; + +typedef enum +{ + HLSDEMUX2_MULTI_VARIANT = 0, + HLSDEMUX2_SINGLE_VARIANT +}HLSDEMUX2_STREAM_CONFIGURATION; + +typedef struct +{ + GstBin *sinkbin; + GstElement *parser; /*only for .aac stream */ + GstElement *queue; + GstElement *sink; +}HLSDemux2SinkBin; + +typedef struct +{ + GstElement *pipe; + GstElement *urisrc; + GstElement *typefind; + GstElement *queue; + GstElement *demuxer; + GList *sinkbins; + + guint64 content_size; + gboolean applied_fast_switch; + + gchar *remaining_data; + guint remaining_size; + + GMutex *lock; + GCond *cond; + gboolean is_encrypted; + + gulong src_bprobe; + gboolean get_next_frag; /* on error like NOT_FOUND, request next fragment */ + + guint cur_stream_cnt; + gboolean first_buffer; + gboolean force_timestamps; /* useful in .aac files playback */ + gboolean error_rcvd; + gboolean find_mediaseq; + GstClockTime seeked_pos; + GstClockTime cur_running_dur; + + /* for fragment download rate calculation */ + guint64 download_rate; + GstClockTime download_start_ts; + GstClockTime download_stop_ts; + guint64 src_downloaded_size; + guint64 queue_downloaded_size; + gint ndownloaded; /* number of fragments downloaded */ + GArray *avg_chunk_drates; /* chunk download rates array */ + GArray *avg_frag_drates; /* fragment download rates array */ + guint64 chunk_downloaded_size; + GstClockTime chunk_start_ts; +}HLSDemux2FragDownloader; + +/* playlist downloader */ +typedef struct +{ + GstElement *pipe; + GstBus *bus; + GstElement *urisrc; + GstElement *sink; + GMutex *lock; + GCond *cond; + + GstBuffer *playlist; + HLSDEMUX2_RECOVERY_MODE recovery_mode; + GstClockTime download_start_ts; +}HLSDemux2PLDownloader; + +/* Key downloader */ +typedef struct +{ + GstElement *pipe; + GstBus *bus; + GstElement *urisrc; + GstElement *sink; + + GMutex *lock; + GCond *cond; + + GstBuffer *key; + gchar *prev_key_uri; +}HLSDemux2KeyDownloader; + +/** + * GstHLSDemux2: + * + * Opaque #GstHLSDemux2 data structure. + */ +struct _GstHLSDemux2 +{ + GstElement parent; + + GstPad *sinkpad; + + /* M3U8 client */ + GstM3U8Client *client; + + gboolean is_live; + + HLSDemux2FragDownloader *fdownloader; /* fragment downloader */ + HLSDemux2PLDownloader *pldownloader; /* playlist downloader */ + HLSDemux2KeyDownloader *kdownloader; /* key downloader */ + + /* properties */ + gint blocksize; + + /* master playlist got from upstream */ + GstBuffer *playlist; + + /* fragment download task */ + GstTask *download_task; + GStaticRecMutex download_lock; + + /* Playlist updates task for live session */ + GStaticRecMutex updates_lock; + GTimeVal next_update; /* Time of the next update */ + + /* playlist update condition */ + GCond *pl_update_cond; + GMutex *pl_update_lock; + + /* array of pointers to each stream's list */ + GstHLSDemux2Stream *streams[HLSDEMUX2_STREAM_NUM]; + gint active_stream_cnt; + + gint percent; /* buffering percentage posting */ + GMutex *buffering_lock; + gboolean is_buffering; + GThread *buffering_posting_thread; + GMutex *post_msg_lock; + GCond *post_msg_start; + GCond *post_msg_exit; + + /* HTTP request cookies. */ + gchar **lastCookie; + gchar **playlistCookie; + gchar **keyCookie; + gchar **fragCookie; + gchar *lastDomain; + gchar *playlistDomain; + gchar *keyDomain; + gchar *fragDomain; + + gchar *user_agent; + + /* Properties */ + gfloat bitrate_switch_tol; /* tolerance with respect to the fragment duration to switch the bitarate*/ + + gboolean cancelled; + gboolean end_of_playlist; + + guint64 total_cache_duration; + guint64 target_duration; + +#ifdef ON_ERROR_REQUEST_AGAIN + /* requesting the same uron error */ + gchar *playlist_uri; + gchar *frag_uri; /* NOT supporting YET for fragment download errors*/ + gchar *key_uri; +#endif + + int soup_request_fail_cnt; + /* can be removed [mainly for debugging & testing] */ + gboolean force_lower_bitrate; + + GstClockTime cfrag_dur; + gboolean error_posted; + gboolean flushing; + GstClockTime ns_start; + GstClockTime cur_audio_fts; + + GstHLSDemux2PvtStream * private_stream; + gboolean has_image_buffer; + HLSDEMUX2_STREAM_CONFIGURATION stream_config; + GstBuffer *prev_image_buffer; + GstBuffer *prev_video_buffer; +}; + +struct _GstHLSDemux2Class +{ + GstElementClass parent_class; +}; + +GType gst_hlsdemux2_get_type (void); + +G_END_DECLS +#endif /* __GST_HLSDEMUX2_H__ */ diff --git a/hlsdemux2/src/m3u8.c b/hlsdemux2/src/m3u8.c new file mode 100755 index 0000000..f2a8621 --- /dev/null +++ b/hlsdemux2/src/m3u8.c @@ -0,0 +1,1246 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * m3u8.c: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "m3u8.h" + +static GstM3U8 *gst_m3u8_new (void); +static void gst_m3u8_free (GstM3U8 * m3u8); +static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data, + gboolean * updated); +static GstM3U8MediaFile * +gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration, + gchar *key_url, gchar *IV, guint sequence); +static void gst_m3u8_media_file_free (GstM3U8MediaFile * self); +static void gst_m3u8_key_free (GstM3U8Key * self); + +#define GST_CAT_DEFAULT hlsdemux2_m3u8_debug + +#define BITRATE_SWITCH_UPPER_THRESHOLD 0.3 +#define BITRATE_SWITCH_LOWER_THRESHOLD 0 +#define SWITCH_TRIGGER_POINT 1 + +static GstM3U8 * +gst_m3u8_new (void) +{ + GstM3U8 *m3u8; + + m3u8 = g_new0 (GstM3U8, 1); + m3u8->last_data = NULL; + return m3u8; +} + +static void +gst_m3u8_set_uri (GstM3U8 * self, gchar * uri) +{ + g_return_if_fail (self != NULL); + + if (self->uri) + g_free (self->uri); + self->uri = uri; +} + +static void +gst_m3u8_free (GstM3U8 * self) +{ + g_return_if_fail (self != NULL); + + g_free (self->uri); + g_free (self->allowcache); + g_free (self->codecs); + //g_free (self->cipher_ctx); + + g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL); + g_list_free (self->files); + + g_free (self->last_data); + g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL); + g_list_free (self->lists); + + g_free (self); +} + +static GstM3U8MediaFile * +gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration, + gchar *key_url, gchar *IV, guint sequence) +{ + GstM3U8MediaFile *file; + + file = g_new0 (GstM3U8MediaFile, 1); + file->uri = uri; + file->title = title; + file->duration = duration; + file->sequence = sequence; + + GST_DEBUG ("Duration of the file = %"GST_TIME_FORMAT"\n", GST_TIME_ARGS(duration)); + + if (key_url != NULL) + file->key_url = g_strdup (key_url); + else + file->key_url = NULL; + + file->iv = IV; + + return file; +} + +static void +gst_m3u8_media_file_free (GstM3U8MediaFile * self) +{ + g_return_if_fail (self != NULL); + + if (self->key_url) { + g_free (self->key_url); + self->key_url = NULL; + } + if (self->iv) { + g_free (self->iv); + self->iv = NULL; + } + if (self->title) { + g_free (self->title); + self->title = NULL; + } + if (self->uri){ + g_free (self->uri); + self->uri = NULL; + } + g_free (self); +} + +static gchar * +gst_m3u8_getIV_from_mediasequence (GstM3U8 *self) +{ + gchar *IV = NULL; + + IV = g_malloc0 (16); + if (!IV) { + GST_ERROR ("Failed to allocate memory..."); + return NULL; + } + + if (self->mediasequence > INT_MAX) { + GST_ERROR ("media sequnece is greater than INT_MAX...yet to handle"); + } + + IV [15] = (gchar)(self->mediasequence); + IV [14] = (gchar)(self->mediasequence >> 8); + IV [13] = (gchar)(self->mediasequence >> 16); + IV [12] = (gchar)(self->mediasequence >> 24); + return IV; +} + +static gboolean +int_from_string (gchar * ptr, gchar ** endptr, gint * val, gint base) +{ + gchar *end; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + errno = 0; + *val = strtol (ptr, &end, base); + if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) + || (errno != 0 && *val == 0)) { + GST_WARNING ("%s", g_strerror (errno)); + return FALSE; + } + + if (endptr) + *endptr = end; + + return end != ptr; +} + +static gboolean +double_from_string (gchar * ptr, gchar ** endptr, gdouble * val) +{ + gchar *end; + gdouble ret; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + errno = 0; + ret = g_strtod (ptr, &end); + if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL)) + || (errno != 0 && ret == 0)) { + GST_WARNING ("%s", g_strerror (errno)); + return FALSE; + } + + if (!isfinite (ret)) { + GST_WARNING ("%s", g_strerror (ERANGE)); + return FALSE; + } + + if (endptr) + *endptr = end; + + *val = (gdouble) ret; + + return end != ptr; +} + +static gboolean +iv_from_string (gchar * ptr, gchar ** endptr, guint8 * val) +{ + guint8 idx; + guint8 hex; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (*ptr == '0' && (*(ptr + 1) == 'x' || *(ptr + 1) == 'X')) { + ptr = ptr + 2; /* skip 0x or 0X */ + } + + for (idx = 0; idx < GST_M3U8_IV_LEN; idx++) { + hex = *ptr++ - '0'; + if (hex >= 16) + return FALSE; + *(val + idx) = hex * 16; + hex = *ptr++ - '0'; + if (hex >= 16) + return FALSE; + *(val + idx) += hex; + } + + if (endptr) + *endptr = ptr; + + return TRUE; +} + +static gboolean +iv_from_uint (guint uint, guint8 * val) +{ + guint8 idx; + + g_return_val_if_fail (val != NULL, FALSE); + + val = val + GST_M3U8_IV_LEN - 1; + for (idx = 0; idx < sizeof(guint); idx++) { + *val-- = (guint8)uint; + uint = uint >> 8; + } + return TRUE; +} + +static gboolean +make_valid_uri (gchar *prefix, gchar *suffix, gchar **uri) +{ + gchar *slash; + + if (!prefix) { + GST_WARNING ("uri prefix not set, can't build a valid uri"); + return FALSE; + } + slash = g_utf8_strrchr (prefix, -1, '/'); + if (!slash) { + GST_WARNING ("Can't build a valid uri"); + return FALSE; + } + + *slash = '\0'; + *uri = g_strdup_printf ("%s/%s", prefix, suffix); + *slash = '/'; + + return TRUE; +} + +static gboolean +parse_attributes (gchar ** ptr, gchar ** a, gchar ** v) +{ + gchar *end, *p; + + g_return_val_if_fail (ptr != NULL, FALSE); + g_return_val_if_fail (*ptr != NULL, FALSE); + g_return_val_if_fail (a != NULL, FALSE); + g_return_val_if_fail (v != NULL, FALSE); + + /* [attribute=value,]* */ + + *a = *ptr; + end = p = g_utf8_strchr (*ptr, -1, ','); + if (end) { + do { + end = g_utf8_next_char (end); + } while (end && *end == ' '); + *p = '\0'; + } + + *v = p = g_utf8_strchr (*ptr, -1, '='); + if (*v) { + *v = g_utf8_next_char (*v); + *p = '\0'; + } else { + GST_WARNING ("missing = after attribute"); + return FALSE; + } + + *ptr = end; + return TRUE; +} + +static gint +_m3u8_compare_uri (GstM3U8 * a, gchar * uri) +{ + g_return_val_if_fail (a != NULL, 0); + g_return_val_if_fail (uri != NULL, 0); + + return g_strcmp0 (a->uri, uri); +} + +static gint +gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b) +{ + return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth; +} + +/* + * @data: a m3u8 playlist text data, taking ownership + */ +static gboolean +gst_m3u8_update (GstM3U8 * self, gchar *data, gboolean * updated) +{ + gint val; + GstClockTime duration = 0; + gchar *title, *end; +// gboolean discontinuity; + GstM3U8 *list; + gchar *key_url = NULL; + gchar *IV = NULL; + gchar *actual_data = NULL; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (updated != NULL, FALSE); + + *updated = TRUE; + + /* check if the data changed since last update */ + if (self->last_data && g_str_equal (self->last_data, data)) { + GST_DEBUG ("Playlist is the same as previous one"); + *updated = FALSE; + g_free (data); + return TRUE; + } + + if (!g_str_has_prefix (data, "#EXTM3U")) { + GST_WARNING ("Data doesn't start with #EXTM3U"); + *updated = FALSE; + g_free (data); + return FALSE; + } + + if (self->last_data) + g_free (self->last_data); + + self->last_data = g_strdup(data); + + actual_data = data; + + if (self->files) { + g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL); + g_list_free (self->files); + self->files = NULL; + } + + list = NULL; + duration = 0; + title = NULL; + data += 7; + + while (TRUE) { + + end = g_utf8_strchr (data, -1, '\n'); + if (end) + *end = '\0'; + + if (data[0] != '#') { + gchar *r; + gchar *uri = NULL; + + if (duration <= 0 && list == NULL) { + GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data); + goto next_line; + } + + if (!gst_uri_is_valid (data)) { + if (!make_valid_uri(self->uri, data, &uri)) + goto next_line; + } else { + uri = g_strdup (data); + } + + r = g_utf8_strchr (uri, -1, '\r'); + if (r) + *r = '\0'; + + if (list != NULL) { + if (g_list_find_custom (self->lists, uri, (GCompareFunc) _m3u8_compare_uri)) { + GST_DEBUG ("Already have a list with this URI"); + gst_m3u8_free (list); + g_free (uri); + } else { + gst_m3u8_set_uri (list, uri); + self->lists = g_list_append (self->lists, list); + } + list = NULL; + } else { + GstM3U8MediaFile *file; + gchar *send_IV = NULL; + + if (key_url) { + GST_DEBUG ("AES-128 key url = %s", key_url); + if (NULL == IV) { + /* IV is not present in EXT-X-KEY tag. Prepare IV based on mediasequence */ + GST_DEBUG ("IV is not in EXT-X-KEY tag... generating from media_seq_num = %d", self->mediasequence); + send_IV = gst_m3u8_getIV_from_mediasequence (self); + } else { + int i=0; + send_IV = g_malloc0(16); + for(i=0;i<16;i++){ + send_IV[i]=IV[i]; + } + } + } + + file = + gst_m3u8_media_file_new (uri, title, duration, key_url, send_IV, + self->mediasequence++); + duration = 0; + title = NULL; + send_IV = NULL; + + self->files = g_list_append (self->files, file); + GST_DEBUG ("------------>>>>>>appending uri = %s and key = %s\n", file->uri, file->key_url); + } + } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) { + GST_DEBUG ("*************** End list is present ****************"); + self->endlist = TRUE; + } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) { + if (int_from_string (data + 15, &data, &val, 10)) + self->version = val; + } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) { + gchar *v, *a; + + if (list != NULL) { + GST_WARNING ("Found a list without a uri..., dropping"); + gst_m3u8_free (list); + } + + list = gst_m3u8_new (); + data = data + 18; + while (data && parse_attributes (&data, &a, &v)) { + if (g_str_equal (a, "BANDWIDTH")) { + if (!int_from_string (v, NULL, &list->bandwidth, 10)) + GST_WARNING ("Error while reading BANDWIDTH"); + } else if (g_str_equal (a, "PROGRAM-ID")) { + if (!int_from_string (v, NULL, &list->program_id, 10)) + GST_WARNING ("Error while reading PROGRAM-ID"); + } else if (g_str_equal (a, "CODECS")) { + g_free (list->codecs); + list->codecs = g_strdup (v); + } else if (g_str_equal (a, "RESOLUTION")) { + if (!int_from_string (v, &v, &list->width, 10)) + GST_WARNING ("Error while reading RESOLUTION width"); + if (!v || *v != '=') { + GST_WARNING ("Missing height"); + } else { + v = g_utf8_next_char (v); + if (!int_from_string (v, NULL, &list->height, 10)) + GST_WARNING ("Error while reading RESOLUTION height"); + } + } + } + } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) { + if (int_from_string (data + 22, &data, &val, 10)) + self->targetduration = val * GST_SECOND; + } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) { + if (int_from_string (data + 22, &data, &val, 10)) + self->mediasequence = val; + } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) { + /* discontinuity = TRUE; */ + } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) { + /* */ + GST_DEBUG ("FIXME parse date"); + } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) { + g_free (self->allowcache); + self->allowcache = g_strdup (data + 19); + } else if (g_str_has_prefix (data, "#EXTINF:")) { + gdouble fval; + if (!double_from_string (data + 8, &data, &fval)) { + GST_WARNING ("Can't read EXTINF duration"); + goto next_line; + } + duration = fval * (gdouble) GST_SECOND; + if (duration > self->targetduration) + GST_WARNING ("EXTINF duration > TARGETDURATION"); + if (!data || *data != ',') + goto next_line; + data = g_utf8_next_char (data); + if (data != end) { + g_free (title); + title = g_strdup (data); + } + } else if (g_str_has_prefix (data, "#EXT-X-KEY:")) { + gchar *val, *attr; + GST_DEBUG ("Found EXT-X-KEY tag..."); + + /* handling encrypted content */ + data = data + 11; /* skipping "#EXT-X-KEY:" tag */ + + while (data && parse_attributes (&data, &attr, &val)) { + if (g_str_equal (attr, "METHOD")) { + if (g_str_equal (val, "NONE")) { + GST_LOG ("Non encrypted file...and skipping current line and going to next line\n"); + goto next_line; + } else if (g_str_equal (val, "AES-128")) { + /* media files are encrypted */ + GST_LOG ("media files are encrypted with AES-128\n"); + // TODO: indicate in flag whether encrypted files or not + } + } else if (g_str_equal (attr, "URI")) { + gchar *end_dq = NULL; + + if (!val){ + GST_ERROR ("val is NULL"); + break; + } + + val = val + 1; /* eliminating first double quote in url */ + if (!val) { + GST_ERROR ("val is NULL"); + break; + } + + end_dq = g_utf8_strrchr (val, -1, '"'); + if (!end_dq) { + GST_ERROR ("end_dq is NULL"); + break; + } + + *end_dq = '\0'; + GST_DEBUG ("Key URI = %s\n", val); + + if (!gst_uri_is_valid (val)) { + gchar *slash; + if (!self->uri) { + GST_WARNING ("uri not set, can't build a valid uri"); + goto next_line; + } + slash = g_utf8_strrchr (self->uri, -1, '/'); + if (!slash) { + GST_WARNING ("Can't build a valid uri"); + goto next_line; + } + *slash = '\0'; + key_url = g_strdup_printf ("%s/%s", self->uri, val); + *slash = '/'; + } else { + key_url= g_strdup (val); + } + } else if (g_str_equal (attr, "IV")) { + gint iv_len = 0; + gchar tmp_byte[3]; + gint tmp_val = 0; + gint idx = 0; + gchar *car = NULL; + + if (IV) { + g_free (IV); + IV = NULL; + } + + IV = g_malloc0 (16); + if (NULL == IV) { + GST_ERROR ("Failed to allocate memory...\n"); + goto error; + } + + /* eliminating 0x/0X prefix */ + val = val + 2; + car = g_utf8_strchr (val, -1, '\r'); + if (car) + *car = '\0'; + + iv_len = g_utf8_strlen(val, -1); + if (iv_len < 1 || iv_len >32) { + GST_ERROR ("Wrong IV and iv_len = %d", iv_len); + goto error; + } + + val+=iv_len; + idx = 15; + while (iv_len > 0) { + // TODO: val need to incremented I feel.. check again + val-=(iv_len==1)?1:2; + g_utf8_strncpy(tmp_byte, val, (iv_len==1)?1:2); + tmp_byte[2] = '\0'; + tmp_val = 0; + if (!int_from_string (tmp_byte, NULL, &tmp_val, 16)) + GST_WARNING ("Error while reading PROGRAM-ID"); + IV[idx] = tmp_val; + idx--; + iv_len = iv_len - 2; + } + } + } + } else { + GST_LOG ("Ignored line: %s", data); + } + + next_line: + if (!end) + break; + data = g_utf8_next_char (end); /* skip \n */ + } + + /* redorder playlists by bitrate */ + if (self->lists) { + gchar *top_variant_uri = NULL; + + if (!self->current_variant) + top_variant_uri = GST_M3U8 (self->lists->data)->uri; + else + top_variant_uri = GST_M3U8 (self->current_variant->data)->uri; + + self->lists = + g_list_sort (self->lists, + (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate); + + self->current_variant = g_list_find_custom (self->lists, top_variant_uri, + (GCompareFunc) _m3u8_compare_uri); + } + + g_free (actual_data); + actual_data = NULL; + g_free (key_url); + key_url = NULL; + g_free (title); + title = NULL; + return TRUE; + +error: + g_free (actual_data); + actual_data = NULL; + g_free (key_url); + key_url = NULL; + g_free (title); + title = NULL; + return FALSE; +} + + +GstM3U8Client * +gst_m3u8_client_new (const gchar * uri) +{ + GstM3U8Client *client; + + g_return_val_if_fail (uri != NULL, NULL); + + client = g_new0 (GstM3U8Client, 1); + client->main = gst_m3u8_new (); + client->current = NULL; + client->sequence = -1; + client->update_failed_count = 0; + client->lock = g_mutex_new (); + gst_m3u8_set_uri (client->main, g_strdup (uri)); + + return client; +} + +void +gst_m3u8_client_free (GstM3U8Client * self) +{ + g_return_if_fail (self != NULL); + + gst_m3u8_free (self->main); + g_mutex_free (self->lock); + g_free (self); +} + +void +gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8) +{ + g_return_if_fail (self != NULL); + + GST_M3U8_CLIENT_LOCK (self); + if (m3u8 != self->current) { + self->current = m3u8; + self->update_failed_count = 0; + } + GST_M3U8_CLIENT_UNLOCK (self); +} + +static guint +_get_start_sequence (GstM3U8Client * client) +{ + GList *l; + GstClockTime duration_limit, duration_count = 0; + guint sequence = -1; + + if (client->current->endlist) { + l = g_list_first (client->current->files); + sequence = GST_M3U8_MEDIA_FILE (l->data)->sequence; + } else { + duration_limit = client->current->targetduration * 3; + for (l = g_list_last (client->current->files); l; l = l->prev) { + duration_count += GST_M3U8_MEDIA_FILE (l->data)->duration; + sequence = GST_M3U8_MEDIA_FILE (l->data)->sequence; + if (duration_count >= duration_limit) { + break; + } + } + } + return sequence; +} + +gboolean +gst_m3u8_client_update (GstM3U8Client * self, gchar * data) +{ + GstM3U8 *m3u8; + gboolean updated = FALSE; + gboolean ret = FALSE; + + g_return_val_if_fail (self != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (self); + m3u8 = self->current ? self->current : self->main; + + if (!gst_m3u8_update (m3u8, data, &updated)) + goto out; + + if (!updated) { + self->update_failed_count++; + goto out; + } + + /* select the first playlist, for now */ + if (!self->current) { + if (self->main->lists) { + self->current = self->main->current_variant->data; + } else { + self->current = self->main; + } + } + + if (m3u8->files && self->sequence == -1) { +#if 0 + self->sequence = GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence; +#else + /* (spec : 6.3.3) In live case, the client SHOULD NOT choose a segment + which starts less than three target durations from the end of the Playlist file */ + self->sequence = _get_start_sequence (self); +#endif + GST_DEBUG ("Setting first sequence at %d", self->sequence); + } + + ret = TRUE; +out: + GST_M3U8_CLIENT_UNLOCK (self); + return ret; +} + +static gboolean +_find_next (GstM3U8MediaFile * file, GstM3U8Client * client) +{ + GST_DEBUG ("Found fragment %d", file->sequence); + if (file->sequence >= client->sequence) + return FALSE; + return TRUE; +} + +static gboolean +_find_key (GstM3U8Key * key, GstM3U8Client * client) +{ + if (key->sequence <= client->sequence) + return FALSE; + return TRUE; +} + +void +gst_m3u8_client_get_current_position (GstM3U8Client * client, + GstClockTime * timestamp) +{ + GList *l; + GList *walk; + + l = g_list_find_custom (client->current->files, client, + (GCompareFunc) _find_next); + + *timestamp = 0; + for (walk = client->current->files; walk; walk = walk->next) { + if (walk == l) + break; + *timestamp += GST_M3U8_MEDIA_FILE (walk->data)->duration; + } +} + +gboolean +gst_m3u8_client_get_next_fragment (GstM3U8Client * client, GstClockTime cur_running_time, + gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, + GstClockTime * timestamp, gchar **key_uri, gchar **iv) +{ + GList *l; + GstM3U8MediaFile *file; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->current != NULL, FALSE); + g_return_val_if_fail (discontinuity != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + + GST_DEBUG ("Looking for fragment %d", client->sequence); + + if (client->main && client->main->lists && client->current->endlist) { + GList *walk = NULL; + GstClockTime current_pos, target_pos; + gint current_sequence; + gboolean found_sequence = FALSE; + + /* VOD playlist, so find mediasequence from time */ + // TODO: need to handle when live converts to VOD by putting END_LIST tag + + file = GST_M3U8_MEDIA_FILE (client->current->files->data); + current_sequence = file->sequence; + current_pos = 0 ; + target_pos = cur_running_time; + + for (walk = client->current->files; walk; walk = walk->next) { + file = walk->data; + + current_sequence = file->sequence; + if (current_pos <= target_pos && + target_pos < current_pos + file->duration) { + found_sequence = TRUE; + break; + } + current_pos += file->duration; + } + + if (found_sequence) { + GST_ERROR ("changing client sequence from %d -> %d", client->sequence, current_sequence); + client->sequence = current_sequence; + } else { + GST_WARNING ("max file_seq = %d and client_seq = %d", current_sequence, client->sequence); + GST_M3U8_CLIENT_UNLOCK (client); + return FALSE; + } + } + + l = g_list_find_custom (client->current->files, client, (GCompareFunc) _find_next); + if (l == NULL) { + GST_M3U8_CLIENT_UNLOCK (client); + return FALSE; + } + + gst_m3u8_client_get_current_position (client, timestamp); + + file = GST_M3U8_MEDIA_FILE (l->data); + + if (file->key_url) + *key_uri = g_strdup (file->key_url); + + if (file->iv) { + *iv = g_malloc0 (GST_M3U8_IV_LEN); + //*iv = file->iv; + memcpy (*iv, file->iv, GST_M3U8_IV_LEN); + } + + *discontinuity = client->sequence != file->sequence; + client->sequence = file->sequence + 1; + + if (file->uri) + *uri = g_strdup (file->uri); + + *duration = file->duration; + + GST_M3U8_CLIENT_UNLOCK (client); + return TRUE; +} + +void gst_m3u8_client_decrement_sequence (GstM3U8Client * client) +{ + GST_M3U8_CLIENT_LOCK (client); + client->sequence = client->sequence - 1; + GST_M3U8_CLIENT_UNLOCK (client); +} + +static void +_sum_duration (GstM3U8MediaFile * self, GstClockTime * duration) +{ + *duration += self->duration; +} + +GstClockTime +gst_m3u8_client_get_duration (GstM3U8Client * client) +{ + GstClockTime duration = 0; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + GST_M3U8_CLIENT_LOCK (client); + /* We can only get the duration for on-demand streams */ + if (!client->current->endlist) { + GST_M3U8_CLIENT_UNLOCK (client); + return GST_CLOCK_TIME_NONE; + } + + g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration); + GST_M3U8_CLIENT_UNLOCK (client); + return duration; +} + +GstClockTime +gst_m3u8_client_get_target_duration (GstM3U8Client * client) +{ + GstClockTime duration = 0; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + GST_M3U8_CLIENT_LOCK (client); + duration = client->current->targetduration; + GST_M3U8_CLIENT_UNLOCK (client); + return duration; +} + +/* used for LIVE case only, to update playlist file */ +GstClockTime +gst_m3u8_client_get_last_fragment_duration (GstM3U8Client * client) +{ + GstClockTime duration = 0; + GstM3U8MediaFile *last_file = NULL; + GList *last_list = NULL; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + GST_M3U8_CLIENT_LOCK (client); + + if (!client->current) { + GST_ERROR ("current variant is NULL.. return CLOCK_NONE"); + GST_M3U8_CLIENT_UNLOCK (client); + return GST_CLOCK_TIME_NONE; + } + + if (!client->current->files) { + GST_WARNING ("Due to playlist updation problem.. did not update the playlist"); + GST_M3U8_CLIENT_UNLOCK (client); + return 0; /* update playlist immediately */ + } + + last_list = g_list_last (client->current->files); + + if (!last_list) { + GST_WARNING ("list does not have last object... return target_duration"); + GST_M3U8_CLIENT_UNLOCK (client); + return client->current->targetduration; + } + + last_file = (GstM3U8MediaFile *)(last_list->data); + if(last_file) + duration = last_file->duration; + else + duration=client->current->targetduration; + + GST_DEBUG ("Last file duration = %"GST_TIME_FORMAT"\n", GST_TIME_ARGS(duration)); + GST_M3U8_CLIENT_UNLOCK (client); + return duration; +} + +const gchar * +gst_m3u8_client_get_uri (GstM3U8Client * client) +{ + const gchar *uri; + + g_return_val_if_fail (client != NULL, NULL); + + GST_M3U8_CLIENT_LOCK (client); + uri = client->main->uri; + GST_M3U8_CLIENT_UNLOCK (client); + return uri; +} + +const gchar * +gst_m3u8_client_get_current_uri (GstM3U8Client * client) +{ + const gchar *uri; + + g_return_val_if_fail (client != NULL, NULL); + + GST_M3U8_CLIENT_LOCK (client); + uri = client->current->uri; + GST_M3U8_CLIENT_UNLOCK (client); + return uri; +} + +gint +gst_m3u8_client_get_current_bandwidth (GstM3U8Client * client) +{ + gint bandwidth; + + g_return_val_if_fail (client != NULL, -1); + + GST_M3U8_CLIENT_LOCK (client); + bandwidth = client->current->bandwidth; + GST_M3U8_CLIENT_UNLOCK (client); + return bandwidth; +} + +gboolean +gst_m3u8_client_has_variant_playlist (GstM3U8Client * client) +{ + gboolean ret; + + g_return_val_if_fail (client != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + ret = (client->main->lists != NULL); + GST_M3U8_CLIENT_UNLOCK (client); + return ret; +} + +gboolean +gst_m3u8_client_is_live (GstM3U8Client * client) +{ + gboolean ret; + + g_return_val_if_fail (client != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + if (!client->current || client->current->endlist) + ret = FALSE; + else + ret = TRUE; + GST_M3U8_CLIENT_UNLOCK (client); + return ret; +} + +GList * +gst_m3u8_client_get_next_higher_bw_playlist (GstM3U8Client * client) +{ + GList *current_variant; + + g_return_val_if_fail (client != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + current_variant = g_list_next (client->main->current_variant); + if (!current_variant) { + GST_WARNING ("no next variant available..."); + GST_M3U8_CLIENT_UNLOCK (client); + return NULL; + } + + GST_DEBUG ("next variant = %p and uri = %s", current_variant, GST_M3U8(current_variant->data)->uri); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; + +} + +GList * +gst_m3u8_client_get_next_lower_bw_playlist (GstM3U8Client * client) +{ + GList *current_variant; + + g_return_val_if_fail (client != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + current_variant = g_list_previous (client->main->current_variant); + if (!current_variant) { + GST_WARNING ("no previous variant available..."); + GST_M3U8_CLIENT_UNLOCK (client); + return NULL; + } + + GST_DEBUG ("previous variant = %p and uri = %s", current_variant, GST_M3U8(current_variant->data)->uri); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; +} + + +#ifndef SWITCH_TRIGGER_POINT +GList * +gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate) +{ + GList *list, *current_variant; + + GST_M3U8_CLIENT_LOCK (client); + current_variant = client->main->current_variant; + + /* Go to the highest possible bandwidth allowed */ + while (GST_M3U8 (current_variant->data)->bandwidth < bitrate) { + list = g_list_next (current_variant); + if (!list) + break; + current_variant = list; + } + + while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) { + list = g_list_previous (current_variant); + if (!list) + break; + current_variant = list; + } + GST_M3U8_CLIENT_UNLOCK (client); + + return current_variant; +} +#else +GList * +gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate) +{ + GList *next_variant = NULL; + GList *current_variant = NULL; + guint current_bandwidth = 0; + + GST_M3U8_CLIENT_LOCK (client); + current_variant = client->main->current_variant; + current_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; + + if ((current_bandwidth + BITRATE_SWITCH_UPPER_THRESHOLD * current_bandwidth) < bitrate) { + GST_DEBUG ("Switch to upper band...\n"); + + if (!g_list_next (current_variant)) { + GST_DEBUG ("no next upper fragment...\n"); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; + } else { + GST_DEBUG (">>>>> UP : current_bw = %d, bitrate = %d\n", current_bandwidth, bitrate); + next_variant = current_variant; + + /* Go to the highest possible bandwidth allowed */ + while ((current_bandwidth + BITRATE_SWITCH_UPPER_THRESHOLD * current_bandwidth) < bitrate) { + current_variant = next_variant; + next_variant = g_list_next (next_variant); + if (!next_variant) { + GST_DEBUG ("no next upper fragment...\n"); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; + } + + current_bandwidth = GST_M3U8 (next_variant->data)->bandwidth; + GST_DEBUG ("current_bw in while= %d & URI = %s\n", current_bandwidth, GST_M3U8 (next_variant->data)->uri); + } + + /* as condition failed fallback to previous */ + GST_M3U8_CLIENT_UNLOCK (client); + + return current_variant; + } + } else if ((current_bandwidth + BITRATE_SWITCH_LOWER_THRESHOLD * current_bandwidth) > bitrate) { + GST_DEBUG ("Switch to lower band...\n"); + + if (!g_list_previous (current_variant)) { + GST_DEBUG ("no previous lower fragment...to go further down\n"); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; + } else { + + GST_DEBUG (">>>>> LOW : current_bw = %d, bitrate = %d\n", current_bandwidth, bitrate); + next_variant = current_variant; + + /* Go to the lowest possible bandwidth allowed */ + while ((current_bandwidth + BITRATE_SWITCH_LOWER_THRESHOLD * current_bandwidth) > bitrate) { + + next_variant = g_list_previous (next_variant); + if (!next_variant) { + GST_DEBUG ("no previous lower fragment...\n"); + GST_M3U8_CLIENT_UNLOCK (client); + return current_variant; + } + + current_variant = next_variant; + + current_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; + GST_DEBUG ("current_bw in while= %d\n", current_bandwidth); + } + + GST_M3U8_CLIENT_UNLOCK (client); + + /* as condition failed fallback to previous */ + return current_variant; + } + } else { + GST_DEBUG ("No need to switch .... returning current_variant..\n"); + } + + GST_M3U8_CLIENT_UNLOCK (client); + + return current_variant; +} +#endif + +gboolean +gst_m3u8_client_decrypt_init (GstM3U8Client * client, unsigned char *key, gchar *iv) +{ + //if (!client->current->cipher_ctx) + // client->current->cipher_ctx = g_malloc0 (sizeof(EVP_CIPHER_CTX)); + + EVP_CIPHER_CTX_init (&(client->cipher_ctx)); + EVP_CIPHER_CTX_set_padding(&(client->cipher_ctx), 0); // added to check avodining final buffer decryption error + + return EVP_DecryptInit_ex (&(client->cipher_ctx), EVP_aes_128_cbc(), NULL, key, iv); +} + +gboolean +gst_m3u8_client_decrypt_update (GstM3U8Client * client, guint8 * out_data, + gint * out_size, guint8 * in_data, gint in_size) +{ + //g_return_val_if_fail (client->current->cipher_ctx != NULL, FALSE); + + return EVP_DecryptUpdate (&(client->cipher_ctx), out_data, out_size, + in_data, in_size); +} + +gboolean +gst_m3u8_client_decrypt_final (GstM3U8Client * client, guint8 * out_data, + gint * out_size) +{ + //g_return_val_if_fail (client->current->cipher_ctx != NULL, FALSE); + + return EVP_DecryptFinal_ex (&(client->cipher_ctx), out_data, out_size); +} + +gboolean +gst_m3u8_client_decrypt_deinit (GstM3U8Client * client) +{ + //g_return_val_if_fail (client->current->cipher_ctx != NULL, FALSE); + + return EVP_CIPHER_CTX_cleanup (&(client->cipher_ctx)); +} + + +gboolean +gst_m3u8_client_is_playlist_download_needed (GstM3U8Client * self) +{ + /* download is not need, if last_data is present & not live (i.e. we can reuse last VOD child playlist)*/ + if (self->current->last_data && !gst_m3u8_client_is_live(self)) + return FALSE; + else + return TRUE; +} diff --git a/hlsdemux2/src/m3u8.h b/hlsdemux2/src/m3u8.h new file mode 100755 index 0000000..d2a67e2 --- /dev/null +++ b/hlsdemux2/src/m3u8.h @@ -0,0 +1,137 @@ +/* GStreamer + * Copyright (C) 2010 Marc-Andre Lureau + * Copyright (C) 2010 Andoni Morales Alastruey + * Copyright (c) 2000 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * m3u8.h: + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __M3U8_H__ +#define __M3U8_H__ + +#include +#include +#include + +G_BEGIN_DECLS typedef struct _GstM3U8 GstM3U8; +typedef struct _GstM3U8MediaFile GstM3U8MediaFile; +typedef struct _GstM3U8Key GstM3U8Key; +typedef struct _GstM3U8Client GstM3U8Client; + +#define GST_M3U8(m) ((GstM3U8*)m) +#define GST_M3U8_KEY(k) ((GstM3U8Key*)k) +#define GST_M3U8_MEDIA_FILE(f) ((GstM3U8MediaFile*)f) + +#define GST_M3U8_CLIENT_LOCK(c) g_mutex_lock (c->lock); +#define GST_M3U8_CLIENT_UNLOCK(c) g_mutex_unlock (c->lock); + +#define GST_M3U8_IV_LEN 16 + +GST_DEBUG_CATEGORY_EXTERN (hlsdemux2_m3u8_debug); + +typedef enum { + GST_M3U8_ENCRYPTED_NONE, + GST_M3U8_ENCRYPTED_AES_128, +} GstM3U8EncryptionMethod; + +struct _GstM3U8 +{ + gchar *uri; + + gboolean endlist; /* if ENDLIST has been reached */ + gint version; /* last EXT-X-VERSION */ + GstClockTime targetduration; /* last EXT-X-TARGETDURATION */ + gchar *allowcache; /* last EXT-X-ALLOWCACHE */ + + gint bandwidth; + gint program_id; + gchar *codecs; + gint width; + gint height; + GList *files; + + /*< private > */ + gchar *last_data; + GList *lists; /* list of GstM3U8 from the main playlist */ + GList *current_variant; /* Current variant playlist used */ + GstM3U8 *parent; /* main playlist (if any) */ + guint mediasequence; /* EXT-X-MEDIA-SEQUENCE & increased with new media file */ +}; + +struct _GstM3U8MediaFile +{ + gchar *title; + GstClockTime duration; + gchar *uri; + guint sequence; /* the sequence nb of this file */ + gchar *key_url; + unsigned char *key; + unsigned char *iv; +}; + +struct _GstM3U8Key +{ + GstM3U8EncryptionMethod method; + gchar *uri; + guint8 *iv; + guint sequence; + guint8 *data; +}; + +struct _GstM3U8Client +{ + GstM3U8 *main; /* main playlist */ + GstM3U8 *current; + guint update_failed_count; + gint sequence; /* the next sequence for this client */ + GMutex *lock; + EVP_CIPHER_CTX cipher_ctx; +}; + + +GstM3U8Client *gst_m3u8_client_new (const gchar * uri); +void gst_m3u8_client_free (GstM3U8Client * client); +gboolean gst_m3u8_client_update (GstM3U8Client * client, gchar * data); +void gst_m3u8_client_set_current (GstM3U8Client * client, GstM3U8 * m3u8); +gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, GstClockTime cur_running_time, + gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, + GstClockTime * timestamp, gchar **key_uri, gchar **iv); +void gst_m3u8_client_get_current_position (GstM3U8Client * client, + GstClockTime * timestamp); +GstClockTime gst_m3u8_client_get_duration (GstM3U8Client * client); +GstClockTime gst_m3u8_client_get_target_duration (GstM3U8Client * client); +const gchar *gst_m3u8_client_get_uri(GstM3U8Client * client); +const gchar *gst_m3u8_client_get_current_uri(GstM3U8Client * client); +gboolean gst_m3u8_client_has_variant_playlist(GstM3U8Client * client); +gboolean gst_m3u8_client_is_live(GstM3U8Client * client); +GList * gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, + guint bitrate); +gboolean gst_m3u8_client_decrypt_init (GstM3U8Client * client, unsigned char *key, gchar *iv); +gboolean gst_m3u8_client_decrypt_update (GstM3U8Client * client, + guint8 * out_data, gint * out_size, guint8 * in_data, gint in_size); +gboolean gst_m3u8_client_is_playlist_download_needed (GstM3U8Client * self); +GstClockTime gst_m3u8_client_get_last_fragment_duration (GstM3U8Client * client); +gint gst_m3u8_client_get_current_bandwidth (GstM3U8Client * client); +gboolean gst_m3u8_client_decrypt_final (GstM3U8Client * client, guint8 * out_data, gint * out_size); +gboolean gst_m3u8_client_decrypt_deinit (GstM3U8Client * client); +void gst_m3u8_client_decrement_sequence (GstM3U8Client * client); +GList *gst_m3u8_client_get_next_higher_bw_playlist (GstM3U8Client * client); +GList *gst_m3u8_client_get_next_lower_bw_playlist (GstM3U8Client * client); + +G_END_DECLS +#endif /* __M3U8_H__ */ diff --git a/packaging/gst-plugins-ext0.10.spec b/packaging/gst-plugins-ext0.10.spec index e57fa95..46b821d 100644 --- a/packaging/gst-plugins-ext0.10.spec +++ b/packaging/gst-plugins-ext0.10.spec @@ -1,19 +1,33 @@ Name: gst-plugins-ext0.10 -Version: 0.2.4 -Summary: GStreamer extra plugins (common) -Release: 1 -Group: TO_BE/FILLED_IN -License: TO BE FILLED IN +Version: 0.4.88 +Summary: GStreamer extra plugins (common) +Release: 0 +Group: libs +License: LGPL-2.1+ Source0: %{name}-%{version}.tar.gz -BuildRequires: pkgconfig(avsysaudio) BuildRequires: pkgconfig(camsrcjpegenc) +#BuildRequires: pkgconfig(drm-client) +#BuildRequires: pkgconfig(drm-trusted) BuildRequires: pkgconfig(ecore) BuildRequires: pkgconfig(ecore-x) BuildRequires: pkgconfig(evas) -BuildRequires: pkgconfig(mm-ta) +BuildRequires: gst-plugins-base-devel BuildRequires: pkgconfig(gstreamer-plugins-base-0.10) -BuildRequires: pkgconfig(gstreamer-0.10) +BuildRequires: pkgconfig(gstreamer-0.10) BuildRequires: pkgconfig(libexif) +BuildRequires: pkgconfig(libxml-2.0) +BuildRequires: pkgconfig(libdri2) +BuildRequires: pkgconfig(x11) +BuildRequires: pkgconfig(xext) +BuildRequires: pkgconfig(xv) +BuildRequires: pkgconfig(xdamage) +BuildRequires: pkgconfig(libdrm) +BuildRequires: pkgconfig(libtbm) +BuildRequires: pkgconfig(dri2proto) +BuildRequires: pkgconfig(xfixes) +BuildRequires: pkgconfig(libtbm) +BuildRequires: pkgconfig(vconf) +BuildRequires: pkgconfig(libcrypto) %description GStreamer extra plugins (common) @@ -23,18 +37,31 @@ GStreamer extra plugins (common) %build -export CFLAGS+=" -DGST_EXT_TIME_ANALYSIS -DEXPORT_API=\"__attribute__((visibility(\\\"default\\\")))\" " +export CFLAGS+=" -DCONTROL_PAGECACHE -DGST_EXT_TIME_ANALYSIS -DEXPORT_API=\"__attribute__((visibility(\\\"default\\\")))\" " ./autogen.sh --disable-static -%configure --disable-static +%configure --disable-static \ + --enable-ext-hlsdemux2\ + --disable-ext-drmsrc\ +%if 0%{?tizen_build_binary_release_type_eng} + --enable-pcmdump +%endif make %{?jobs:-j%jobs} %install rm -rf %{buildroot} +mkdir -p %{buildroot}/usr/share/license +cp COPYING %{buildroot}/usr/share/license/%{name} %make_install - +mkdir -p %{buildroot}/usr/etc/ +install -m 755 hlsdemux2/predefined_frame/* %{buildroot}/usr/etc/ %files -%defattr(-,root,root,-) +%manifest gst-plugins-ext0.10.manifest +%defattr(-,root,root,-) %{_libdir}/gstreamer-0.10/*.so +/usr/share/license/%{name} +/usr/etc/blackframe_QVGA.264 +/usr/etc/sec_audio_fixed_qvga.264 +/usr/etc/sec_audio_fixed_qvga.jpg diff --git a/pdpushsrc/src/gstpdpushsrc.c b/pdpushsrc/src/gstpdpushsrc.c index a5ed22f..29efdc1 100755 --- a/pdpushsrc/src/gstpdpushsrc.c +++ b/pdpushsrc/src/gstpdpushsrc.c @@ -166,14 +166,14 @@ gst_pd_pushsrc_class_init (GstPDPushSrcClass * klass) "Location of the file to read", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)); - + g_object_class_install_property (gobject_class, ARG_EOS, g_param_spec_boolean ("eos", "EOS recived on downloading pipeline", "download of clip is over", 0, G_PARAM_READWRITE)); - + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_pd_pushsrc_start); gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_pd_pushsrc_stop); gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_pd_pushsrc_is_seekable); @@ -187,7 +187,7 @@ gst_pd_pushsrc_class_init (GstPDPushSrcClass * klass) } GST_LOG ("OUT"); - + } static void @@ -201,7 +201,7 @@ gst_pd_pushsrc_init (GstPDPushSrc * src, GstPDPushSrcClass * g_class) src->uri = NULL; src->is_regular = FALSE; src->is_eos = FALSE; - + gst_pad_set_checkgetrange_function (basesrc->srcpad, GST_DEBUG_FUNCPTR (gst_pd_pushsrc_checkgetrange)); GST_LOG ("OUT"); @@ -255,7 +255,7 @@ gst_pd_pushsrc_set_location (GstPDPushSrc * src, const gchar * location) } g_object_notify (G_OBJECT (src), "location"); gst_uri_handler_new_uri (GST_URI_HANDLER (src), src->uri); - + GST_LOG ("OUT"); return TRUE; @@ -328,17 +328,20 @@ gst_pd_pushsrc_create_read (GstBaseSrc * basesrc, guint64 offset, guint length, GST_LOG ("IN"); int ret; - GstBuffer *buf; + GstBuffer *buf = NULL; struct stat stat_results; GstPDPushSrc *src; + if(length < 1) + return GST_FLOW_ERROR; + src = GST_PD_PUSHSRC_CAST (basesrc); - - GST_LOG_OBJECT (src, "read position = %"G_GUINT64_FORMAT ", offset = %"G_GUINT64_FORMAT", length = %d", - src->read_position, offset, length); - + + GST_LOG_OBJECT (src, "read position = %"G_GUINT64_FORMAT ", offset = %"G_GUINT64_FORMAT", length = %d", + src->read_position, offset, length); + memset (&stat_results, 0, sizeof (stat_results)); - + if (fstat (src->fd, &stat_results) < 0) goto could_not_stat; @@ -363,9 +366,9 @@ gst_pd_pushsrc_create_read (GstBaseSrc * basesrc, guint64 offset, guint length, GST_DEBUG_OBJECT (src, "Going to wait for %ld msec", timeout.tv_usec); ret = select (src->fd + 1, &fds, NULL, NULL, &timeout); - if (-1 == ret) + if (-1 == ret) { - GST_ERROR_OBJECT (src, "ERROR in select () : reason - %s...\n", strerror(errno)); + GST_ERROR_OBJECT (src, "ERROR in select () : reason - %s...\n", g_strerror(errno)); return GST_FLOW_ERROR; } else if (0 == ret) @@ -375,7 +378,7 @@ gst_pd_pushsrc_create_read (GstBaseSrc * basesrc, guint64 offset, guint length, else { memset (&stat_results, 0, sizeof (stat_results)); - + if (fstat (src->fd, &stat_results) < 0) goto could_not_stat; @@ -402,36 +405,33 @@ gst_pd_pushsrc_create_read (GstBaseSrc * basesrc, guint64 offset, guint length, goto seek_failed; src->read_position = offset; } - + buf = gst_buffer_try_new_and_alloc (length); - if (G_UNLIKELY (buf == NULL && length > 0)) { + if (G_UNLIKELY (buf == NULL)) { GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", length); return GST_FLOW_ERROR; } - /* No need to read anything if length is 0 */ - if (length > 0) { - GST_LOG_OBJECT (src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x", - length, offset); - ret = read (src->fd, GST_BUFFER_DATA (buf), length); - if (G_UNLIKELY (ret < 0)) - goto could_not_read; + GST_LOG_OBJECT (src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x", + length, offset); + ret = read (src->fd, GST_BUFFER_DATA (buf), length); + if (G_UNLIKELY (ret < 0)) + goto could_not_read; - /* seekable regular files should have given us what we expected */ - if (G_UNLIKELY ((guint) ret < length && src->seekable)) - goto unexpected_eos; + /* seekable regular files should have given us what we expected */ + if (G_UNLIKELY ((guint) ret < length && src->seekable)) + goto unexpected_eos; - /* other files should eos if they read 0 and more was requested */ - if (G_UNLIKELY (ret == 0 && length > 0)) - goto eos; + /* other files should eos if they read 0 and more was requested */ + if (G_UNLIKELY (ret == 0 && length > 0)) + goto eos; - length = ret; - GST_BUFFER_SIZE (buf) = length; - GST_BUFFER_OFFSET (buf) = offset; - GST_BUFFER_OFFSET_END (buf) = offset + length; + length = ret; + GST_BUFFER_SIZE (buf) = length; + GST_BUFFER_OFFSET (buf) = offset; + GST_BUFFER_OFFSET_END (buf) = offset + length; - src->read_position += length; - } + src->read_position += length; *buffer = buf; GST_LOG ("OUT"); @@ -461,15 +461,15 @@ could_not_read: unexpected_eos: { GST_ERROR_OBJECT (src, "Unexpected EOS occured..."); - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), - ("unexpected end of file.")); + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("unexpected end of file.")); gst_buffer_unref (buf); return GST_FLOW_ERROR; } eos: { GST_ERROR_OBJECT (src, "non-regular file hits EOS"); - gst_buffer_unref (buf); + if (buf) + gst_buffer_unref (buf); return GST_FLOW_UNEXPECTED; } } @@ -562,7 +562,7 @@ gst_pd_pushsrc_get_size (GstBaseSrc * basesrc, guint64 * size) *size = G_MAXUINT64; GST_DEBUG ("size of the file = %"G_GUINT64_FORMAT, *size); - + GST_LOG ("OUT"); return TRUE; @@ -658,7 +658,7 @@ open_failed: } no_stat: { - GST_ERROR_OBJECT (src, "Could not get stat info..."); + GST_ERROR_OBJECT (src, "Could not get stat info..."); GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("Could not get info on \"\"."), (NULL)); close (src->fd); @@ -674,7 +674,7 @@ was_directory: } was_socket: { - GST_ERROR_OBJECT (src, "Is a Socket"); + GST_ERROR_OBJECT (src, "Is a Socket"); GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, ("File \"\" is a socket."), (NULL)); close (src->fd); @@ -772,7 +772,7 @@ gst_pd_pushsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri) #ifdef G_OS_WIN32 /* Unfortunately, g_filename_from_uri() doesn't handle some UNC paths * correctly on windows, it leaves them with an extra backslash - * at the start if they're of the mozilla-style file://///host/path/file + * at the start if they're of the mozilla-style file://///host/path/file * form. Correct this. */ if (location[0] == '\\' && location[1] == '\\' && location[2] == '\\') diff --git a/piffdemux/Makefile.am b/piffdemux/Makefile.am new file mode 100755 index 0000000..308a09c --- /dev/null +++ b/piffdemux/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/piffdemux/src/Makefile.am b/piffdemux/src/Makefile.am new file mode 100755 index 0000000..a966f06 --- /dev/null +++ b/piffdemux/src/Makefile.am @@ -0,0 +1,47 @@ + +plugin_LTLIBRARIES = libgstpiff.la + +libgstpiffincludedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/ + +libgstpiff_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(DRM_CLIENT_CFLAGS) $(DRM_TRUSTED_CFLAGS) + +libgstpiff_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_MAJORMINOR@ \ + -lgstaudio-@GST_MAJORMINOR@ \ + -lgsttag-@GST_MAJORMINOR@ \ + -lgstpbutils-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) $(GST_LIBS) $(ZLIB_LIBS) $(DRM_CLIENT_LIBS) $(DRM_TRUSTED_LIBS) + +libgstpiff_la_LDFLAGS = ${GST_PLUGIN_LDFLAGS} + +libgstpiff_la_SOURCES = piff-plugin.c \ + piffdemux.c \ + piffdemux_types.c \ + piffdemux_dump.c + +libgstpiff_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = \ + piffatomparser.h \ + piffdemux_types.h \ + piffdemux_dump.h \ + piffdemux_fourcc.h \ + piffpalette.h \ + piffcommon.h + +#libgstpiffinclude_HEADERS = piffcommon.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstpiff -:SHARED libgstpiff \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstpiff_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(CPPFLAGS) $(libgstpiff_la_CFLAGS) \ + -:LDFLAGS $(libgstpiff_la_LDFLAGS) \ + $(libgstpiff_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/piffdemux/src/piff-plugin.c b/piffdemux/src/piff-plugin.c new file mode 100755 index 0000000..ee96f6e --- /dev/null +++ b/piffdemux/src/piff-plugin.c @@ -0,0 +1,23 @@ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +//#include "gst/gst-i18n-plugin.h" +#include "piffdemux.h" +#include + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "piffdemux", GST_RANK_PRIMARY, GST_TYPE_PIFFDEMUX)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "piffdemux", + "ISO base media file format support (PIFF)", + plugin_init, VERSION, "LGPL", "Samsung Electronics Co", "http://www.samsung.com"); diff --git a/piffdemux/src/piffatomparser.h b/piffdemux/src/piffatomparser.h new file mode 100755 index 0000000..f90d5db --- /dev/null +++ b/piffdemux/src/piffatomparser.h @@ -0,0 +1,139 @@ +/* GStreamer QuickTime atom parser + * Copyright (C) 2009 Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef QT_ATOM_PARSER_H +#define QT_ATOM_PARSER_H + +#include + +/* our inlined version of GstByteReader */ + +static inline gboolean +piff_atom_parser_has_remaining (GstByteReader * parser, guint64 bytes_needed) +{ + return G_LIKELY (parser->size >= bytes_needed) && + G_LIKELY ((parser->size - bytes_needed) >= parser->byte); +} + +static inline gboolean +piff_atom_parser_has_chunks (GstByteReader * parser, guint32 n_chunks, + guint32 chunk_size) +{ + /* assumption: n_chunks and chunk_size are 32-bit, we cast to 64-bit here + * to avoid overflows, to handle e.g. (guint32)-1 * size correctly */ + return piff_atom_parser_has_remaining (parser, (guint64) n_chunks * chunk_size); +} + +static inline gboolean +piff_atom_parser_peek_sub (GstByteReader * parser, guint offset, guint size, + GstByteReader * sub) +{ + *sub = *parser; + + if (G_UNLIKELY (!gst_byte_reader_skip (sub, offset))) + return FALSE; + + return (gst_byte_reader_get_remaining (sub) >= size); +} + +static inline gboolean +piff_atom_parser_skipn_and_get_uint32 (GstByteReader * parser, + guint bytes_to_skip, guint32 * val) +{ + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < (bytes_to_skip + 4))) + return FALSE; + + gst_byte_reader_skip_unchecked (parser, bytes_to_skip); + *val = gst_byte_reader_get_uint32_be_unchecked (parser); + return TRUE; +} + +/* off_size must be either 4 or 8 */ +static inline gboolean +piff_atom_parser_get_offset (GstByteReader * parser, guint off_size, + guint64 * val) +{ + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < off_size)) + return FALSE; + + if (off_size == sizeof (guint64)) { + *val = gst_byte_reader_get_uint64_be_unchecked (parser); + } else { + *val = gst_byte_reader_get_uint32_be_unchecked (parser); + } + return TRUE; +} + +/* off_size must be either 4 or 8 */ +static inline guint64 +piff_atom_parser_get_offset_unchecked (GstByteReader * parser, guint off_size) +{ + if (off_size == sizeof (guint64)) { + return gst_byte_reader_get_uint64_be_unchecked (parser); + } else { + return gst_byte_reader_get_uint32_be_unchecked (parser); + } +} + +/* size must be from 1 to 4 */ +static inline guint32 +piff_atom_parser_get_uint_with_size_unchecked (GstByteReader * parser, + guint size) +{ + switch (size) { + case 1: + return gst_byte_reader_get_uint8_unchecked (parser); + case 2: + return gst_byte_reader_get_uint16_be_unchecked (parser); + case 3: + return gst_byte_reader_get_uint24_be_unchecked (parser); + case 4: + return gst_byte_reader_get_uint32_be_unchecked (parser); + default: + g_assert_not_reached (); + gst_byte_reader_skip_unchecked (parser, size); + break; + } + return 0; +} + +static inline gboolean +piff_atom_parser_get_fourcc (GstByteReader * parser, guint32 * fourcc) +{ + guint32 f_be; + + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < 4)) + return FALSE; + + f_be = gst_byte_reader_get_uint32_be_unchecked (parser); + *fourcc = GUINT32_SWAP_LE_BE (f_be); + return TRUE; +} + +static inline guint32 +piff_atom_parser_get_fourcc_unchecked (GstByteReader * parser) +{ + guint32 fourcc; + + fourcc = gst_byte_reader_get_uint32_be_unchecked (parser); + return GUINT32_SWAP_LE_BE (fourcc); +} + +#endif /* QT_ATOM_PARSER_H */ diff --git a/piffdemux/src/piffcommon.h b/piffdemux/src/piffcommon.h new file mode 100755 index 0000000..e24a19c --- /dev/null +++ b/piffdemux/src/piffcommon.h @@ -0,0 +1,29 @@ + +#ifndef __GST_PIFFCOMMON_H__ +#define __GST_PIFFCOMMON_H__ + +G_BEGIN_DECLS + +typedef struct _piff_fragment_longtime_info_t +{ + guint64 ts; + guint64 duration; +}piff_fragment_longtime_info; + +typedef struct _piff_fragment_time_info_t +{ + guint32 ts; + guint32 duration; +}piff_fragment_time_info; + +typedef struct _live_param_t +{ + gboolean is_eos; /* is live session ended */ + guint count; /* fragment parameters count */ + gchar *media_type; + piff_fragment_time_info *info; + piff_fragment_longtime_info *long_info; +}piff_live_param_t; +G_END_DECLS + +#endif /* __GST_PIFFPALETTE_H__ */ diff --git a/piffdemux/src/piffdemux.c b/piffdemux/src/piffdemux.c new file mode 100755 index 0000000..aa8c196 --- /dev/null +++ b/piffdemux/src/piffdemux.c @@ -0,0 +1,3273 @@ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "piffdemux.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PIFF_DEFAULT_TRACKID -1 +#define PIFF_DEFAULT_FOURCC 0 +#define PIFF_DEFAULT_TIMESCALE 10000000 +#define PIFF_DEFAULT_DURATION -1 +#define PIFF_DEFAULT_START_TS 0 +#define PIFF_DEFAULT_START_TS 0 + +#define PIFF_DEFAULT_WIDTH 16 +#define PIFF_DEFAULT_HEIGHT 16 +#define PIFF_DEFAULT_BPS 16 + +#undef DEC_OUT_FRAME_DUMP + +#ifdef DEC_OUT_FRAME_DUMP +#include +FILE *piffdump = NULL; +#endif + +#define PIFFDEMUX_RB16(x) ((((const unsigned char*)(x))[0] << 8) | ((const unsigned char*)(x))[1]) +/* max. size considered 'sane' for non-mdat atoms */ +#define PIFFDEMUX_MAX_ATOM_SIZE (25*1024*1024) + +/* if the sample index is larger than this, something is likely wrong */ +#define PIFFDEMUX_MAX_SAMPLE_INDEX_SIZE (50*1024*1024) + +GST_DEBUG_CATEGORY (piffdemux_debug); + +typedef struct _PiffDemuxSegment PiffDemuxSegment; +typedef struct _PiffDemuxSample PiffDemuxSample; +typedef struct _PiffDemuxSubSampleEncryption PiffDemuxSubSampleEncryption; +typedef struct _PiffDemuxSubSampleEntryInfo PiffDemuxSubSampleEntryInfo; + +enum +{ + PROR_PIFF_0, + PROP_PIFF_MEDIA_CAPS, + PROP_PIFF_MEDIA_TIMESCALE, + PROP_PIFF_MEDIA_DURATION, + PROP_PIFF_MEDIA_START_TIMESTAMP, + PROP_PIFF_IS_LIVE, + PROP_PIFF_LOOKAHEAD_COUNT, + PROP_PIFF_AVG_FRAME_DUR, +#ifdef DRM_ENABLE + PROP_PROTECTION_HEADER_BUFFER, +#endif +}; + +enum +{ + SIGNAL_LIVE_PARAM, + LAST_SIGNAL +}; + +static guint gst_piffdemux_signals[LAST_SIGNAL] = { 0 }; + +struct _PiffDemuxSubSampleEntryInfo +{ + guint16 LenofClearData; + guint32 LenofEncryptData; +}; + +struct _PiffDemuxSubSampleEncryption +{ + guint16 n_entries; + PiffDemuxSubSampleEntryInfo *sub_entry; +}; + +struct _PiffDemuxSample +{ + guint32 size; + gint32 pts_offset; /* Add this value to timestamp to get the pts */ + guint64 offset; + guint64 timestamp; /* DTS In mov time */ + guint32 duration; /* In mov time */ + gboolean keyframe; /* TRUE when this packet is a keyframe */ + guint8 *iv; /* initialization vector for decryption*/ + PiffDemuxSubSampleEncryption *sub_encry; +}; + +/* timestamp is the DTS */ +#define PIFFSAMPLE_DTS(stream,sample) gst_util_uint64_scale ((sample)->timestamp,\ + GST_SECOND, (stream)->timescale) +/* timestamp + offset is the PTS */ +#define PIFFSAMPLE_PTS(stream,sample) gst_util_uint64_scale ((sample)->timestamp + \ + (sample)->pts_offset, GST_SECOND, (stream)->timescale) +/* timestamp + duration - dts is the duration */ +#define PIFFSAMPLE_DUR_DTS(stream,sample,dts) (gst_util_uint64_scale ((sample)->timestamp + \ + (sample)->duration, GST_SECOND, (stream)->timescale) - (dts)); +/* timestamp + offset + duration - pts is the duration */ +#define PIFFSAMPLE_DUR_PTS(stream,sample,pts) (gst_util_uint64_scale ((sample)->timestamp + \ + (sample)->pts_offset + (sample)->duration, GST_SECOND, (stream)->timescale) - (pts)); + +#define PIFFSAMPLE_KEYFRAME(stream,sample) ((sample)->keyframe); + +typedef char uuid_t[16]; + +static const uuid_t tfxd_uuid = { 0x6d, 0x1d, 0x9b, 0x05, + 0x42, 0xd5, 0x44, 0xe6, + 0x80, 0xe2, 0x14, 0x1d, + 0xaf, 0xf7, 0x57, 0xb2 }; + +static const uuid_t tfrf_uuid = { 0xd4, 0x80, 0x7e, 0xf2, + 0xca, 0x39, 0x46, 0x95, + 0x8e, 0x54, 0x26, 0xcb, + 0x9e, 0x46, 0xa7, 0x9f }; + +static const uuid_t encrypt_uuid = { 0xa2, 0x39, 0x4f, 0x52, + 0x5a, 0x9b, 0x4f, 0x14, + 0xa2, 0x44, 0x6c, 0x42, + 0x7c, 0x64, 0x8d, 0xf4 }; + +#define SE_OVERRIDE_TE_FLAGS 0x000001 +#define SE_USE_SUBSAMPLE_ENCRYPTION 0x000002 + +typedef enum +{ + UUID_UNKNOWN = -1, + UUID_TFXD, + UUID_TFRF, + UUID_SAMPLE_ENCRYPT, +}uuid_type_t; + +struct _PiffDemuxSegment +{ + /* global time and duration, all gst time */ + guint64 time; + guint64 stop_time; + guint64 duration; + /* media time of trak, all gst time */ + guint64 media_start; + guint64 media_stop; + gdouble rate; +}; + + +struct _PiffDemuxStream +{ + /* stream type */ + guint32 subtype; + GstCaps *caps; + guint32 fourcc; + + /* duration/scale */ + guint64 duration; /* in timescale */ + guint32 timescale; + + /* our samples */ + guint32 n_samples; + PiffDemuxSample *samples; + guint32 min_duration; /* duration in timescale of first sample, used for figuring out + the framerate, in timescale units */ + + /* if we use chunks or samples */ + gboolean sampled; + guint padding; + + /* when a discontinuity is pending */ + gboolean discont; + + /* list of buffers to push first */ + GSList *buffers; + + /* buffer needs some custom processing, e.g. subtitles */ + gboolean need_process; + + /* current position */ + guint32 segment_index; + guint32 sample_index; + guint64 time_position; /* in gst time */ + + /* the Gst segment we are processing out, used for clipping */ + GstSegment segment; + + /* last GstFlowReturn */ + GstFlowReturn last_ret; + + + /* quicktime segments */ + guint32 n_segments; + PiffDemuxSegment *segments; + guint32 from_sample; + guint32 to_sample; + + gboolean sent_eos; + GstTagList *pending_tags; + gboolean send_global_tags; + + GstEvent *pending_event; + + gboolean sent_nsevent; + + guint64 start_ts; + + guint64 avg_dur; /* average frame duration */ +}; + + +enum PiffDemuxState +{ + PIFFDEMUX_STATE_INITIAL, /* Initial state (haven't got the header yet) */ + PIFFDEMUX_STATE_HEADER, /* Parsing the header */ + PIFFDEMUX_STATE_MOVIE, /* Parsing/Playing the media data */ + PIFFDEMUX_STATE_BUFFER_MDAT /* Buffering the mdat atom */ +}; + + +static GNode *piffdemux_tree_get_child_by_type (GNode * node, guint32 fourcc); +static GNode *piffdemux_tree_get_child_by_type_full (GNode * node, + guint32 fourcc, GstByteReader * parser); +static GNode *piffdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc); +static GNode *piffdemux_tree_get_sibling_by_type_full (GNode * node, + guint32 fourcc, GstByteReader * parser); + +static GstStaticPadTemplate gst_piffdemux_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-piff") + ); + +static GstStaticPadTemplate gst_piffdemux_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + +GST_BOILERPLATE (GstPiffDemux, gst_piffdemux, GstPiffDemux, GST_TYPE_ELEMENT); + +static void gst_piffdemux_dispose (GObject * object); + +static GstStateChangeReturn gst_piffdemux_change_state (GstElement * element, + GstStateChange transition); +static void +gst_piffdemux_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void +gst_piffdemux_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static GstFlowReturn gst_piffdemux_chain (GstPad * sinkpad, GstBuffer * inbuf); +static gboolean gst_piffdemux_handle_sink_event (GstPad * pad, GstEvent * event); +static gboolean piffdemux_parse_node (GstPiffDemux * piffdemux, GNode * node, const guint8 * buffer, guint length); +static gboolean piffdemux_parse_sample_encryption(GstPiffDemux * piffdemux, GstByteReader *sample_encrypt, PiffDemuxStream * stream); +static gboolean piffdemux_parse_mfhd (GstPiffDemux * piffdemux, GstByteReader * mfhd); +static gboolean gst_piffdemux_handle_src_event (GstPad * pad, GstEvent * event); +static const GstQueryType *gst_piffdemux_get_src_query_types (GstPad * pad); +static gboolean gst_piffdemux_handle_src_query (GstPad * pad, GstQuery * query); + +#ifdef DRM_ENABLE +static void piffdemux_get_playready_licence (GstPiffDemux *demux); +void test_drm_trusted_operation_cb(drm_trusted_user_operation_info_s *operation_info, void *output_data); +#endif + +static gboolean +ConvertH264_MetaDCI_to_3GPPDCI(unsigned char *dci_meta_buf, unsigned int dci_meta_size, unsigned char **dci_3gpp_buf, unsigned int *dci_3gpp_size); +void +__gst_piffdemux_marshal_BOOLEAN__OBJECT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data); + +static void +gst_piffdemux_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_piffdemux_sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_piffdemux_src_template)); + gst_element_class_set_details_simple (element_class, "PIFF demuxer", + "Codec/Parser", + "Parser for PIFF file format", + "naveen ch "); + + GST_DEBUG_CATEGORY_INIT (piffdemux_debug, "piffdemux", 0, "piffdemux plugin"); +} + +static void +gst_piffdemux_class_init (GstPiffDemuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_piffdemux_dispose; + gobject_class->set_property = gst_piffdemux_set_property; + gobject_class->get_property = gst_piffdemux_get_property; + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_piffdemux_change_state); + + g_object_class_install_property (gobject_class, PROP_PIFF_MEDIA_CAPS, + g_param_spec_boxed ("caps", "Caps", + "The allowed caps for the src pad", GST_TYPE_CAPS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* timescale of media to be set by application */ + g_object_class_install_property (gobject_class, PROP_PIFF_MEDIA_TIMESCALE, + g_param_spec_uint64 ("timescale", "media timescale", + "media timescale in PIFF Manifest", 0, G_MAXUINT64, + PIFF_DEFAULT_TIMESCALE, + G_PARAM_READWRITE)); +#ifdef DRM_ENABLE + g_object_class_install_property (gobject_class, PROP_PROTECTION_HEADER_BUFFER, + gst_param_spec_mini_object ("protection-header", "protection header buffer", + "protection header used for playready", GST_TYPE_BUFFER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif + g_object_class_install_property (gobject_class, PROP_PIFF_MEDIA_DURATION, + g_param_spec_int64 ("duration", "Duration of media", + "Total duration of the content", -1, G_MAXINT64, + PIFF_DEFAULT_DURATION, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PIFF_MEDIA_START_TIMESTAMP, + g_param_spec_uint64 ("start-ts", "expected start timestamp", + "expected start timestamp to avoid reset", 0, G_MAXUINT64, + PIFF_DEFAULT_START_TS, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PIFF_IS_LIVE, + g_param_spec_boolean ("is-live", "Is presentation is Live or VOD", + "If Presentation is Live (true) else VOD (false)", + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PIFF_LOOKAHEAD_COUNT, + g_param_spec_uint ("lookahead-count", "Lookahead count value", + "Look ahead count used in case of Live presentation", 0, G_MAXUINT, + 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_PIFF_AVG_FRAME_DUR, + g_param_spec_uint64 ("frame-dur", "Average frame duration", + "Average frame duration", 0, G_MAXUINT64, + G_MAXUINT64, + G_PARAM_READABLE)); + + gst_piffdemux_signals[SIGNAL_LIVE_PARAM] = g_signal_new ("live-param", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstPiffDemuxClass, live_param), NULL, NULL, + g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + +} + + +static void +gst_piffdemux_init (GstPiffDemux * piffdemux, GstPiffDemuxClass * klass) +{ + /* sink pad */ + piffdemux->sinkpad = gst_pad_new_from_static_template (&gst_piffdemux_sink_template, "sink"); + gst_pad_set_chain_function (piffdemux->sinkpad, gst_piffdemux_chain); + gst_pad_set_event_function (piffdemux->sinkpad, gst_piffdemux_handle_sink_event); + gst_element_add_pad (GST_ELEMENT_CAST (piffdemux), piffdemux->sinkpad); + + /* source pad */ + piffdemux->srcpad = gst_pad_new_from_static_template (&gst_piffdemux_src_template, "src"); + gst_pad_set_event_function (piffdemux->srcpad, gst_piffdemux_handle_src_event); + gst_pad_use_fixed_caps (piffdemux->srcpad); + gst_pad_set_query_type_function (piffdemux->srcpad, gst_piffdemux_get_src_query_types); + gst_pad_set_query_function (piffdemux->srcpad, gst_piffdemux_handle_src_query); + gst_element_add_pad (GST_ELEMENT_CAST (piffdemux), piffdemux->srcpad); + + piffdemux->stream = g_new0 (PiffDemuxStream, 1); + piffdemux->stream->fourcc = PIFF_DEFAULT_FOURCC; + piffdemux->stream->timescale = PIFF_DEFAULT_TIMESCALE; + piffdemux->stream->duration = PIFF_DEFAULT_DURATION; + piffdemux->stream->caps = NULL; + piffdemux->stream->discont = TRUE; + piffdemux->stream->need_process = FALSE; + piffdemux->stream->segment_index = -1; + piffdemux->stream->time_position = 0; + piffdemux->stream->sample_index = -1; + piffdemux->stream->last_ret = GST_FLOW_OK; + piffdemux->stream->sent_nsevent = FALSE; + piffdemux->stream->start_ts = PIFF_DEFAULT_START_TS; + piffdemux->stream->avg_dur = -1; + + piffdemux->state = PIFFDEMUX_STATE_INITIAL; + piffdemux->neededbytes = 16; + piffdemux->todrop = 0; + piffdemux->adapter = gst_adapter_new (); + piffdemux->offset = 0; + piffdemux->first_mdat = -1; + piffdemux->mdatoffset = GST_CLOCK_TIME_NONE; + piffdemux->mdatbuffer = NULL; + piffdemux->moof_rcvd = FALSE; + piffdemux->is_live = FALSE; + piffdemux->lookahead_cnt = 0; +#ifdef DRM_ENABLE + piffdemux->pr_handle = NULL; +#endif + piffdemux->decrypt_init = FALSE; + piffdemux->encrypt_content = FALSE; + +#ifdef DEC_OUT_FRAME_DUMP + piffdump = fopen ("/opt/media/piff_out_dump.dmp", "w+"); + if (piffdump == NULL) + { + g_print ("\nNot able to create frame dump file\n"); + } +#endif + + gst_segment_init (&piffdemux->segment, GST_FORMAT_TIME); +} + +static void +gst_piffdemux_dispose (GObject * object) +{ + GstPiffDemux *piffdemux = GST_PIFFDEMUX (object); + + if (piffdemux->adapter) { + g_object_unref (G_OBJECT (piffdemux->adapter)); + piffdemux->adapter = NULL; + } + +#ifdef DEC_OUT_FRAME_DUMP + { + fclose (piffdump); + piffdump = NULL; + } +#endif + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +static void +gst_piffdemux_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstPiffDemux *piffdemux = GST_PIFFDEMUX (object); + + switch (prop_id) { + case PROP_PIFF_MEDIA_CAPS: { + if (piffdemux->stream->caps) + gst_caps_unref(piffdemux->stream->caps); + piffdemux->stream->caps = gst_caps_copy (gst_value_get_caps (value)); + gchar *caps_string = gst_caps_to_string(piffdemux->stream->caps); + GST_DEBUG_OBJECT (piffdemux, "stream caps = %s", caps_string); + g_free(caps_string); + caps_string = NULL; + if (!gst_pad_set_caps(piffdemux->srcpad, piffdemux->stream->caps)) { + GST_ERROR_OBJECT (piffdemux, "not able to set caps..."); + } + break; + } + case PROP_PIFF_MEDIA_TIMESCALE: + piffdemux->stream->timescale = g_value_get_uint64(value); + break; + case PROP_PIFF_MEDIA_DURATION: + piffdemux->stream->duration = g_value_get_int64(value); + break; + case PROP_PIFF_MEDIA_START_TIMESTAMP: + piffdemux->stream->start_ts = g_value_get_uint64(value); + GST_INFO_OBJECT (piffdemux, "start_ts = %"GST_TIME_FORMAT, GST_TIME_ARGS(piffdemux->stream->start_ts)); + break; + case PROP_PIFF_IS_LIVE: + piffdemux->is_live = g_value_get_boolean(value); + break; + case PROP_PIFF_LOOKAHEAD_COUNT: + piffdemux->lookahead_cnt = g_value_get_uint(value); + GST_DEBUG_OBJECT (piffdemux, "Look ahead count = %d", piffdemux->lookahead_cnt); + break; +#ifdef DRM_ENABLE + case PROP_PROTECTION_HEADER_BUFFER: + piffdemux->protection_header = gst_value_get_buffer(value); + piffdemux_get_playready_licence (piffdemux); + break; +#endif + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_piffdemux_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstPiffDemux *piffdemux = GST_PIFFDEMUX (object); + + switch (prop_id) { + case PROP_PIFF_MEDIA_CAPS: + gst_value_set_caps (value, piffdemux->stream->caps); + break; + case PROP_PIFF_MEDIA_TIMESCALE: + g_value_set_uint64 (value, piffdemux->stream->timescale); + break; + case PROP_PIFF_MEDIA_DURATION: + g_value_set_int64 (value, piffdemux->stream->duration); + break; + case PROP_PIFF_MEDIA_START_TIMESTAMP: + g_value_set_uint64 (value, piffdemux->stream->start_ts); + break; + case PROP_PIFF_IS_LIVE: + g_value_set_boolean(value, piffdemux->is_live); + break; + case PROP_PIFF_LOOKAHEAD_COUNT: + g_value_set_uint (value, piffdemux->lookahead_cnt); + break; + case PROP_PIFF_AVG_FRAME_DUR: + g_value_set_uint64 (value, piffdemux->stream->avg_dur); + break; +#ifdef DRM_ENABLE + case PROP_PROTECTION_HEADER_BUFFER: + gst_value_take_buffer (value, piffdemux->protection_header); + break; +#endif + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_piffdemux_post_no_playable_stream_error (GstPiffDemux * piffdemux) +{ + if (piffdemux->posted_redirect) { + GST_ELEMENT_ERROR (piffdemux, STREAM, DEMUX, + ("This file contains no playable streams."), + ("no known streams found, a redirect message has been posted")); + } else { + GST_ELEMENT_ERROR (piffdemux, STREAM, DEMUX, + ("This file contains no playable streams."), + ("no known streams found")); + } + +} + +static gboolean +gst_piffdemux_src_convert (GstPad * pad, GstFormat src_format, gint64 src_value, + GstFormat dest_format, gint64 * dest_value) +{ + gboolean res = TRUE; + PiffDemuxStream *stream = gst_pad_get_element_private (pad); + GstPiffDemux *piffdemux = GST_PIFFDEMUX (gst_pad_get_parent (pad)); + + if (stream->subtype != FOURCC_vide) { + res = FALSE; + goto done; + } + + switch (src_format) { + case GST_FORMAT_TIME: + switch (dest_format) { + case GST_FORMAT_BYTES:{ + + break; + } + default: + res = FALSE; + break; + } + break; + case GST_FORMAT_BYTES: + switch (dest_format) { + case GST_FORMAT_TIME:{ + + break; + } + default: + res = FALSE; + break; + } + break; + default: + res = FALSE; + } + +done: + gst_object_unref (piffdemux); + + return res; +} + +static const GstQueryType * +gst_piffdemux_get_src_query_types (GstPad * pad) +{ + static const GstQueryType src_types[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_CONVERT, + GST_QUERY_FORMATS, + GST_QUERY_SEEKING, + 0 + }; + + return src_types; +} + +static gboolean +gst_piffdemux_get_duration (GstPiffDemux * piffdemux, gint64 * duration) +{ + gboolean res = TRUE; + + *duration = GST_CLOCK_TIME_NONE; + + if (piffdemux->stream->duration != 0) { + if (piffdemux->stream->duration != G_MAXINT64 && piffdemux->stream->timescale != 0) { + *duration = gst_util_uint64_scale (piffdemux->stream->duration, + GST_SECOND, piffdemux->stream->timescale); + } + } + return res; +} + +static gboolean +gst_piffdemux_handle_src_query (GstPad * pad, GstQuery * query) +{ + gboolean res = FALSE; + GstPiffDemux *piffdemux = GST_PIFFDEMUX (gst_pad_get_parent (pad)); + + GST_LOG_OBJECT (pad, "%s query", GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + GST_ERROR ("Querying POSITION from piffdemux...."); + if (GST_CLOCK_TIME_IS_VALID (piffdemux->segment.last_stop)) { + gst_query_set_position (query, GST_FORMAT_TIME, + piffdemux->segment.last_stop); + res = TRUE; + } + break; + case GST_QUERY_DURATION:{ + GstFormat fmt; + GST_ERROR ("Querying DURATION from piffdemux...."); + + gst_query_parse_duration (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME) { + gint64 duration = -1; + + gst_piffdemux_get_duration (piffdemux, &duration); + if (duration > 0) { + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + res = TRUE; + } + } + break; + } + case GST_QUERY_CONVERT:{ + GstFormat src_fmt, dest_fmt; + gint64 src_value, dest_value = 0; + + gst_query_parse_convert (query, &src_fmt, &src_value, &dest_fmt, NULL); + + res = gst_piffdemux_src_convert (pad, + src_fmt, src_value, dest_fmt, &dest_value); + if (res) { + gst_query_set_convert (query, src_fmt, src_value, dest_fmt, dest_value); + res = TRUE; + } + break; + } + case GST_QUERY_FORMATS: + gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); + res = TRUE; + break; + case GST_QUERY_SEEKING:{ + + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (piffdemux); + + return res; +} + + +static void +gst_piffdemux_push_tags (GstPiffDemux * piffdemux, PiffDemuxStream * stream) +{ + if (G_UNLIKELY (stream->pending_tags)) { + GST_DEBUG_OBJECT (piffdemux, "Sending tags %" GST_PTR_FORMAT, + stream->pending_tags); + gst_pad_push_event (piffdemux->srcpad, + gst_event_new_tag (stream->pending_tags)); + stream->pending_tags = NULL; + } + + if (G_UNLIKELY (stream->send_global_tags && piffdemux->tag_list)) { + GST_DEBUG_OBJECT (piffdemux, "Sending global tags %" GST_PTR_FORMAT, + piffdemux->tag_list); + gst_pad_push_event (piffdemux->srcpad, + gst_event_new_tag (gst_tag_list_copy (piffdemux->tag_list))); + stream->send_global_tags = FALSE; + } +} + + +static void +gst_piffdemux_push_event (GstPiffDemux * piffdemux, GstEvent * event) +{ + GstEventType etype = GST_EVENT_TYPE (event); + + GST_DEBUG_OBJECT (piffdemux, "pushing %s event on source pad", + GST_EVENT_TYPE_NAME (event)); + + if (piffdemux->stream->sent_eos) { + GST_INFO_OBJECT (piffdemux, "already sent eos"); + return; + } + + if (!gst_pad_push_event (piffdemux->srcpad, event)) { + GST_ERROR_OBJECT (piffdemux, "error in sending event to srcpad..."); + } + + if (etype == GST_EVENT_EOS) + piffdemux->stream->sent_eos = TRUE; +} + + +/* find the segment for @time_position for @stream + * + * Returns -1 if the segment cannot be found. + */ +static guint32 +gst_piffdemux_find_segment (GstPiffDemux * piffdemux, PiffDemuxStream * stream, + guint64 time_position) +{ + gint i; + guint32 seg_idx; + + GST_LOG_OBJECT (piffdemux, "finding segment for %" GST_TIME_FORMAT, + GST_TIME_ARGS (time_position)); + + /* find segment corresponding to time_position if we are looking + * for a segment. */ + seg_idx = -1; + for (i = 0; i < stream->n_segments; i++) { + PiffDemuxSegment *segment = &stream->segments[i]; + + GST_LOG_OBJECT (piffdemux, + "looking at segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->stop_time)); + + /* For the last segment we include stop_time in the last segment */ + if (i < stream->n_segments - 1) { + if (segment->time <= time_position && time_position < segment->stop_time) { + GST_LOG_OBJECT (piffdemux, "segment %d matches", i); + seg_idx = i; + break; + } + } else { + if (segment->time <= time_position && time_position <= segment->stop_time) { + GST_LOG_OBJECT (piffdemux, "segment %d matches", i); + seg_idx = i; + break; + } + } + } + return seg_idx; +} + + +static gboolean +gst_piffdemux_handle_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstPiffDemux *piffdemux = GST_PIFFDEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_QOS: + case GST_EVENT_NAVIGATION: + res = FALSE; + gst_event_unref (event); + break; + case GST_EVENT_SEEK: + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (piffdemux); + + return res; +} + + +static void +gst_piffdemux_move_stream (GstPiffDemux * piffdemux, PiffDemuxStream * str, + guint32 index) +{ + /* no change needed */ + if (index == str->sample_index) + return; + + GST_DEBUG_OBJECT (piffdemux, "moving to sample %u of %u", index, + str->n_samples); + + /* position changed, we have a discont */ + str->sample_index = index; + /* Each time we move in the stream we store the position where we are + * starting from */ + str->from_sample = index; + str->discont = TRUE; +} + +// TODO: need to check more on this below function +/* stream/index return sample that is min/max w.r.t. byte position, + * time is min/max w.r.t. time of samples, + * the latter need not be time of the former sample */ +static void +gst_piffdemux_find_sample (GstPiffDemux * piffdemux, gint64 byte_pos, gboolean fw, + gboolean set, PiffDemuxStream ** _stream, gint * _index, gint64 * _time) +{ + gint i, index; + gint64 time, min_time; + PiffDemuxStream *stream; + PiffDemuxStream *str = piffdemux->stream; + gint inc; + gboolean set_sample; + + min_time = -1; + stream = NULL; + index = -1; + + set_sample = !set; + if (fw) { + i = 0; + inc = 1; + } else { + i = str->n_samples - 1; + inc = -1; + } + + for (; (i >= 0) && (i < str->n_samples); i += inc) { + if (str->samples[i].size && + ((fw && (str->samples[i].offset >= byte_pos)) || + (!fw && + (str->samples[i].offset + str->samples[i].size <= + byte_pos)))) { + /* move stream to first available sample */ + if (set) { + gst_piffdemux_move_stream (piffdemux, str, i); + set_sample = TRUE; + } + /* determine min/max time */ + time = str->samples[i].timestamp + str->samples[i].pts_offset; + time = gst_util_uint64_scale (time, GST_SECOND, str->timescale); + /*if (min_time == -1 || (!fw && time > min_time) || + (fw && time < min_time)) : Dead code*/ { + min_time = time; + } + index = i; + break; + } + } + /* no sample for this stream, mark eos */ + if (!set_sample) + gst_piffdemux_move_stream (piffdemux, str, str->n_samples); + + if (_time) + *_time = min_time; + if (_stream) + *_stream = str; + if (_index) + *_index = index; +} + + +static gboolean +gst_piffdemux_handle_sink_event (GstPad * sinkpad, GstEvent * event) +{ + GstPiffDemux *demux = GST_PIFFDEMUX (GST_PAD_PARENT (sinkpad)); + gboolean res; + + GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time, offset = 0; + PiffDemuxStream *stream; + gint idx; + gboolean update; + GstSegment segment; + + /* some debug output */ + gst_segment_init (&segment, GST_FORMAT_UNDEFINED); + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + gst_segment_set_newsegment_full (&segment, update, rate, arate, format, + start, stop, time); + GST_ERROR_OBJECT (demux, + "received format %d newsegment %" GST_SEGMENT_FORMAT, format, + &segment); + + /* chain will send initial newsegment after pads have been added */ + if (demux->state != PIFFDEMUX_STATE_MOVIE ) { + GST_DEBUG_OBJECT (demux, "still starting, eating event"); + goto exit; + } + + /* we only expect a BYTE segment, e.g. following a seek */ + if (format == GST_FORMAT_BYTES) { + if (start > 0) { + gint64 requested_seek_time; + guint64 seek_offset; + + offset = start; + + GST_OBJECT_LOCK (demux); + requested_seek_time = demux->requested_seek_time; + seek_offset = demux->seek_offset; + demux->requested_seek_time = -1; + demux->seek_offset = -1; + GST_OBJECT_UNLOCK (demux); + + if (offset == seek_offset) { + start = requested_seek_time; + } else { + gst_piffdemux_find_sample (demux, start, TRUE, FALSE, NULL, NULL, + &start); + start = MAX (start, 0); + } + } + if (stop > 0) { + gst_piffdemux_find_sample (demux, stop, FALSE, FALSE, NULL, NULL, + &stop); + /* keyframe seeking should already arrange for start >= stop, + * but make sure in other rare cases */ + stop = MAX (stop, start); + } + } +#if 0 + else if (format == GST_FORMAT_TIME) { + // Supporting TIME_FORMAT for new_segment + //gst_piffdemux_push_event (demux,event); + PiffDemuxStream *stream = NULL; + int i = -1; + + demux->neededbytes = 16; + demux->state = PIFFDEMUX_STATE_INITIAL; + demux->offset = 0; + + /* Figure out which stream this is packet belongs to */ + for (i = 0; i < demux->n_streams; i++) { + stream = demux->streams[i]; + stream->last_ts = start; + stream->discont = TRUE; + stream->sample_index = stream->n_samples; + } + + /* accept upstream's notion of segment and distribute along */ + gst_segment_set_newsegment_full (&demux->segment, update, rate, arate, + GST_FORMAT_TIME, start, stop, start); + GST_ERROR_OBJECT (demux, "Pushing newseg update %d, rate %g, " + "applied rate %g, format %d, start %" GST_TIME_FORMAT ", " + "stop %" GST_TIME_FORMAT, update, rate, arate, GST_FORMAT_TIME, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + gst_piffdemux_push_event (demux, + gst_event_new_new_segment_full (update, rate, arate, GST_FORMAT_TIME, start, stop, start)); + + /* clear leftover in current segment, if any */ + gst_adapter_clear (demux->adapter); + + goto exit; + } +#endif + else { + GST_DEBUG_OBJECT (demux, "unsupported segment format, ignoring"); + goto exit; + } + + /* accept upstream's notion of segment and distribute along */ + gst_segment_set_newsegment_full (&demux->segment, update, rate, arate, + GST_FORMAT_TIME, start, stop, start); + GST_ERROR_OBJECT (demux, "Pushing newseg update %d, rate %g, " + "applied rate %g, format %d, start %" GST_TIME_FORMAT ", " + "stop %" GST_TIME_FORMAT, update, rate, arate, GST_FORMAT_TIME, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + gst_piffdemux_push_event (demux, + gst_event_new_new_segment_full (update, rate, arate, GST_FORMAT_TIME, + start, stop, start)); + + /* clear leftover in current segment, if any */ + gst_adapter_clear (demux->adapter); + /* set up streaming thread */ + gst_piffdemux_find_sample (demux, offset, TRUE, TRUE, &stream, &idx, NULL); + demux->offset = offset; + if (stream) { + demux->todrop = stream->samples[idx].offset - offset; + demux->neededbytes = demux->todrop + stream->samples[idx].size; + } else { + /* set up for EOS */ + demux->neededbytes = -1; + demux->todrop = 0; + } + exit: + gst_event_unref (event); + res = TRUE; + goto drop; + break; + } + case GST_EVENT_FLUSH_STOP: + { + /* clean up, force EOS if no more info follows */ + gst_adapter_clear (demux->adapter); + demux->offset = 0; + demux->neededbytes = -1; + /* reset flow return, e.g. following seek */ + demux->stream->last_ret = GST_FLOW_OK; + demux->stream->sent_eos = FALSE; + break; + } + case GST_EVENT_EOS: + break; + default: + break; + } + + res = gst_pad_event_default (demux->sinkpad, event); + +drop: + return res; +} + + +static void +gst_piffdemux_stream_free (GstPiffDemux * piffdemux, PiffDemuxStream * stream) +{ + int i = 0; + + g_return_if_fail (stream != NULL); + + while (stream->buffers) { + gst_buffer_unref (GST_BUFFER_CAST (stream->buffers->data)); + stream->buffers = g_slist_delete_link (stream->buffers, stream->buffers); + } + + for (i = 0; i < stream->n_samples; i++) { + if (stream->samples[i].iv) { + free (stream->samples[i].iv); + stream->samples[i].iv = NULL; + } + if (stream->samples[i].sub_encry) { + if (stream->samples[i].sub_encry->sub_entry) { + g_free (stream->samples[i].sub_encry->sub_entry); + stream->samples[i].sub_encry->sub_entry = NULL; + } + + free (stream->samples[i].sub_encry); + stream->samples[i].sub_encry = NULL; + } + } + + if (stream->samples) { + g_free (stream->samples); + stream->samples = NULL; + } + if (stream->caps) { + gst_caps_unref (stream->caps); + stream->caps = NULL; + } + if (stream->segments) { + g_free (stream->segments); + stream->segments = NULL; + } + if (stream->pending_tags) { + gst_tag_list_free (stream->pending_tags); + stream->pending_tags = NULL; + } + g_free (stream); +} + + +static GstStateChangeReturn +gst_piffdemux_change_state (GstElement * element, GstStateChange transition) +{ + GstPiffDemux *piffdemux = GST_PIFFDEMUX (element); + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + default: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY:{ + piffdemux->state = PIFFDEMUX_STATE_INITIAL; + piffdemux->neededbytes = 16; + piffdemux->todrop = 0; + piffdemux->posted_redirect = FALSE; + piffdemux->offset = 0; + piffdemux->first_mdat = -1; + piffdemux->mdatoffset = GST_CLOCK_TIME_NONE; + if (piffdemux->mdatbuffer) + gst_buffer_unref (piffdemux->mdatbuffer); + piffdemux->mdatbuffer = NULL; + if (piffdemux->tag_list) + gst_tag_list_free (piffdemux->tag_list); + piffdemux->tag_list = NULL; + gst_adapter_clear (piffdemux->adapter); + gst_piffdemux_stream_free (piffdemux, piffdemux->stream); + gst_segment_init (&piffdemux->segment, GST_FORMAT_TIME); + break; + } + default: + break; + } + + return result; +} + +static void +piffdemux_post_global_tags (GstPiffDemux * piffdemux) +{ + if (piffdemux->tag_list) { + /* all header tags ready and parsed, push them */ + GST_INFO_OBJECT (piffdemux, "posting global tags: %" GST_PTR_FORMAT, + piffdemux->tag_list); + /* post now, send event on pads later */ + gst_element_post_message (GST_ELEMENT (piffdemux), + gst_message_new_tag (GST_OBJECT (piffdemux), + gst_tag_list_copy (piffdemux->tag_list))); + } +} + + +/* caller verifies at least 8 bytes in buf */ +static void +extract_initial_length_and_fourcc (const guint8 * data, guint size, + guint64 * plength, guint32 * pfourcc) +{ + guint64 length; + guint32 fourcc; + + length = PIFF_UINT32 (data); + GST_DEBUG ("length 0x%08" G_GINT64_MODIFIER "x", length); + fourcc = PIFF_FOURCC (data + 4); + GST_DEBUG ("atom type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + + if (length == 0) { + length = G_MAXUINT32; + } else if (length == 1 && size >= 16) { + /* this means we have an extended size, which is the 64 bit value of + * the next 8 bytes */ + length = PIFF_UINT64 (data + 8); + GST_DEBUG ("length 0x%08" G_GINT64_MODIFIER "x", length); + } + + if (plength) + *plength = length; + if (pfourcc) + *pfourcc = fourcc; +} + +static gboolean +piffdemux_update_sample_offset (GstPiffDemux * piffdemu, PiffDemuxStream * stream, gint64 uuid_offset) +{ + PiffDemuxSample *sample; + gint i; + + sample = stream->samples ; + for (i = 0; i < stream->n_samples; i++) + { + sample->offset = sample->offset + uuid_offset; + sample++; + } + return TRUE; +} + +static uuid_type_t +piffdemux_get_uuid_type(GstPiffDemux * piffdemux, GstByteReader *uuid_data, gint64 *uuid_offset) +{ + uuid_type_t uuid_type = UUID_UNKNOWN; + guint32 box_len = 0; + guint64 box_long_len = 0; + gchar uuid[16] = {0,}; + int i = 0; + + if (!gst_byte_reader_get_uint32_be (uuid_data, &box_len)) + goto invalid_uuid; + + /* Skipping fourcc */ + if (!gst_byte_reader_skip (uuid_data, 4)) + goto invalid_uuid; + + if (box_len == 1) + { + GST_WARNING ("TfxdBoxLongLength field is present..."); + if (!gst_byte_reader_get_uint64_be (uuid_data, &box_long_len)) + goto invalid_uuid; + GST_DEBUG ("tfxd long length = %llu", box_long_len); + + *uuid_offset = box_long_len; + } + else + { + GST_DEBUG ("Box Len = %d", box_len); + *uuid_offset = box_len; + } + + //g_print ("\n\n\n 0x"); + for (i = 0; i < sizeof (uuid); i++) + { + if (!gst_byte_reader_get_uint8 (uuid_data, &(uuid[i]))) + goto invalid_uuid; + //g_print ("%02x", uuid[i]); + } + //g_print ("\n\n\n"); + + if (!memcmp(uuid, tfxd_uuid, sizeof (uuid_t))) + { + GST_INFO ("Found TFXD box"); + return UUID_TFXD; + } + else if (!memcmp(uuid, tfrf_uuid, sizeof (uuid_t))) + { + GST_INFO ("Found TFRF box"); + return UUID_TFRF; + } + else if (!memcmp(uuid, encrypt_uuid, sizeof (uuid_t))) + { + GST_INFO ("Found sample encryption box"); + return UUID_SAMPLE_ENCRYPT; + } + else + { + GST_WARNING ("Not an valid UUID box.."); + goto invalid_uuid; + } + return uuid_type; + +invalid_uuid: + GST_ERROR ("Error in parsing UUID atom..."); + return UUID_UNKNOWN; +} + +static gboolean +piffdemux_parse_sample_encryption(GstPiffDemux * piffdemux, GstByteReader *sample_encrypt, PiffDemuxStream * stream) +{ + guint32 flags = 0; + guint32 sample_count = 0; + guint32 i = 0; + guint32 algo_id; + guint8 iv_size = 0; + + if (!gst_byte_reader_skip (sample_encrypt, 1) || + !gst_byte_reader_get_uint24_be (sample_encrypt, &flags)) + goto invalid_encryption; + + if (flags & SE_OVERRIDE_TE_FLAGS) { + /* get algorithm id */ + if (!gst_byte_reader_get_uint32_be (sample_encrypt, &algo_id)) + goto invalid_encryption; + + /* get IV size */ + if (!gst_byte_reader_get_uint8 (sample_encrypt, &iv_size)) + goto invalid_encryption; + + // TODO: need to add reading of KID + } else { + GST_INFO_OBJECT (piffdemux, "Override flags are not present... taking default IV_Size = 8"); + iv_size = 8; + } + + /* Get sample count*/ + if (!gst_byte_reader_get_uint32_be (sample_encrypt, &sample_count)) + goto invalid_encryption; + + GST_INFO_OBJECT (piffdemux, "Sample count = %d", sample_count); + + if (sample_count != stream->n_samples) { + GST_ERROR_OBJECT (piffdemux, "Not all samples has IV vectors... Don't know how to handle. sample_cnt = %d and stream->n_samples = %d", + sample_count, stream->n_samples); + goto invalid_encryption; + } + + for (i = 0; i < stream->n_samples; i++) { + guint8 iv_idx = iv_size; + + /* resetting entire IV array */ + stream->samples[i].iv = (guint8 *)malloc (iv_size); + if (NULL == stream->samples[i].iv) { + GST_ERROR ("Failed to allocate memory...\n"); + goto invalid_encryption; + } + + memset (stream->samples[i].iv, 0x00, iv_size); + + iv_idx = 0; + while (iv_idx < iv_size) { + /* get IV byte */ + if (!gst_byte_reader_get_uint8 (sample_encrypt, &(stream->samples[i].iv[iv_idx]))) + goto invalid_encryption; + + iv_idx++; + } + +#ifdef DEBUG_IV + { + guint8 tmp_idx = 0; + g_print ("sample[%d] : 0x ", i); + + while (tmp_idx < iv_size ) { + g_print ("%02x ", stream->samples[i].iv[tmp_idx]); + tmp_idx++; + } + g_print ("\n"); + } +#endif + + if (flags & SE_USE_SUBSAMPLE_ENCRYPTION) { + guint16 n_entries; + guint16 n_idx; + + /* NumberofEntries in SubSampleEncryption */ + if (!gst_byte_reader_get_uint16_be (sample_encrypt, &n_entries)) + goto invalid_encryption; + + stream->samples[i].sub_encry = (PiffDemuxSubSampleEncryption *)malloc (sizeof (PiffDemuxSubSampleEncryption)); + if (NULL == stream->samples[i].sub_encry) { + GST_ERROR ("Failed to allocate memory...\n"); + goto invalid_encryption; + } + + stream->samples[i].sub_encry->sub_entry = g_try_new0 (PiffDemuxSubSampleEntryInfo, n_entries); + if (NULL == stream->samples[i].sub_encry->sub_entry) { + GST_ERROR_OBJECT (piffdemux, "Failed to allocate memory..."); + goto invalid_encryption; + } + + stream->samples[i].sub_encry->n_entries = n_entries; + + GST_DEBUG_OBJECT (piffdemux,"No. of subsample entries = %d", stream->samples[i].sub_encry->n_entries); + + for (n_idx = 0; n_idx < n_entries; n_idx++) { + if (!gst_byte_reader_get_uint16_be (sample_encrypt, &(stream->samples[i].sub_encry->sub_entry[n_idx].LenofClearData))) + goto invalid_encryption; + + GST_DEBUG_OBJECT (piffdemux,"entry[%d] and lengthofClearData = %d", n_idx, stream->samples[i].sub_encry->sub_entry[n_idx].LenofClearData); + + if (!gst_byte_reader_get_uint32_be (sample_encrypt, &(stream->samples[i].sub_encry->sub_entry[n_idx].LenofEncryptData))) + goto invalid_encryption; + + GST_DEBUG_OBJECT (piffdemux,"entry[%d] and lengthofEncryptData = %d", n_idx, stream->samples[i].sub_encry->sub_entry[n_idx].LenofEncryptData); + } + } + } + + return TRUE; + +invalid_encryption: + { + GST_WARNING_OBJECT (piffdemux, "invalid sample encryption header"); + return FALSE; + } +} + + +static gboolean +piffdemux_parse_trun (GstPiffDemux * piffdemux, GstByteReader * trun, + PiffDemuxStream * stream, guint32 d_sample_duration, guint32 d_sample_size, + guint32 d_sample_flags, gint64 moof_offset, gint64 moof_length, + gint64 * base_offset, gint64 * running_offset) +{ + guint64 timestamp; + gint32 data_offset = 0; + guint32 flags = 0, first_flags = 0, samples_count = 0; + gint i; + guint8 *data; + guint entry_size, dur_offset, size_offset, flags_offset = 0, ct_offset = 0; + PiffDemuxSample *sample; + gboolean ismv = FALSE; + guint64 total_duration = 0; + + GST_LOG_OBJECT (piffdemux, "parsing trun stream ; " + "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT, + d_sample_duration, d_sample_size, d_sample_flags, + *base_offset); + + //Resetting the samples + stream->n_samples = 0; + + if (!gst_byte_reader_skip (trun, 1) || + !gst_byte_reader_get_uint24_be (trun, &flags)) + goto fail; + + if (!gst_byte_reader_get_uint32_be (trun, &samples_count)) + goto fail; + + if (flags & TR_DATA_OFFSET) { + /* note this is really signed */ + if (!gst_byte_reader_get_int32_be (trun, &data_offset)) + goto fail; + GST_LOG_OBJECT (piffdemux, "trun data offset %d", data_offset); + /* default base offset = first byte of moof */ + if (*base_offset == -1) { + GST_LOG_OBJECT (piffdemux, "base_offset at moof and moof_offset = %"G_GINT64_FORMAT, moof_offset); + *base_offset = moof_offset; + } + *running_offset = *base_offset + data_offset; + } else { + /* if no offset at all, that would mean data starts at moof start, + * which is a bit wrong and is ismv crappy way, so compensate + * assuming data is in mdat following moof */ + if (*base_offset == -1) { + *base_offset = moof_offset + moof_length + 8; + GST_LOG_OBJECT (piffdemux, "base_offset assumed in mdat after moof"); + ismv = TRUE; + } + if (*running_offset == -1) + *running_offset = *base_offset; + } + + GST_LOG_OBJECT (piffdemux, "running offset now %" G_GINT64_FORMAT, + *running_offset); + GST_LOG_OBJECT (piffdemux, "trun offset %d, flags 0x%x, entries %d", + data_offset, flags, samples_count); + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (G_UNLIKELY (flags & TR_SAMPLE_FLAGS)) { + GST_DEBUG_OBJECT (piffdemux, + "invalid flags; SAMPLE and FIRST_SAMPLE present, discarding latter"); + flags ^= TR_FIRST_SAMPLE_FLAGS; + } else { + if (!gst_byte_reader_get_uint32_be (trun, &first_flags)) + goto fail; + GST_LOG_OBJECT (piffdemux, "first flags: 0x%x", first_flags); + } + } + + /* FIXME ? spec says other bits should also be checked to determine + * entry size (and prefix size for that matter) */ + entry_size = 0; + dur_offset = size_offset = 0; + if (flags & TR_SAMPLE_DURATION) { + GST_LOG_OBJECT (piffdemux, "entry duration present"); + dur_offset = entry_size; + entry_size += 4; + } + if (flags & TR_SAMPLE_SIZE) { + GST_LOG_OBJECT (piffdemux, "entry size present"); + size_offset = entry_size; + entry_size += 4; + } + if (flags & TR_SAMPLE_FLAGS) { + GST_LOG_OBJECT (piffdemux, "entry flags present"); + flags_offset = entry_size; + entry_size += 4; + } + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + GST_LOG_OBJECT (piffdemux, "entry ct offset present"); + ct_offset = entry_size; + entry_size += 4; + } + + if (!piff_atom_parser_has_chunks (trun, samples_count, entry_size)) + goto fail; + data = (guint8 *) gst_byte_reader_peek_data_unchecked (trun); + + if (stream->n_samples >= + PIFFDEMUX_MAX_SAMPLE_INDEX_SIZE / sizeof (PiffDemuxSample)) + goto index_too_big; + + GST_DEBUG_OBJECT (piffdemux, "allocating n_samples %u * %u (%.2f MB)", + stream->n_samples, (guint) sizeof (PiffDemuxSample), + stream->n_samples * sizeof (PiffDemuxSample) / (1024.0 * 1024.0)); + + /* create a new array of samples if it's the first sample parsed */ + if (stream->n_samples == 0) + stream->samples = g_try_new0 (PiffDemuxSample, samples_count); + /* or try to reallocate it with space enough to insert the new samples */ + else + stream->samples = g_try_renew (PiffDemuxSample, stream->samples, + stream->n_samples + samples_count); + if (stream->samples == NULL) + goto out_of_memory; + + if (G_UNLIKELY (stream->n_samples == 0)) { + /* the timestamp of the first sample is also provided by the tfra entry + * but we shouldn't rely on it as it is at the end of files */ + timestamp = 0; + } else { + /* subsequent fragments extend stream */ + timestamp = + stream->samples[stream->n_samples - 1].timestamp + + stream->samples[stream->n_samples - 1].duration; + } + sample = stream->samples + stream->n_samples; + for (i = 0; i < samples_count; i++) { + guint32 dur, size, sflags, ct; + + /* first read sample data */ + if (flags & TR_SAMPLE_DURATION) { + dur = PIFF_UINT32 (data + dur_offset); + } else { + dur = d_sample_duration; + } + if (flags & TR_SAMPLE_SIZE) { + size = PIFF_UINT32 (data + size_offset); + } else { + size = d_sample_size; + } + + GST_DEBUG_OBJECT(piffdemux,"Size of sample %d is %d", i, size); + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (i == 0) { + sflags = first_flags; + } else { + sflags = d_sample_flags; + } + } else if (flags & TR_SAMPLE_FLAGS) { + sflags = PIFF_UINT32 (data + flags_offset); + } else { + sflags = d_sample_flags; + } + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + ct = PIFF_UINT32 (data + ct_offset); + } else { + ct = 0; + } + data += entry_size; + + /* fill the sample information */ + sample->offset = *running_offset; + sample->pts_offset = ct; + sample->size = size; + sample->timestamp = timestamp; + sample->duration = dur; + /* sample-is-difference-sample */ + /* ismv seems to use 0x40 for keyframe, 0xc0 for non-keyframe, + * now idea how it relates to bitfield other than massive LE/BE confusion */ + sample->keyframe = ismv ? ((sflags & 0xff) == 0x40) : !(sflags & 0x10000); + sample->iv = NULL; + sample->sub_encry = NULL; + + stream->samples[i] = *sample; + + *running_offset += size; + timestamp += dur; + sample++; + + /* calculate total duration of the present fragment */ + total_duration += gst_util_uint64_scale (dur, GST_SECOND, stream->timescale); + } + + stream->sample_index = 0; + + stream->n_samples += samples_count; + + /* calculate avg fps based on avg frame duration */ + stream->avg_dur = total_duration/samples_count; + g_print ("total dur = %"GST_TIME_FORMAT", avg_dur = %"GST_TIME_FORMAT"count = %d\n", + GST_TIME_ARGS(total_duration), GST_TIME_ARGS(stream->avg_dur), samples_count); + + return TRUE; + +fail: + { + GST_WARNING_OBJECT (piffdemux, "failed to parse trun"); + return FALSE; + } +out_of_memory: + { + GST_WARNING_OBJECT (piffdemux, "failed to allocate %d samples", + stream->n_samples); + return FALSE; + } +index_too_big: + { + GST_WARNING_OBJECT (piffdemux, "not allocating index of %d samples, would " + "be larger than %uMB (broken file?)", stream->n_samples, + PIFFDEMUX_MAX_SAMPLE_INDEX_SIZE >> 20); + return FALSE; + } +} + +static gboolean +piffdemux_parse_mfhd (GstPiffDemux * piffdemux, GstByteReader * mfhd) +{ + guint32 seq_num = 0; + + if (!gst_byte_reader_skip (mfhd, 4)) + goto invalid_mfhd; + + if (!gst_byte_reader_get_uint32_be (mfhd, &seq_num)) + goto invalid_mfhd; + + GST_DEBUG_OBJECT (piffdemux, "sequence number present in mfhd = %d", seq_num); + + return TRUE; + +invalid_mfhd: + { + GST_WARNING_OBJECT (piffdemux, "invalid movie fragment header"); + return FALSE; + } +} + + +static gboolean +piffdemux_parse_tfhd (GstPiffDemux * piffdemux, GstByteReader * tfhd, + guint32 * default_sample_duration, + guint32 * default_sample_size, guint32 * default_sample_flags, + gint64 * base_offset) +{ + guint32 flags = 0; + guint32 track_id = 0; + + if (!gst_byte_reader_skip (tfhd, 1) || + !gst_byte_reader_get_uint24_be (tfhd, &flags)) + goto invalid_track; + + if (!gst_byte_reader_get_uint32_be (tfhd, &track_id)) + goto invalid_track; + + GST_DEBUG_OBJECT (piffdemux, "trackID = %d", track_id); + + if (flags & TF_BASE_DATA_OFFSET) { + if (!gst_byte_reader_get_uint64_be (tfhd, (guint64 *) base_offset)) + goto invalid_track; + GST_DEBUG ("BaseData Offset = %"G_GUINT64_FORMAT, base_offset); + } + + /* FIXME: Handle TF_SAMPLE_DESCRIPTION_INDEX properly */ + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) + if (!gst_byte_reader_skip (tfhd, 4)) + goto invalid_track; + + if (flags & TF_DEFAULT_SAMPLE_DURATION) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_duration)) + goto invalid_track; + + if (flags & TF_DEFAULT_SAMPLE_SIZE) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_size)) + goto invalid_track; + + if (flags & TF_DEFAULT_SAMPLE_FLAGS) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_flags)) + goto invalid_track; + + return TRUE; + +invalid_track: + { + GST_WARNING_OBJECT (piffdemux, "invalid track fragment header"); + return FALSE; + } +} + +static gboolean +piffdemux_parse_tfxd (GstPiffDemux * piffdemux, PiffDemuxStream *stream,GstByteReader * tfxd) +{ + guint8 version = 0; + + // TODO: In my opinion, tfxd will be mainly useful when lookahead count = 0. In this case, based on this duration, next fragment timstamp can be calculted.. Need to test this using our server + + if (!gst_byte_reader_get_uint8 (tfxd, &version)) + goto invalid_tfxd; + + if (!gst_byte_reader_skip (tfxd, 3)) + goto invalid_tfxd; + + if (!piffdemux->lookahead_cnt) { + piffdemux->param = (piff_live_param_t *)malloc (sizeof (piff_live_param_t)); + if (NULL == piffdemux->param) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + return FALSE; + } + piffdemux->param->count = 1; + piffdemux->param->long_info = NULL; + piffdemux->param->info = NULL; + piffdemux->param->is_eos = FALSE; + + // TODO: presentation will be ended based on timeout in souphttpsrc in lookaheadcnt = 0 case + } + + if (version == 1) { + guint64 duration = 0; + guint64 timestamp = 0; + + GST_LOG_OBJECT (piffdemux, "Time and Duration are in 64-bit format..."); + if (!gst_byte_reader_get_uint64_be (tfxd, ×tamp)) + goto invalid_tfxd; + if (!gst_byte_reader_get_uint64_be (tfxd, &duration)) + goto invalid_tfxd; + + GST_DEBUG_OBJECT (piffdemux, "tfxd : absolute timestamp = %"G_GUINT64_FORMAT" and duration of fragment = %"G_GUINT64_FORMAT, + timestamp, duration); + + if (!piffdemux->lookahead_cnt) { + piffdemux->param->long_info = (piff_fragment_longtime_info *)malloc (piffdemux->param->count * sizeof (piff_fragment_longtime_info)); + if (NULL == piffdemux->param->long_info) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + return FALSE; + } + + /* Calculate next fragment's timestamp using current fragment's timestamp + duration */ + piffdemux->param->long_info->duration = GST_CLOCK_TIME_NONE; + piffdemux->param->long_info->ts = timestamp +duration; + } + } else if (version == 0) { + guint32 duration = 0; + guint32 timestamp = 0; + GST_LOG_OBJECT (piffdemux, "Time and Duration are in 32-bit format..."); + + if (!gst_byte_reader_get_uint32_be (tfxd, ×tamp)) + goto invalid_tfxd; + + if (!gst_byte_reader_get_uint32_be (tfxd, &duration)) + goto invalid_tfxd; + + GST_DEBUG_OBJECT (piffdemux, "tfxd : absolute timestamp = %"G_GUINT32_FORMAT" and duration of fragment = %"G_GUINT32_FORMAT, + timestamp, duration); + + if (!piffdemux->lookahead_cnt) { + piffdemux->param->info = (piff_fragment_time_info *)malloc (piffdemux->param->count * sizeof (piff_fragment_time_info)); + if (NULL == piffdemux->param->info) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + return FALSE; + } + /* Calculate next fragment's timestamp using current fragment's timestamp + duration */ + piffdemux->param->info->duration = GST_CLOCK_TIME_NONE; + piffdemux->param->info->ts = timestamp +duration; + } + } else { + GST_ERROR_OBJECT (piffdemux, "Invalid Version in tfxd..."); + return FALSE; + } + + if (!piffdemux->lookahead_cnt) { + GST_DEBUG_OBJECT (piffdemux, "Emitting live-param signal..."); + g_signal_emit (piffdemux, gst_piffdemux_signals[SIGNAL_LIVE_PARAM], 0, piffdemux->param); + } + + return TRUE; + +invalid_tfxd: + GST_ERROR ("Invalid TFXD atom..."); + return FALSE; +} + + +static gboolean +piffdemux_parse_tfrf (GstPiffDemux * piffdemux, PiffDemuxStream *stream,GstByteReader * tfrf) +{ + guint8 version = 0; + guint8 frag_cnt = 0; + guint8 i = 0; + + /* Getting version info */ + if (!gst_byte_reader_get_uint8 (tfrf, &version)) + goto invalid_tfrf; + + /* skipping reserved flags */ + if (!gst_byte_reader_skip (tfrf, 3)) + goto invalid_tfrf; + + if (!gst_byte_reader_get_uint8 (tfrf, &frag_cnt)) + goto invalid_tfrf; + + GST_INFO_OBJECT (piffdemux, "Subsequent fragments info count = %d", frag_cnt); + + piffdemux->param = (piff_live_param_t *)malloc(sizeof (piff_live_param_t)); + if (NULL == piffdemux->param) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + return FALSE; + } + + piffdemux->param->count = frag_cnt; + piffdemux->param->long_info = NULL; + piffdemux->param->info = NULL; + piffdemux->param->is_eos = FALSE; + + // TODO: Duration and timestamp values need to be posted to msl using g_signal_emit + + if (version == 1) { + guint64 duration = 0; + guint64 timestamp = 0; + GST_LOG_OBJECT (piffdemux, "Time and Duration are in 64-bit format..."); + + piffdemux->param->long_info = (piff_fragment_longtime_info *)malloc (piffdemux->param->count * sizeof (piff_fragment_longtime_info)); + if (NULL == piffdemux->param->long_info) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + return FALSE; + } + + for (i = 0; i < frag_cnt; i++) { + if (!gst_byte_reader_get_uint64_be (tfrf, ×tamp)) + goto invalid_tfrf; + if (!gst_byte_reader_get_uint64_be (tfrf, &duration)) + goto invalid_tfrf; + GST_DEBUG_OBJECT (piffdemux, "tfrf long: absolute timestamp = %"G_GUINT64_FORMAT" and duration of fragment = %"G_GUINT64_FORMAT"\n", + timestamp, duration); + (piffdemux->param->long_info[i]).ts = timestamp; + (piffdemux->param->long_info[i]).duration = duration; + } + } else if (version == 0) { + guint32 duration = 0; + guint32 timestamp = 0; + GST_LOG_OBJECT (piffdemux, "Time and Duration are in 32-bit format..."); + + piffdemux->param->info = (piff_fragment_time_info *)malloc (piffdemux->param->count * sizeof (piff_fragment_time_info)); + if (NULL == piffdemux->param->info) { + GST_ERROR ("Memory not available...\n"); + return FALSE; + } + + for (i = 0; i < frag_cnt; i++) { + if (!gst_byte_reader_get_uint32_be (tfrf, ×tamp)) + goto invalid_tfrf; + if (!gst_byte_reader_get_uint32_be (tfrf, &duration)) + goto invalid_tfrf; + + GST_DEBUG_OBJECT (piffdemux, "tfrf int: absolute timestamp = %"G_GUINT32_FORMAT" and duration of fragment = %"G_GUINT32_FORMAT, + timestamp, duration); + (piffdemux->param->info[i]).ts = timestamp; + (piffdemux->param->info[i]).duration = duration; + } + } else { + GST_ERROR_OBJECT (piffdemux, "Invalid Version in tfrf..."); + return FALSE; + } + + g_print ("Signalling from TFRF box..\n"); + g_signal_emit (piffdemux, gst_piffdemux_signals[SIGNAL_LIVE_PARAM], 0, piffdemux->param); + + return TRUE; + +invalid_tfrf: + GST_ERROR_OBJECT (piffdemux, "Invalid TFRF atom..."); + return FALSE; +} + + +static gboolean +piffdemux_parse_moof (GstPiffDemux * piffdemux, const guint8 * buffer, guint length, + guint64 moof_offset, PiffDemuxStream * stream) +{ + GNode *moof_node, *mfhd_node, *traf_node, *tfhd_node, *trun_node, *uuid_node; + GstByteReader mfhd_data, trun_data, tfhd_data, uuid_data; + guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; + gint64 base_offset, running_offset; + gint64 uuid_offset = 0; + gboolean found_tfxd = FALSE; + gboolean found_tfrf = FALSE; + + /* NOTE @stream ignored */ + + moof_node = g_node_new ((guint8 *) buffer); + piffdemux_parse_node (piffdemux, moof_node, buffer, length); + //piffdemux_node_dump (piffdemux, moof_node); + + /* unknown base_offset to start with */ + base_offset = running_offset = -1; + + mfhd_node = piffdemux_tree_get_child_by_type_full (moof_node, FOURCC_mfhd, &mfhd_data); + if (!mfhd_node) + goto missing_mfhd; + + if (!piffdemux_parse_mfhd (piffdemux, &mfhd_data)) + goto missing_mfhd; + + traf_node = piffdemux_tree_get_child_by_type (moof_node, FOURCC_traf); + while (traf_node) { + /* Fragment Header node */ + tfhd_node = + piffdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfhd, + &tfhd_data); + if (!tfhd_node) + goto missing_tfhd; + if (!piffdemux_parse_tfhd (piffdemux, &tfhd_data, &ds_duration, + &ds_size, &ds_flags, &base_offset)) + goto missing_tfhd; + + if (G_UNLIKELY (base_offset < -1)) + goto lost_offset; + + /* Track Run node */ + trun_node = + piffdemux_tree_get_child_by_type_full (traf_node, FOURCC_trun, + &trun_data); + while (trun_node) { + piffdemux_parse_trun (piffdemux, &trun_data, stream, + ds_duration, ds_size, ds_flags, moof_offset, length, &base_offset, + &running_offset); + /* iterate all siblings */ + trun_node = piffdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun, + &trun_data); + } + + uuid_node = piffdemux_tree_get_child_by_type (traf_node, FOURCC_uuid); + while (uuid_node) { + uuid_type_t uuid_type; + guint8 *lbuffer = (guint8 *) uuid_node->data; + + gst_byte_reader_init (&uuid_data, lbuffer, PIFF_UINT32 (lbuffer)); + + uuid_type = piffdemux_get_uuid_type (piffdemux, &uuid_data, &uuid_offset); + + if ((UUID_TFXD == uuid_type) && piffdemux->is_live) { + // TODO: Dont know, why we should not consider tfxd offset...if we use tfxd offset also, not working.. PIFF doc does not say anything :( + found_tfxd = TRUE; + if (!piffdemux_parse_tfxd (piffdemux, stream, &uuid_data)) + goto fail; + } else if ((UUID_TFRF == uuid_type) && piffdemux->is_live && piffdemux->lookahead_cnt) { + found_tfrf = TRUE; + if (!piffdemux_parse_tfrf (piffdemux, stream, &uuid_data)) + goto fail; + piffdemux_update_sample_offset (piffdemux, stream, uuid_offset); + running_offset += uuid_offset; + } else if (UUID_SAMPLE_ENCRYPT == uuid_type) { + if (!piffdemux_parse_sample_encryption (piffdemux, &uuid_data, stream)) + goto fail; + } else { + GST_WARNING_OBJECT (piffdemux, "Ignoring Wrong UUID..."); + } + + /* iterate all siblings */ + uuid_node = piffdemux_tree_get_sibling_by_type (uuid_node, FOURCC_uuid); + } + + if (piffdemux->is_live) { + if (!found_tfxd) { + GST_ERROR_OBJECT (piffdemux, "TFXD box is not present for live stream"); + goto fail; + } + + if (!found_tfrf && piffdemux->lookahead_cnt) { + /* when lookahead count is non-zero in manifest & if tfrf box is not present., means EOS */ + GST_INFO_OBJECT (piffdemux, "Reached Endof Live presentation.."); + + piffdemux->param = (piff_live_param_t *)malloc (sizeof (piff_live_param_t)); + if (NULL == piffdemux->param) { + GST_ERROR_OBJECT (piffdemux, "Memory not available...\n"); + goto fail; + } + piffdemux->param->count = 0; + piffdemux->param->long_info = NULL; + piffdemux->param->info = NULL; + piffdemux->param->is_eos = TRUE; /* marking EOS */ + g_signal_emit (piffdemux, gst_piffdemux_signals[SIGNAL_LIVE_PARAM], 0, piffdemux->param); + } + } + + /* if no new base_offset provided for next traf, + * base is end of current traf */ + base_offset = running_offset; + running_offset = -1; + + /* iterate all siblings */ + traf_node = piffdemux_tree_get_sibling_by_type (traf_node, FOURCC_traf); + } + g_node_destroy (moof_node); + return TRUE; + +missing_mfhd: + { + GST_DEBUG_OBJECT (piffdemux, "missing mfhd box"); + goto fail; + } + +missing_tfhd: + { + GST_DEBUG_OBJECT (piffdemux, "missing tfhd box"); + goto fail; + } +lost_offset: + { + GST_DEBUG_OBJECT (piffdemux, "lost offset"); + goto fail; + } +fail: + { + g_node_destroy (moof_node); + + GST_ELEMENT_ERROR (piffdemux, STREAM, DEMUX, + ("This file is corrupt and cannot be played."), (NULL)); + + return FALSE; + } +} + + +/* activate the given segment number @seg_idx of @stream at time @offset. + * @offset is an absolute global position over all the segments. + * + * This will push out a NEWSEGMENT event with the right values and + * position the stream index to the first decodable sample before + * @offset. + */ +static gboolean +gst_piffdemux_activate_segment (GstPiffDemux * piffdemux, PiffDemuxStream * stream, + guint32 seg_idx, guint64 offset) +{ + GstEvent *event; + PiffDemuxSegment *segment; + guint64 seg_time; + guint64 start, stop, time; + gdouble rate; + + GST_LOG_OBJECT (piffdemux, "activate segment %d, offset %" G_GUINT64_FORMAT, + seg_idx, offset); + + /* update the current segment */ + stream->segment_index = seg_idx; + + /* get the segment */ + segment = &stream->segments[seg_idx]; + + if (G_UNLIKELY (offset < segment->time)) { + GST_WARNING_OBJECT (piffdemux, "offset < segment->time %" G_GUINT64_FORMAT, + segment->time); + return FALSE; + } + + /* segment lies beyond total indicated duration */ + if (G_UNLIKELY (piffdemux->segment.duration != -1 && + segment->time > piffdemux->segment.duration)) { + GST_WARNING_OBJECT (piffdemux, "file duration %" G_GINT64_FORMAT + " < segment->time %" G_GUINT64_FORMAT, piffdemux->segment.duration, + segment->time); + return FALSE; + } + + /* get time in this segment */ + seg_time = offset - segment->time; + + GST_LOG_OBJECT (piffdemux, "seg_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (seg_time)); + + if (G_UNLIKELY (seg_time > segment->duration)) { + GST_LOG_OBJECT (piffdemux, "seg_time > segment->duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->duration)); + return FALSE; + } + + /* piffdemux->segment.stop is in outside-time-realm, whereas + * segment->media_stop is in track-time-realm. + * + * In order to compare the two, we need to bring segment.stop + * into the track-time-realm */ + + stop = piffdemux->segment.stop; + if (stop == -1) + stop = piffdemux->segment.duration; + if (stop == -1) + stop = segment->media_stop; + else + stop = + MIN (segment->media_stop, stop - segment->time + segment->media_start); + + if (piffdemux->segment.rate >= 0) { + start = MIN (segment->media_start + seg_time, stop); + time = offset; + } else { + if (segment->media_start >= piffdemux->segment.start) { + start = segment->media_start; + time = segment->time; + } else { + start = piffdemux->segment.start; + time = segment->time + (piffdemux->segment.start - segment->media_start); + } + + start = MAX (segment->media_start, piffdemux->segment.start); + stop = MIN (segment->media_start + seg_time, stop); + } + + GST_DEBUG_OBJECT (piffdemux, "newsegment %d from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, seg_idx, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (time)); + + /* combine global rate with that of the segment */ + rate = segment->rate * piffdemux->segment.rate; + + /* update the segment values used for clipping */ + gst_segment_init (&stream->segment, GST_FORMAT_TIME); + gst_segment_set_newsegment (&stream->segment, FALSE, rate, GST_FORMAT_TIME, + start, stop, time); + + /* now prepare and send the segment */ + event = gst_event_new_new_segment (FALSE, rate, GST_FORMAT_TIME, + start, stop, time); + gst_pad_push_event (piffdemux->srcpad, event); + /* assume we can send more data now */ + stream->last_ret = GST_FLOW_OK; + /* clear to send tags on this pad now */ + gst_piffdemux_push_tags (piffdemux, stream); + + return TRUE; +} + + +/* prepare to get the current sample of @stream, getting essential values. + * + * This function will also prepare and send the segment when needed. + * + * Return FALSE if the stream is EOS. + */ +static gboolean +gst_piffdemux_prepare_current_sample (GstPiffDemux * piffdemux, + PiffDemuxStream * stream, guint64 * offset, guint * size, guint64 * timestamp, + guint64 * duration, gboolean * keyframe) +{ + PiffDemuxSample *sample; + guint64 time_position; + guint32 seg_idx; + + g_return_val_if_fail (stream != NULL, FALSE); + + time_position = stream->time_position; + if (G_UNLIKELY (time_position == -1)) + goto eos; + + seg_idx = stream->segment_index; + if (G_UNLIKELY (seg_idx == -1)) { + /* find segment corresponding to time_position if we are looking + * for a segment. */ + seg_idx = gst_piffdemux_find_segment (piffdemux, stream, time_position); + + /* nothing found, we're really eos */ + if (seg_idx == -1) + goto eos; + } + + /* different segment, activate it, sample_index will be set. */ + if (G_UNLIKELY (stream->segment_index != seg_idx)) + gst_piffdemux_activate_segment (piffdemux, stream, seg_idx, time_position); + + GST_LOG_OBJECT (piffdemux, "segment active, index = %u of %u", + stream->sample_index, stream->n_samples); + + if (G_UNLIKELY (stream->sample_index >= stream->n_samples)) + goto eos; + + + /* now get the info for the sample we're at */ + sample = &stream->samples[stream->sample_index]; + + *timestamp = PIFFSAMPLE_PTS (stream, sample); + *offset = sample->offset; + *size = sample->size; + *duration = PIFFSAMPLE_DUR_PTS (stream, sample, *timestamp); + *keyframe = PIFFSAMPLE_KEYFRAME (stream, sample); + + return TRUE; + + /* special cases */ +eos: + { + stream->time_position = -1; + return FALSE; + } +} + +/* the input buffer metadata must be writable, + * but time/duration etc not yet set and need not be preserved */ +static GstBuffer * +gst_piffdemux_process_buffer (GstPiffDemux * piffdemux, PiffDemuxStream * stream, + GstBuffer * buf) +{ + guint8 *data; + guint size, nsize = 0; + gchar *str; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + if (G_UNLIKELY (stream->subtype != FOURCC_text)) { + return buf; + } + + if (G_LIKELY (size >= 2)) { + nsize = GST_READ_UINT16_BE (data); + nsize = MIN (nsize, size - 2); + } + + GST_LOG_OBJECT (piffdemux, "3GPP timed text subtitle: %d/%d", nsize, size); + + /* takes care of UTF-8 validation or UTF-16 recognition, + * no other encoding expected */ + str = gst_tag_freeform_string_to_utf8 ((gchar *) data + 2, nsize, NULL); + if (str) { + gst_buffer_unref (buf); + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = (guint8 *) str; + GST_BUFFER_SIZE (buf) = strlen (str); + } else { + /* may be 0-size subtitle, which is also sent to keep pipeline going */ + GST_BUFFER_DATA (buf) = data + 2; + GST_BUFFER_SIZE (buf) = nsize; + } + + /* FIXME ? convert optional subsequent style info to markup */ + + return buf; +} + +/* Sets a buffer's attributes properly and pushes it downstream. + * Also checks for additional actions and custom processing that may + * need to be done first. + */ +static gboolean +gst_piffdemux_decorate_and_push_buffer (GstPiffDemux * piffdemux, + PiffDemuxStream * stream, GstBuffer * buf, + guint64 timestamp, guint64 duration, gboolean keyframe, guint64 position, + guint64 byte_position) +{ + GstFlowReturn ret = GST_FLOW_OK; + + if (!stream->caps) { + GST_WARNING_OBJECT (piffdemux, "caps are empty...creat any caps"); + stream->caps = gst_caps_new_any(); + if (!stream->caps) { + GST_ERROR_OBJECT (piffdemux, "failed to create caps..."); + ret = GST_FLOW_ERROR; + goto exit; + } + } + + /* position reporting */ + if (piffdemux->segment.rate >= 0) { + // TODO: Segment fault is coming here for Audio stream.. need to check + + gst_segment_set_last_stop (&piffdemux->segment, GST_FORMAT_TIME, position); + //gst_piffdemux_sync_streams (piffdemux); + } + + /* send out pending buffers */ + while (stream->buffers) { + GstBuffer *buffer = (GstBuffer *) stream->buffers->data; + + if (G_UNLIKELY (stream->discont)) { + GST_LOG_OBJECT (piffdemux, "marking discont buffer"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } + gst_buffer_set_caps (buffer, stream->caps); + + gst_pad_push (piffdemux->srcpad, buffer); + + stream->buffers = g_slist_delete_link (stream->buffers, stream->buffers); + } + + /* we're going to modify the metadata */ + buf = gst_buffer_make_metadata_writable (buf); + + /* for subtitle processing */ + if (G_UNLIKELY (stream->need_process)) + buf = gst_piffdemux_process_buffer (piffdemux, stream, buf); + + GST_BUFFER_TIMESTAMP (buf) = timestamp; + GST_BUFFER_DURATION (buf) = duration; + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = -1; + + if (G_UNLIKELY (stream->padding)) { + GST_BUFFER_DATA (buf) += stream->padding; + GST_BUFFER_SIZE (buf) -= stream->padding; + } + + if (G_UNLIKELY (buf == NULL)) + goto exit; + + if (G_UNLIKELY (stream->discont)) { + GST_LOG_OBJECT (piffdemux, "marking discont buffer"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } + + if (!keyframe) + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + + //g_print ("\n\npad caps : %s\n\n", gst_caps_to_string (gst_pad_get_caps (stream->pad))); + + //gst_buffer_set_caps (buf, stream->caps); // commenting to avoid caps by setting properties + + // TODO: need to see how resolution switch will work + gst_buffer_set_caps (buf, stream->caps); + +#ifdef DRM_ENABLE + if (piffdemux->encrypt_content) { + drm_trusted_payload_info_s read_input_data = {0, }; + drm_trusted_read_decrypt_resp_data_s read_output_data = {0, }; + gint offset = 0; + PiffDemuxSample *sample = &stream->samples[stream->sample_index]; + + if (sample->sub_encry) { + offset = sample->sub_encry->sub_entry[0].LenofClearData; + } + + read_input_data.media_offset = 0; + read_input_data.payload_data = GST_BUFFER_DATA(buf) + offset; + read_input_data.payload_data_len = GST_BUFFER_SIZE(buf) - offset; + read_input_data.payload_iv_len = 8; + read_input_data.payload_iv = (unsigned char *) malloc (8); + read_input_data.payload_data_output = GST_BUFFER_DATA(buf) + offset; + if (NULL == read_input_data.payload_iv) { + GST_ERROR ("Failed to allocate memory..."); + ret = GST_FLOW_ERROR; + goto exit; + } + memcpy (read_input_data.payload_iv, sample->iv, 8); + + ret = drm_trusted_read_decrypt_session(piffdemux->pr_handle , &read_input_data, &read_output_data); + if (DRM_TRUSTED_RETURN_SUCCESS != ret) { + GST_ERROR_OBJECT (piffdemux, "failed to decrypt buffer..."); + free (read_input_data.payload_iv); + ret = GST_FLOW_ERROR; + goto exit; + } + + if (read_output_data.read_size != read_input_data.payload_data_len) { + g_print ("Decrypter did not consume data fully...\n\n\n"); + } + + free (read_input_data.payload_iv); + read_input_data.payload_iv = NULL; + + } +#endif + + GST_LOG_OBJECT (piffdemux, + "Pushing buffer of size = %d with time %" GST_TIME_FORMAT ", duration %" + GST_TIME_FORMAT, GST_BUFFER_SIZE(buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + +#ifdef DEC_OUT_FRAME_DUMP + { + int written = 0; + written = fwrite (GST_BUFFER_DATA (buf), sizeof (unsigned char), GST_BUFFER_SIZE (buf), piffdump); + g_print ("PIFFDEMUX: written = %d\n", written); + fflush (piffdump); + } +#endif + + ret = gst_pad_push (piffdemux->srcpad, buf); + +exit: + return ret; +} + + +/* + * next_entry_size + * + * Returns the size of the first entry at the current offset. + * If -1, there are none (which means EOS or empty file). + */ +static guint64 +next_entry_size (GstPiffDemux * demux) +{ + PiffDemuxStream *stream = demux->stream; + PiffDemuxSample *sample; + + GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT, + demux->offset); + + GST_DEBUG_OBJECT (demux, "demux->sample_index = %d", stream->sample_index); + + if (stream->sample_index == -1) + stream->sample_index = 0; + + if (stream->sample_index >= stream->n_samples) { + GST_LOG_OBJECT (demux, "stream %d samples exhausted n_samples = %d", + stream->sample_index, stream->n_samples); + return -1; + } + + sample = &stream->samples[stream->sample_index]; + + GST_LOG_OBJECT (demux, + "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT + " / size:%" G_GUINT32_FORMAT ")", stream->sample_index, stream->sample_index, + sample->offset, sample->size); + + GST_LOG_OBJECT (demux, "stream : demux->offset :%"G_GUINT64_FORMAT, demux->offset); + + stream = demux->stream; + sample = &stream->samples[stream->sample_index]; + + if (sample->offset >= demux->offset) { + demux->todrop = sample->offset - demux->offset; + return sample->size + demux->todrop; + } + + GST_DEBUG_OBJECT (demux, + "There wasn't any entry at offset %" G_GUINT64_FORMAT, demux->offset); + return -1; +} + +static void +gst_piffdemux_post_progress (GstPiffDemux * demux, gint num, gint denom) +{ + gint perc = (gint) ((gdouble) num * 100.0 / (gdouble) denom); + + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT_CAST (demux), + gst_structure_new ("progress", "percent", G_TYPE_INT, perc, NULL))); +} + +static gboolean +piffdemux_seek_offset (GstPiffDemux * demux, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + GST_DEBUG_OBJECT (demux, "Seeking to %" G_GUINT64_FORMAT, offset); + + event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, + GST_SEEK_TYPE_NONE, -1); + + res = gst_pad_push_event (demux->sinkpad, event); + + return res; +} + +static GstFlowReturn +gst_piffdemux_chain (GstPad * sinkpad, GstBuffer * inbuf) +{ + GstPiffDemux *demux; + GstFlowReturn ret = GST_FLOW_OK; + demux = GST_PIFFDEMUX (gst_pad_get_parent (sinkpad)); +#ifdef DRM_ENABLE + if (demux->encrypt_content && !demux->decrypt_init) { + int ret = -1; + drm_trusted_permission_type_e perm_type = DRM_TRUSTED_PERMISSION_TYPE_PLAY; + drm_trusted_open_decrypt_info_s open_input_data = {0, }; + drm_trusted_open_decrypt_resp_data_s open_output_data = {0, }; + drm_trusted_set_consumption_state_info_s state_input_data = {0, }; + + open_input_data.file_type = DRM_TRUSTED_TYPE_PIFF; + open_input_data.permission = perm_type; + open_input_data.operation_callback.callback = test_drm_trusted_operation_cb; + open_input_data.lic_header.header = GST_BUFFER_DATA(demux->protection_header); + open_input_data.lic_header.header_len = GST_BUFFER_SIZE (demux->protection_header); + + /* Open Decrypt Session*/ + ret = drm_trusted_open_decrypt_session(&open_input_data, &open_output_data, &(demux->pr_handle)); + if (DRM_TRUSTED_RETURN_SUCCESS != ret) { + GST_ERROR_OBJECT (demux, "failed to open decrypt session"); + goto unknown_stream; + } + + /* Before Read, Appropriate state MUST be SET */ + state_input_data.state = DRM_CONSUMPTION_STARTED; + ret = drm_trusted_set_decrypt_state(demux->pr_handle, &state_input_data); + if (DRM_TRUSTED_RETURN_SUCCESS != ret) { + GST_ERROR_OBJECT (demux, "failed to set decrypt state..."); + goto unknown_stream; + } + + demux->decrypt_init = TRUE; + } +#endif + gst_adapter_push (demux->adapter, inbuf); + + /* we never really mean to buffer that much */ + if (demux->neededbytes == -1) + goto eos; + + GST_DEBUG_OBJECT (demux, "pushing in inbuf %p, neededbytes:%u, available:%u", + inbuf, demux->neededbytes, gst_adapter_available (demux->adapter)); + + while (((gst_adapter_available (demux->adapter)) >= demux->neededbytes) && + (ret == GST_FLOW_OK)) { + + GST_DEBUG_OBJECT (demux, + "state:%d , demux->neededbytes:%d, demux->offset:%" G_GUINT64_FORMAT, + demux->state, demux->neededbytes, demux->offset); + + switch (demux->state) { + case PIFFDEMUX_STATE_INITIAL:{ + const guint8 *data; + guint32 fourcc; + guint64 size; + + data = gst_adapter_peek (demux->adapter, demux->neededbytes); + + if (!data) { + GST_ERROR_OBJECT (demux, "peeked adapter buffer is NULL"); + ret = GST_FLOW_ERROR; + break; + } + + /* get fourcc/length, set neededbytes */ + extract_initial_length_and_fourcc ((guint8 *) data, demux->neededbytes, + &size, &fourcc); + GST_DEBUG_OBJECT (demux, "Peeking found [%" GST_FOURCC_FORMAT "] " + "size: %" G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), size); + if (size == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + ("This file is invalid and cannot be played."), + ("initial atom '%" GST_FOURCC_FORMAT "' has empty length", + GST_FOURCC_ARGS (fourcc))); + + ret = GST_FLOW_ERROR; + break; + } + + if (fourcc == FOURCC_mdat) { + if (demux->moof_rcvd) { + /* we have the headers, start playback */ + demux->state = PIFFDEMUX_STATE_MOVIE; + demux->neededbytes = next_entry_size (demux); + demux->mdatleft = size; + + /* Only post, event on pads is done after newsegment */ + piffdemux_post_global_tags (demux); + } else { + GST_ERROR_OBJECT (demux, "mdata received before moof.. not handled"); + goto unknown_stream; + } + } else if (G_UNLIKELY (size > PIFFDEMUX_MAX_ATOM_SIZE)) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + ("This file is invalid and cannot be played."), + ("atom %" GST_FOURCC_FORMAT " has bogus size %" G_GUINT64_FORMAT, + GST_FOURCC_ARGS (fourcc), size)); + ret = GST_FLOW_ERROR; + break; + } else { + demux->neededbytes = size; + demux->state = PIFFDEMUX_STATE_HEADER; + } + break; + } + case PIFFDEMUX_STATE_HEADER:{ + const guint8 *data; + guint32 fourcc; + + GST_DEBUG_OBJECT (demux, "In header"); + + data = gst_adapter_peek (demux->adapter, demux->neededbytes); + + if (!data) { + GST_ERROR_OBJECT (demux, "peeked adapter buffer is NULL"); + ret = GST_FLOW_ERROR; + break; + } + + /* parse the header */ + extract_initial_length_and_fourcc (data, demux->neededbytes, NULL, + &fourcc); + if (fourcc == FOURCC_moof) { + GST_DEBUG_OBJECT (demux, "Parsing [moof]"); + if (!piffdemux_parse_moof (demux, data, demux->neededbytes, + demux->offset, demux->stream)) { + ret = GST_FLOW_ERROR; + goto done; + } + demux->moof_rcvd = TRUE; + } else { + GST_WARNING_OBJECT (demux, + "Unknown fourcc while parsing header : %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + /* Let's jump that one and go back to initial state */ + } + + if (demux->mdatbuffer) { + /* the mdat was before the header */ + GST_DEBUG_OBJECT (demux, "We have mdatbuffer:%p", + demux->mdatbuffer); + gst_adapter_clear (demux->adapter); + demux->mdatbuffer = NULL; + demux->offset = demux->mdatoffset; + demux->neededbytes = next_entry_size (demux); + demux->state = PIFFDEMUX_STATE_MOVIE; + demux->mdatleft = gst_adapter_available (demux->adapter); + + /* Only post, event on pads is done after newsegment */ + piffdemux_post_global_tags (demux); + } else { + GST_DEBUG_OBJECT (demux, "Carrying on normally"); + gst_adapter_flush (demux->adapter, demux->neededbytes); + demux->offset += demux->neededbytes; + demux->neededbytes = 16; + demux->state = PIFFDEMUX_STATE_INITIAL; + } + + break; + } + case PIFFDEMUX_STATE_BUFFER_MDAT:{ + GstBuffer *buf; + + GST_DEBUG_OBJECT (demux, "Got our buffer at offset %" G_GUINT64_FORMAT, + demux->offset); + buf = gst_adapter_take_buffer (demux->adapter, demux->neededbytes); + GST_DEBUG_OBJECT (demux, "mdatbuffer starts with %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (PIFF_FOURCC (GST_BUFFER_DATA (buf) + 4))); + if (demux->mdatbuffer) + demux->mdatbuffer = gst_buffer_join (demux->mdatbuffer, buf); + else + demux->mdatbuffer = buf; + demux->offset += demux->neededbytes; + demux->neededbytes = 16; + demux->state = PIFFDEMUX_STATE_INITIAL; + gst_piffdemux_post_progress (demux, 1, 1); + + break; + } + case PIFFDEMUX_STATE_MOVIE:{ + GstBuffer *outbuf; + PiffDemuxStream *stream = demux->stream; + PiffDemuxSample *sample; + guint64 timestamp, duration, position; + gboolean keyframe; + + GST_DEBUG_OBJECT (demux, + "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset); + + if (demux->fragmented) { + GST_DEBUG_OBJECT (demux, "mdat remaining %" G_GUINT64_FORMAT, + demux->mdatleft); + if (G_LIKELY (demux->todrop < demux->mdatleft)) { + /* if needed data starts within this atom, + * then it should not exceed this atom */ + if (G_UNLIKELY (demux->neededbytes > demux->mdatleft)) { + + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + ("This file is invalid and cannot be played."), + ("sample data crosses atom boundary")); + + ret = GST_FLOW_ERROR; + break; + } + demux->mdatleft -= demux->neededbytes; + } else { + GST_DEBUG_OBJECT (demux, "data atom emptied; resuming atom scan"); + /* so we are dropping more than left in this atom */ + demux->todrop -= demux->mdatleft; + demux->neededbytes -= demux->mdatleft; + demux->mdatleft = 0; + /* need to resume atom parsing so we do not miss any other pieces */ + demux->state = PIFFDEMUX_STATE_INITIAL; + demux->neededbytes = 16; + break; + } + } + + if (demux->todrop) { + GST_LOG_OBJECT (demux, "Dropping %d bytes", demux->todrop); + gst_adapter_flush (demux->adapter, demux->todrop); + demux->neededbytes -= demux->todrop; + demux->offset += demux->todrop; + } + + if ( !stream->sent_nsevent) { + //TODO: better to parse sink event function and send that new_segment +#if 1 + demux->pending_newsegment = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + demux->stream->start_ts, -1, demux->stream->start_ts); +#else + demux->pending_newsegment = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + 0, gst_util_uint64_scale (stream->duration, GST_SECOND, stream->timescale), 0); +#endif + + GST_INFO_OBJECT (demux, "New segment event : start = %"GST_TIME_FORMAT", stop = %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->stream->start_ts), GST_TIME_ARGS(gst_util_uint64_scale (stream->duration, GST_SECOND, stream->timescale))); + + if (!gst_pad_push_event (demux->srcpad, demux->pending_newsegment)) { + GST_ERROR_OBJECT (demux, "failding to send new segment..."); + goto newsegment_error; + } + stream->sent_nsevent = TRUE; + } + + /* Put data in a buffer, set timestamps, caps, ... */ + outbuf = gst_adapter_take_buffer (demux->adapter, demux->neededbytes); + + GST_DEBUG_OBJECT (demux, "Taken %d size buffer from adapter...", outbuf ? GST_BUFFER_SIZE (outbuf) : 0); + + GST_DEBUG_OBJECT (demux, "stream : %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (stream->fourcc)); + + g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); + + sample = &stream->samples[stream->sample_index]; + + GST_DEBUG_OBJECT (demux, "start_ts = %"GST_TIME_FORMAT" ts : %"GST_TIME_FORMAT" ts = %llu, pts_offset = %u, scale = %d\n", + GST_TIME_ARGS(stream->start_ts),GST_TIME_ARGS(sample->timestamp), sample->timestamp, + sample->pts_offset,stream->timescale); + + position = PIFFSAMPLE_DTS (stream, sample); + timestamp = PIFFSAMPLE_PTS (stream, sample) + stream->start_ts; // Adding to avoid resetting of timestamp + duration = PIFFSAMPLE_DUR_DTS (stream, sample, position); + keyframe = PIFFSAMPLE_KEYFRAME (stream, sample); + + ret = gst_piffdemux_decorate_and_push_buffer (demux, stream, outbuf, + timestamp, duration, keyframe, position, demux->offset); + + stream->sample_index++; + + /* update current offset and figure out size of next buffer */ + GST_LOG_OBJECT (demux, "increasing offset %" G_GUINT64_FORMAT " by %u", + demux->offset, demux->neededbytes); + demux->offset += demux->neededbytes; + GST_LOG_OBJECT (demux, "offset is now %" G_GUINT64_FORMAT, + demux->offset); + + if ((demux->neededbytes = next_entry_size (demux)) == -1) { + GST_DEBUG_OBJECT (demux, "finished parsing mdat, need to search next moof atom"); + demux->neededbytes = 16; + demux->state = PIFFDEMUX_STATE_INITIAL; + GST_DEBUG ("\n\n Storing %s last_ts %"GST_TIME_FORMAT"\n\n", stream->subtype == FOURCC_vide ? "video" : "audio", GST_TIME_ARGS(timestamp)); + stream->start_ts = timestamp + duration; + //goto eos; + } + break; + } + default: + goto invalid_state; + } + } + + /* when buffering movie data, at least show user something is happening */ + if (ret == GST_FLOW_OK && demux->state == PIFFDEMUX_STATE_BUFFER_MDAT && + gst_adapter_available (demux->adapter) <= demux->neededbytes) { + gst_piffdemux_post_progress (demux, gst_adapter_available (demux->adapter), + demux->neededbytes); + } +done: + gst_object_unref (demux); + + return ret; + + /* ERRORS */ +unknown_stream: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL), ("unknown stream found")); + ret = GST_FLOW_ERROR; + goto done; + } +eos: + { + GST_DEBUG_OBJECT (demux, "no next entry, EOS"); + ret = GST_FLOW_UNEXPECTED; + goto done; + } +invalid_state: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (NULL), ("piffdemuxer invalid state %d", demux->state)); + ret = GST_FLOW_ERROR; + goto done; + } +newsegment_error: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (NULL), ("could not send newsegment event")); + ret = GST_FLOW_ERROR; + goto done; + } +} + +static gboolean +piffdemux_parse_container (GstPiffDemux * piffdemux, GNode * node, const guint8 * buf, + const guint8 * end) +{ + while (G_UNLIKELY (buf < end)) { + GNode *child; + guint32 len; + + if (G_UNLIKELY (buf + 4 > end)) { + GST_LOG_OBJECT (piffdemux, "buffer overrun"); + break; + } + len = PIFF_UINT32 (buf); + if (G_UNLIKELY (len == 0)) { + GST_LOG_OBJECT (piffdemux, "empty container"); + break; + } + if (G_UNLIKELY (len < 8)) { + GST_WARNING_OBJECT (piffdemux, "length too short (%d < 8)", len); + break; + } + if (G_UNLIKELY (len > (end - buf))) { + GST_WARNING_OBJECT (piffdemux, "length too long (%d > %d)", len, + (gint) (end - buf)); + break; + } + + child = g_node_new ((guint8 *) buf); + g_node_append (node, child); + GST_LOG_OBJECT (piffdemux, "adding new node of len %d", len); + piffdemux_parse_node (piffdemux, child, buf, len); + + buf += len; + } + return TRUE; +} + + +static gboolean +piffdemux_parse_node (GstPiffDemux * piffdemux, GNode * node, const guint8 * buffer, + guint length) +{ + guint32 fourcc = 0; + guint32 node_length = 0; + const PiffNodeType *type; + const guint8 *end; + + GST_LOG_OBJECT (piffdemux, "piffdemux_parse buffer %p length %u", buffer, length); + + if (G_UNLIKELY (length < 8)) + goto not_enough_data; + + node_length = PIFF_UINT32 (buffer); + fourcc = PIFF_FOURCC (buffer + 4); + + /* ignore empty nodes */ + if (G_UNLIKELY (fourcc == 0 || node_length == 8)) + return TRUE; + + type = piffdemux_type_get (fourcc); + + end = buffer + length; + + GST_LOG_OBJECT (piffdemux, + "parsing '%" GST_FOURCC_FORMAT "', length=%u, name '%s'", + GST_FOURCC_ARGS (fourcc), node_length, type->name); + + if (node_length > length) + goto broken_atom_size; + + if (type->flags & PIFF_FLAG_CONTAINER) { + piffdemux_parse_container (piffdemux, node, buffer + 8, end); + } + GST_LOG_OBJECT (piffdemux, "parsed '%" GST_FOURCC_FORMAT "'", + GST_FOURCC_ARGS (fourcc)); + return TRUE; + +/* ERRORS */ +not_enough_data: + { + + GST_ELEMENT_ERROR (piffdemux, STREAM, DEMUX, + ("This file is corrupt and cannot be played."), + ("Not enough data for an atom header, got only %u bytes", length)); + + return FALSE; + } +broken_atom_size: + { + GST_ELEMENT_ERROR (piffdemux, STREAM, DEMUX, + ("This file is corrupt and cannot be played."), + ("Atom '%" GST_FOURCC_FORMAT "' has size of %u bytes, but we have only " + "%u bytes available.", GST_FOURCC_ARGS (fourcc), node_length, + length)); + + return FALSE; + } +} + + +static GNode * +piffdemux_tree_get_child_by_type (GNode * node, guint32 fourcc) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc; + + for (child = g_node_first_child (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_fourcc = PIFF_FOURCC (buffer + 4); + + if (G_UNLIKELY (child_fourcc == fourcc)) { + return child; + } + } + return NULL; +} + +static GNode * +piffdemux_tree_get_child_by_type_full (GNode * node, guint32 fourcc, + GstByteReader * parser) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc, child_len; + + for (child = g_node_first_child (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_len = PIFF_UINT32 (buffer); + child_fourcc = PIFF_FOURCC (buffer + 4); + + if (G_UNLIKELY (child_fourcc == fourcc)) { + if (G_UNLIKELY (child_len < (4 + 4))) + return NULL; + /* FIXME: must verify if atom length < parent atom length */ + gst_byte_reader_init (parser, buffer + (4 + 4), child_len - (4 + 4)); + return child; + } + } + return NULL; +} + +static GNode * +piffdemux_tree_get_sibling_by_type_full (GNode * node, guint32 fourcc, + GstByteReader * parser) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc, child_len; + + for (child = g_node_next_sibling (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_fourcc = PIFF_FOURCC (buffer + 4); + + if (child_fourcc == fourcc) { + if (parser) { + child_len = PIFF_UINT32 (buffer); + if (G_UNLIKELY (child_len < (4 + 4))) + return NULL; + /* FIXME: must verify if atom length < parent atom length */ + gst_byte_reader_init (parser, buffer + (4 + 4), child_len - (4 + 4)); + } + return child; + } + } + return NULL; +} + +static GNode * +piffdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc) +{ + return piffdemux_tree_get_sibling_by_type_full (node, fourcc, NULL); +} + +#define _codec(name) \ + do { \ + if (codec_name) { \ + *codec_name = g_strdup (name); \ + } \ + } while (0) + +void +gst_piffdemux_set_video_params (GstPiffDemux * piffdemux, guint fourcc, + guint width, guint height, + guint fps_n, guint fps_d, unsigned char *codec_data, unsigned int codec_data_len) +{ + GstCaps *caps = NULL; + GstBuffer *dci = NULL; + + if (codec_data && codec_data_len) { + dci = gst_buffer_new_and_alloc (codec_data_len); + if (!dci) { + GST_ERROR_OBJECT (piffdemux, "failed to create codec data buffer..."); + } else { + memcpy (GST_BUFFER_DATA(dci), codec_data, codec_data_len); + } + } + + switch (fourcc) { + + case GST_MAKE_FOURCC ('a', 'v', 'c', '1'): + caps = gst_caps_new_simple ("video/x-h264", + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, + "stream-format", G_TYPE_STRING, "avc", + "alignment", G_TYPE_STRING, "au", + "codec_data", GST_TYPE_BUFFER, dci, + NULL); + break; + + case FOURCC_ovc1: + caps = gst_caps_new_simple ("video/x-wmv", + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, + "wmvversion", G_TYPE_INT, 3, + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'V', 'C', '1'), + "codec_data", GST_TYPE_BUFFER, dci, + NULL); + break; + + default: { + char *s; + s = g_strdup_printf ("video/x-gst-fourcc-%" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + caps = gst_caps_new_simple (s, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, + "codec_data", GST_TYPE_BUFFER, dci, + NULL); + break; + } + } + + piffdemux->stream->caps = caps; + gchar *caps_string = gst_caps_to_string(caps); + GST_INFO_OBJECT (piffdemux, "prepared video caps : %s", caps_string); + g_free(caps_string); + caps_string = NULL; +} + +void +gst_piffdemux_set_audio_params (GstPiffDemux * piffdemux, guint fourcc, + guint sampling_rate, guint bps, guint channels, unsigned char *codec_data, unsigned int codec_data_len) +{ + GstCaps *caps = NULL; + GstBuffer *dci = NULL; + + if (codec_data && codec_data_len) { + dci = gst_buffer_new_and_alloc (codec_data_len); + if (!dci) { + GST_ERROR_OBJECT (piffdemux, "failed to create codec data buffer..."); + } else { + memcpy (GST_BUFFER_DATA(dci), codec_data, codec_data_len); + } + } + + switch (fourcc) { + + case GST_MAKE_FOURCC ('m', 'p', '4', 'a'): + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 4, + "framed", G_TYPE_BOOLEAN, TRUE, + "stream-format", G_TYPE_STRING, "raw", + "rate", G_TYPE_INT, (int) sampling_rate, + "channels", G_TYPE_INT, channels, + NULL); + break; + + case FOURCC_owma: + caps = gst_caps_new_simple ("audio/x-wma", + "rate", G_TYPE_INT, (int) sampling_rate, + "channels", G_TYPE_INT, channels, + NULL); + break; + + default: { + char *s; + s = g_strdup_printf ("audio/x-gst-fourcc-%" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + caps = gst_caps_new_simple (s, + "rate", G_TYPE_INT, (int) sampling_rate, + "channels", G_TYPE_INT, channels, + NULL); + break; + } + } + + piffdemux->stream->caps = caps; + char *tmp_caps_name = gst_caps_to_string(caps); + GST_INFO_OBJECT (piffdemux, "prepared audio caps : %s", tmp_caps_name); + g_free(tmp_caps_name); + +} + +#define g_marshal_value_peek_object(v) g_value_get_object (v) + +void +__gst_piffdemux_marshal_BOOLEAN__OBJECT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__OBJECT) (gpointer data1, + gpointer arg_1, + gpointer data2); + register GMarshalFunc_BOOLEAN__OBJECT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__OBJECT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_object (param_values + 1), + data2); + + g_value_set_boolean (return_value, v_return); +} + +#define PIFFDEMUX_SPSPPS_LENGTH_SIZE 2 + +static gboolean +ConvertH264_MetaDCI_to_3GPPDCI(unsigned char *dci_meta_buf, unsigned int dci_meta_size, unsigned char **dci_3gpp_buf, unsigned int *dci_3gpp_size) +{ + unsigned short unit_size = 0; + unsigned int total_size = 0; + unsigned char unit_nb = 0; + unsigned char sps_done = 0; + const unsigned char *extradata = NULL; + unsigned int h264_nal_length_size = 0; + unsigned char *out = NULL; + //g_print ("\n\nConvertH264_MetaDCI_to_3GPPDCI Entering.............\n"); + + /* nothing to filter */ + if ((dci_meta_buf == NULL) || (dci_meta_size < 6)) + { + GST_ERROR ("Insufficient codec data...\n"); + return FALSE; + } + + /* Removing unnecessary info in meta data */ + extradata = (unsigned char *)dci_meta_buf + 4; + + /* retrieve Length of Length*/ + h264_nal_length_size = (*extradata++ & 0x03) + 1; + + GST_LOG ("Length Of Length is %d\n", h264_nal_length_size); + if (h264_nal_length_size == 3) + { + GST_ERROR ("LengthOfLength is WRONG...\n"); + return FALSE; + } + + /* retrieve sps and pps unit(s) */ + unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */ + GST_LOG ("No. of SPS units = %u\n", unit_nb); + + if (!unit_nb) + { + GST_ERROR ("SPS is not present....\n"); + return FALSE; + } + + while (unit_nb--) + { + /* get SPS/PPS data Length*/ + unit_size = PIFFDEMUX_RB16(extradata); + + GST_LOG ("SPS size = %d", unit_size); + + /* Extra 4 bytes for adding size of the packet */ + total_size += unit_size + h264_nal_length_size; + + /* Check if SPS/PPS Data Length crossed buffer Length */ + if ((extradata + 2 + unit_size) > (dci_meta_buf + dci_meta_size)) + { + GST_ERROR ("SPS Length is wrong in DCI...\n"); + if (out) + { + free (out); + } + return FALSE; + } + out = realloc(out, total_size); + if (!out) + { + GST_ERROR ("realloc FAILED...\n"); + return FALSE; + } + /* Copy length of SPS header */ + // tmp = (unsigned int *)(out + total_size - unit_size - h264_nal_length_size); + // *tmp = unit_size; + (out + total_size - unit_size - h264_nal_length_size)[0] = 0; + (out + total_size - unit_size - h264_nal_length_size)[1] = 0; + (out + total_size - unit_size - h264_nal_length_size)[2] = 0; + (out + total_size - unit_size - h264_nal_length_size)[3] = (unsigned char)unit_size; + + // memcpy(out + total_size - unit_size - h264_nal_length_size, &unit_size, h264_nal_length_size); + //g_print ("out[0] = %02x, out[1] = %02x, out[2] = %02x = out[3] = %02x\n", + //out[total_size - unit_size - h264_nal_length_size], out[total_size - unit_size - h264_nal_length_size+1], + //out[total_size - unit_size - h264_nal_length_size + 2], out[total_size - unit_size - h264_nal_length_size + 3]); + + /* Copy SPS/PPS Length and data */ + memcpy(out + total_size - unit_size, extradata + PIFFDEMUX_SPSPPS_LENGTH_SIZE, unit_size); + + extradata += (PIFFDEMUX_SPSPPS_LENGTH_SIZE + unit_size); + + if (!unit_nb && !sps_done++) + { + /* Completed reading SPS data, now read PPS data */ + unit_nb = *extradata++; /* number of pps unit(s) */ + GST_DEBUG ("No. of PPS units = %d\n", unit_nb); + } + } + + *dci_3gpp_buf = malloc (total_size); + if (NULL == *dci_3gpp_buf) + { + GST_ERROR ("Memory Allocation FAILED...\n"); + free (out); + return FALSE; + } + + memcpy(*dci_3gpp_buf, out, total_size); + *dci_3gpp_size = total_size; + + GST_DEBUG ("SPS_PPS size = %d\n", total_size); + + free(out); + + return TRUE; + } + +#ifdef DRM_ENABLE +static void +piffdemux_get_playready_licence (GstPiffDemux *demux) +{ + int ret = -1; + drm_trusted_piff_get_license_info_s license_info; + drm_trusted_request_type_e request_type = DRM_TRUSTED_REQ_TYPE_PIFF_GET_LICENSE; + + memset(&license_info, 0x00, sizeof(drm_trusted_piff_get_license_info_s)); + + license_info.lic_header.header = (unsigned char*) GST_BUFFER_DATA (demux->protection_header); + license_info.lic_header.header_len = GST_BUFFER_SIZE (demux->protection_header); + + ret = drm_trusted_handle_request(request_type, (void *) &license_info, NULL); + if (DRM_TRUSTED_RETURN_SUCCESS != ret) { + GST_ERROR_OBJECT (demux,"failed to get license..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to get license"), (NULL)); + return; + } + + GST_INFO_OBJECT (demux, "Got license....\n"); + + demux->encrypt_content = TRUE; + + return; +} + +void +test_drm_trusted_operation_cb(drm_trusted_user_operation_info_s *operation_info, void *output_data) +{ + g_print ("Callback Hit:test_drm_trusted_operation_cb\n"); + g_print ("operation_status=%d\n",operation_info->operation_status); + g_print ("operation_type=%d\n",operation_info->operation_type); +} +#endif + diff --git a/piffdemux/src/piffdemux.h b/piffdemux/src/piffdemux.h new file mode 100755 index 0000000..2147ea6 --- /dev/null +++ b/piffdemux/src/piffdemux.h @@ -0,0 +1,137 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_PIFFDEMUX_H__ +#define __GST_PIFFDEMUX_H__ + +#include +#include +#include "piffcommon.h" + +#ifdef DRM_ENABLE +#include +#include +#include +#include +#endif + +G_BEGIN_DECLS + +GST_DEBUG_CATEGORY_EXTERN (piffdemux_debug); +#define GST_CAT_DEFAULT piffdemux_debug + +#define GST_TYPE_PIFFDEMUX \ + (gst_piffdemux_get_type()) +#define GST_PIFFDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIFFDEMUX,GstPiffDemux)) +#define GST_PIFFDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIFFDEMUX,GstPiffDemuxClass)) +#define GST_IS_PIFFDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIFFDEMUX)) +#define GST_IS_PIFFDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIFFDEMUX)) + +#define GST_PIFFDEMUX_CAST(obj) ((GstPiffDemux *)(obj)) + +/* piffdemux produces these for atoms it cannot parse */ +#define GST_PIFF_DEMUX_PRIVATE_TAG "private-piff-tag" +#define GST_PIFF_DEMUX_CLASSIFICATION_TAG "classification" + +#define GST_PIFFDEMUX_MAX_STREAMS 8 + +typedef struct _GstPiffDemux GstPiffDemux; +typedef struct _GstPiffDemuxClass GstPiffDemuxClass; +typedef struct _PiffDemuxStream PiffDemuxStream; + +struct _GstPiffDemux { + GstElement element; + + /* pads */ + GstPad *sinkpad; + GstPad *srcpad; + + PiffDemuxStream *stream; + + guint32 timescale; + gint64 duration; + + gboolean fragmented; + guint64 moof_offset; + gint state; + gboolean posted_redirect; + + /* push based variables */ + guint neededbytes; + guint todrop; + GstAdapter *adapter; + GstBuffer *mdatbuffer; + guint64 mdatleft; + + /* offset of the media data (i.e.: Size of header) */ + guint64 offset; + /* offset of the mdat atom */ + guint64 mdatoffset; + guint64 first_mdat; + + GstTagList *tag_list; + + /* configured playback region */ + GstSegment segment; + gboolean segment_running; + GstEvent *pending_newsegment; + + /* gst index support */ + GstIndex *element_index; + gint index_id; + + gint64 requested_seek_time; + guint64 seek_offset; + gboolean moof_rcvd; + + /* live specific params */ + piff_live_param_t *param; + guint lookahead_cnt; + gboolean is_live; + + gboolean encrypt_content; + gboolean decrypt_init; +#ifdef DRM_ENABLE + DRM_DECRYPT_HANDLE pr_handle; +#endif + GstBuffer *protection_header; +}; + +struct _GstPiffDemuxClass { + GstElementClass parent_class; + void (*live_param) (GstPiffDemux *piff, const piff_live_param_t *param); +}; + +GType gst_piffdemux_get_type (void); + +/* prepares video caps based on input params */ +void gst_piffdemux_set_video_params (GstPiffDemux * piffdemux, guint fourcc, guint width, guint height, + guint fps_n, guint fps_d, unsigned char *codec_data, unsigned int codec_data_len); +/* prepares audio caps based on input params */ +void gst_piffdemux_set_audio_params (GstPiffDemux * piffdemux, guint fourcc, guint sampling_rate, guint bps, + guint channels, unsigned char *codec_data, unsigned int codec_data_len); +G_END_DECLS + +#endif /* __GST_PIFFDEMUX_H__ */ + diff --git a/piffdemux/src/piffdemux_dump.c b/piffdemux/src/piffdemux_dump.c new file mode 100755 index 0000000..b2d55f7 --- /dev/null +++ b/piffdemux/src/piffdemux_dump.c @@ -0,0 +1,325 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) 2009 Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "piffdemux_types.h" +#include "piffdemux_dump.h" + +#include "piffatomparser.h" + +#include + +#define GET_UINT8(data) gst_byte_reader_get_uint8_unchecked(data) +#define GET_UINT16(data) gst_byte_reader_get_uint16_be_unchecked(data) +#define GET_UINT32(data) gst_byte_reader_get_uint32_be_unchecked(data) +#define GET_UINT64(data) gst_byte_reader_get_uint64_be_unchecked(data) +#define GET_FP32(data) (gst_byte_reader_get_uint32_be_unchecked(data)/65536.0) +#define GET_FP16(data) (gst_byte_reader_get_uint16_be_unchecked(data)/256.0) +#define GET_FOURCC(data) piff_atom_parser_get_fourcc_unchecked(data) + +gboolean +piffdemux_dump_vmhd (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + if (!piff_atom_parser_has_remaining (data, 4 + 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s mode/color: %08x", depth, "", GET_UINT32 (data)); + return TRUE; +} + + +gboolean +piffdemux_dump_mfro (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + if (!piff_atom_parser_has_remaining (data, 4)) + return FALSE; + + GST_LOG ("%*s size: %d", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +piffdemux_dump_tfra (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + guint64 time = 0, moof_offset = 0; + guint32 len = 0, num_entries = 0, ver_flags = 0, track_id = 0, i; + guint value_size, traf_size, trun_size, sample_size; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + + if (!gst_byte_reader_get_uint32_be (data, &track_id) || + gst_byte_reader_get_uint32_be (data, &len) || + gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s track ID: %u", depth, "", track_id); + GST_LOG ("%*s length: 0x%x", depth, "", len); + GST_LOG ("%*s n entries: %u", depth, "", num_entries); + + value_size = ((ver_flags >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + sample_size = (len & 3) + 1; + trun_size = ((len & 12) >> 2) + 1; + traf_size = ((len & 48) >> 4) + 1; + + if (!piff_atom_parser_has_chunks (data, num_entries, + value_size + value_size + traf_size + trun_size + sample_size)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + piff_atom_parser_get_offset (data, value_size, &time); + piff_atom_parser_get_offset (data, value_size, &moof_offset); + GST_LOG ("%*s time: %" G_GUINT64_FORMAT, depth, "", time); + GST_LOG ("%*s moof_offset: %" G_GUINT64_FORMAT, + depth, "", moof_offset); + GST_LOG ("%*s traf_number: %u", depth, "", + piff_atom_parser_get_uint_with_size_unchecked (data, traf_size)); + GST_LOG ("%*s trun_number: %u", depth, "", + piff_atom_parser_get_uint_with_size_unchecked (data, trun_size)); + GST_LOG ("%*s sample_number: %u", depth, "", + piff_atom_parser_get_uint_with_size_unchecked (data, sample_size)); + } + + return TRUE; +} + +gboolean +piffdemux_dump_tfhd (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + guint32 flags = 0, n = 0, track_id = 0; + guint64 base_data_offset = 0; + + if (!gst_byte_reader_skip (data, 1) || + !gst_byte_reader_get_uint24_be (data, &flags)) + return FALSE; + GST_LOG ("%*s flags: %08x", depth, "", flags); + + if (!gst_byte_reader_get_uint32_be (data, &track_id)) + return FALSE; + GST_LOG ("%*s track_id: %u", depth, "", track_id); + + if (flags & TF_BASE_DATA_OFFSET) { + if (!gst_byte_reader_get_uint64_be (data, &base_data_offset)) + return FALSE; + GST_LOG ("%*s base-data-offset: %" G_GUINT64_FORMAT, + depth, "", base_data_offset); + } + + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s sample-description-index: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_DURATION) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-duration: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_SIZE) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-size: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-flags: %u", depth, "", n); + } + + GST_LOG ("%*s duration-is-empty: %s", depth, "", + flags & TF_DURATION_IS_EMPTY ? "yes" : "no"); + + return TRUE; +} + +gboolean +piffdemux_dump_trun (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + guint32 flags = 0, samples_count = 0, data_offset = 0, first_sample_flags = 0; + guint32 sample_duration = 0, sample_size = 0, sample_flags = + 0, composition_time_offsets = 0; + int i = 0; + + if (!gst_byte_reader_skip (data, 1) || + !gst_byte_reader_get_uint24_be (data, &flags)) + return FALSE; + + GST_LOG ("%*s flags: %08x", depth, "", flags); + + if (!gst_byte_reader_get_uint32_be (data, &samples_count)) + return FALSE; + GST_LOG ("%*s samples_count: %u", depth, "", samples_count); + + if (flags & TR_DATA_OFFSET) { + if (!gst_byte_reader_get_uint32_be (data, &data_offset)) + return FALSE; + GST_LOG ("%*s data-offset: %u", depth, "", data_offset); + } + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &first_sample_flags)) + return FALSE; + GST_LOG ("%*s first-sample-flags: %u", depth, "", first_sample_flags); + } + + for (i = 0; i < samples_count; i++) { + if (flags & TR_SAMPLE_DURATION) { + if (!gst_byte_reader_get_uint32_be (data, &sample_duration)) + return FALSE; + GST_LOG ("%*s sample-duration: %u", depth, "", sample_duration); + } + + if (flags & TR_SAMPLE_SIZE) { + if (!gst_byte_reader_get_uint32_be (data, &sample_size)) + return FALSE; + GST_LOG ("%*s sample-size: %u", depth, "", sample_size); + } + + if (flags & TR_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &sample_flags)) + return FALSE; + GST_LOG ("%*s sample-flags: %u", depth, "", sample_flags); + } + + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + if (!gst_byte_reader_get_uint32_be (data, &composition_time_offsets)) + return FALSE; + GST_LOG ("%*s composition_time_offsets: %u", depth, "", + composition_time_offsets); + } + } + + return TRUE; +} + +gboolean +piffdemux_dump_trex (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + if (!piff_atom_parser_has_remaining (data, 4 + 4 + 4 + 4 + 4 + 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s track ID: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s default sample desc. index: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample duration: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample size: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample flags: %08x", depth, "", + GET_UINT32 (data)); + + return TRUE; +} + + +gboolean +piffdemux_dump_sdtp (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + guint32 version; + guint8 val; + guint i = 1; + + version = GET_UINT32 (data); + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + /* the sample_count is specified in the stsz or stz2 box. + * the information for a sample is stored in a single byte, + * so we read until there are no remaining bytes */ + while (piff_atom_parser_has_remaining (data, 1)) { + val = GET_UINT8 (data); + GST_LOG ("%*s sample number: %d", depth, "", i); + GST_LOG ("%*s sample_depends_on: %d", depth, "", + ((guint16) (val)) & 0x3); + GST_LOG ("%*s sample_is_depended_on: %d", depth, "", + ((guint16) (val >> 2)) & 0x3); + GST_LOG ("%*s sample_has_redundancy: %d", depth, "", + ((guint16) (val >> 4)) & 0x3); + ++i; + } + return TRUE; +} + +gboolean +piffdemux_dump_unknown (GstPiffDemux * piffdemux, GstByteReader * data, int depth) +{ + int len; + + len = gst_byte_reader_get_remaining (data); + GST_LOG ("%*s length: %d", depth, "", len); + + GST_MEMDUMP_OBJECT (piffdemux, "unknown atom data", + gst_byte_reader_peek_data_unchecked (data), len); + return TRUE; +} + +static gboolean +piffdemux_node_dump_foreach (GNode * node, gpointer piffdemux) +{ + GstByteReader parser; + guint8 *buffer = (guint8 *) node->data; /* FIXME: move to byte reader */ + guint32 node_length; + guint32 fourcc; + const PiffNodeType *type; + int depth; + + node_length = GST_READ_UINT32_BE (buffer); + fourcc = GST_READ_UINT32_LE (buffer + 4); + + g_warn_if_fail (node_length >= 8); + + gst_byte_reader_init (&parser, buffer + 8, node_length - 8); + + type = piffdemux_type_get (fourcc); + + depth = (g_node_depth (node) - 1) * 2; + GST_LOG ("%*s'%" GST_FOURCC_FORMAT "', [%d], %s", + depth, "", GST_FOURCC_ARGS (fourcc), node_length, type->name); + + if (type->dump) { + gboolean ret; + + ret = type->dump (GST_PIFFDEMUX_CAST (piffdemux), &parser, depth); + + if (!ret) { + GST_WARNING ("%*s not enough data parsing atom %" GST_FOURCC_FORMAT, + depth, "", GST_FOURCC_ARGS (fourcc)); + } + } + + return FALSE; +} + +gboolean +piffdemux_node_dump (GstPiffDemux * piffdemux, GNode * node) +{ + if (__gst_debug_min < GST_LEVEL_LOG) + return TRUE; + + g_node_traverse (node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + piffdemux_node_dump_foreach, piffdemux); + return TRUE; +} diff --git a/piffdemux/src/piffdemux_dump.h b/piffdemux/src/piffdemux_dump.h new file mode 100755 index 0000000..5f34b50 --- /dev/null +++ b/piffdemux/src/piffdemux_dump.h @@ -0,0 +1,49 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2009> STEricsson + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_PIFFDEMUX_DUMP_H__ +#define __GST_PIFFDEMUX_DUMP_H__ + +#include +#include + +G_BEGIN_DECLS + +gboolean piffdemux_dump_vmhd (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_mfro (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_tfra (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_tfhd (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_trun (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_trex (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_sdtp (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); +gboolean piffdemux_dump_unknown (GstPiffDemux * piffdemux, GstByteReader * data, + int depth); + +gboolean piffdemux_node_dump (GstPiffDemux * piffdemux, GNode * node); + +G_END_DECLS +#endif /* __GST_PIFFDEMUX_DUMP_H__ */ diff --git a/piffdemux/src/piffdemux_fourcc.h b/piffdemux/src/piffdemux_fourcc.h new file mode 100755 index 0000000..7ce7a7a --- /dev/null +++ b/piffdemux/src/piffdemux_fourcc.h @@ -0,0 +1,86 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_QTDEMUX_FOURCC_H__ +#define __GST_QTDEMUX_FOURCC_H__ + +#include + +G_BEGIN_DECLS + + +#define FOURCC_vide GST_MAKE_FOURCC('v','i','d','e') +#define FOURCC_soun GST_MAKE_FOURCC('s','o','u','n') +#define FOURCC_subp GST_MAKE_FOURCC('s','u','b','p') +#define FOURCC_hint GST_MAKE_FOURCC('h','i','n','t') +#define FOURCC_mp4a GST_MAKE_FOURCC('m','p','4','a') +#define FOURCC_mp4v GST_MAKE_FOURCC('m','p','4','v') +#define FOURCC_MP4V GST_MAKE_FOURCC('M','P','4','V') +#define FOURCC_fmp4 GST_MAKE_FOURCC('f','m','p','4') +#define FOURCC_FMP4 GST_MAKE_FOURCC('F','M','P','4') + +#define FOURCC_meta GST_MAKE_FOURCC('m','e','t','a') + +#define FOURCC_____ GST_MAKE_FOURCC('-','-','-','-') + +#define FOURCC_free GST_MAKE_FOURCC('f','r','e','e') + +#define FOURCC_drms GST_MAKE_FOURCC('d','r','m','s') +#define FOURCC_drmi GST_MAKE_FOURCC('d','r','m','i') +#define FOURCC_avc1 GST_MAKE_FOURCC('a','v','c','1') +#define FOURCC_avcC GST_MAKE_FOURCC('a','v','c','C') + +#define FOURCC_ulaw GST_MAKE_FOURCC('u','l','a','w') +#define FOURCC_alaw GST_MAKE_FOURCC('a','l','a','w') + +#define FOURCC_raw_ GST_MAKE_FOURCC('r','a','w',' ') + +#define FOURCC_alac GST_MAKE_FOURCC('a','l','a','c') +#define FOURCC_samr GST_MAKE_FOURCC('s','a','m','r') +#define FOURCC_sawb GST_MAKE_FOURCC('s','a','w','b') +#define FOURCC_mdat GST_MAKE_FOURCC('m','d','a','t') +#define FOURCC_in24 GST_MAKE_FOURCC('i','n','2','4') + +#define FOURCC_text GST_MAKE_FOURCC('t','e','x','t') +#define FOURCC_tx3g GST_MAKE_FOURCC('t','x','3','g') +#define FOURCC_mp4s GST_MAKE_FOURCC('m','p','4','s') +#define FOURCC_uuid GST_MAKE_FOURCC('u','u','i','d') + +/* Fragmented MP4 */ + +#define FOURCC_mfhd GST_MAKE_FOURCC('m','f','h','d') +#define FOURCC_mfra GST_MAKE_FOURCC('m','f','r','a') +#define FOURCC_mfro GST_MAKE_FOURCC('m','f','r','o') +#define FOURCC_moof GST_MAKE_FOURCC('m','o','o','f') +#define FOURCC_mvex GST_MAKE_FOURCC('m','v','e','x') +#define FOURCC_sdtp GST_MAKE_FOURCC('s','d','t','p') +#define FOURCC_tfhd GST_MAKE_FOURCC('t','f','h','d') +#define FOURCC_tfxd GST_MAKE_FOURCC('t','f','x','d') +#define FOURCC_tfra GST_MAKE_FOURCC('t','f','r','a') +#define FOURCC_traf GST_MAKE_FOURCC('t','r','a','f') +#define FOURCC_trex GST_MAKE_FOURCC('t','r','e','x') +#define FOURCC_trun GST_MAKE_FOURCC('t','r','u','n') +#define FOURCC_ovc1 GST_MAKE_FOURCC('o','v','c','1') +#define FOURCC_owma GST_MAKE_FOURCC('o','w','m','a') +#define FOURCC_uuid GST_MAKE_FOURCC('u','u','i','d') +#define FOURCC_tfrf GST_MAKE_FOURCC('t','f','r','f') + +G_END_DECLS + +#endif /* __GST_QTDEMUX_FOURCC_H__ */ diff --git a/piffdemux/src/piffdemux_types.c b/piffdemux/src/piffdemux_types.c new file mode 100755 index 0000000..2fa8ea8 --- /dev/null +++ b/piffdemux/src/piffdemux_types.c @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "piffdemux_types.h" +#include "piffdemux_dump.h" +#include "piffdemux_fourcc.h" + +static const PiffNodeType piff_node_types[] = { + {FOURCC_vide, "video media", 0}, + {FOURCC_hint, "hint", 0,}, + {FOURCC_mp4a, "mp4a", 0,}, + {FOURCC_mp4v, "mp4v", 0,}, + {FOURCC_alac, "alac", 0,}, + {FOURCC_meta, "meta", 0, piffdemux_dump_unknown}, + {FOURCC_____, "----", PIFF_FLAG_CONTAINER,}, + {FOURCC_free, "free", 0,}, + {FOURCC_mfra, "movie fragment random access", + PIFF_FLAG_CONTAINER,}, + {FOURCC_tfra, "track fragment random access", 0, + piffdemux_dump_tfra}, + {FOURCC_mfro, "movie fragment random access offset", 0, + piffdemux_dump_mfro}, + {FOURCC_moof, "movie fragment", PIFF_FLAG_CONTAINER,}, + {FOURCC_mfhd, "movie fragment header", 0,}, + {FOURCC_traf, "track fragment", PIFF_FLAG_CONTAINER,}, + {FOURCC_tfhd, "track fragment header", 0, + piffdemux_dump_tfhd}, + {FOURCC_sdtp, "independent and disposable samples", 0, + piffdemux_dump_sdtp}, + {FOURCC_trun, "track fragment run", 0, piffdemux_dump_trun}, + {FOURCC_mdat, "moovie data", 0, piffdemux_dump_unknown}, + {FOURCC_trex, "moovie data", 0, piffdemux_dump_trex}, + {FOURCC_mvex, "mvex", PIFF_FLAG_CONTAINER,}, + {FOURCC_ovc1, "ovc1", 0}, + {FOURCC_owma, "owma", 0}, + {FOURCC_tfxd, "tfxd", 0}, + {FOURCC_tfrf, "tfrf", 0}, + {FOURCC_uuid, "uuid", 0}, + + {0, "unknown", 0,}, +}; + +static const int n_piff_node_types = + sizeof (piff_node_types) / sizeof (piff_node_types[0]); + +const PiffNodeType * +piffdemux_type_get (guint32 fourcc) +{ + int i; + + for (i = 0; i < n_piff_node_types; i++) { + if (G_UNLIKELY (piff_node_types[i].fourcc == fourcc)) + return piff_node_types + i; + } + + GST_WARNING ("unknown QuickTime node type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + + return piff_node_types + n_piff_node_types - 1; +} diff --git a/piffdemux/src/piffdemux_types.h b/piffdemux/src/piffdemux_types.h new file mode 100755 index 0000000..2c94887 --- /dev/null +++ b/piffdemux/src/piffdemux_types.h @@ -0,0 +1,67 @@ + +#ifndef __GST_PIFFDEMUX_TYPES_H__ +#define __GST_PIFFDEMUX_TYPES_H__ + +#include +#include + +#include "piffdemux.h" + +G_BEGIN_DECLS + +typedef gboolean (*PiffDumpFunc) (GstPiffDemux * piffdemux, GstByteReader * data, int depth); + +typedef struct _PiffNodeType PiffNodeType; + +#define PIFF_UINT32(a) (GST_READ_UINT32_BE(a)) +#define PIFF_UINT24(a) (GST_READ_UINT32_BE(a) >> 8) +#define PIFF_UINT16(a) (GST_READ_UINT16_BE(a)) +#define PIFF_UINT8(a) (GST_READ_UINT8(a)) +#define PIFF_FP32(a) ((GST_READ_UINT32_BE(a))/65536.0) +#define PIFF_SFP32(a) (((gint)(GST_READ_UINT32_BE(a)))/65536.0) +#define PIFF_FP16(a) ((GST_READ_UINT16_BE(a))/256.0) +#define PIFF_FOURCC(a) (GST_READ_UINT32_LE(a)) +#define PIFF_UINT64(a) ((((guint64)PIFF_UINT32(a))<<32)|PIFF_UINT32(((guint8 *)a)+4)) + +typedef enum { + PIFF_FLAG_NONE = (0), + PIFF_FLAG_CONTAINER = (1 << 0) +} PiffFlags; + +struct _PiffNodeType { + guint32 fourcc; + const gchar *name; + PiffFlags flags; + PiffDumpFunc dump; +}; + +enum TfFlags +{ + TF_BASE_DATA_OFFSET = 0x000001, /* base-data-offset-present */ + TF_SAMPLE_DESCRIPTION_INDEX = 0x000002, /* sample-description-index-present */ + TF_DEFAULT_SAMPLE_DURATION = 0x000008, /* default-sample-duration-present */ + TF_DEFAULT_SAMPLE_SIZE = 0x000010, /* default-sample-size-present */ + TF_DEFAULT_SAMPLE_FLAGS = 0x000020, /* default-sample-flags-present */ + TF_DURATION_IS_EMPTY = 0x100000 /* duration-is-empty */ +}; + +enum TrFlags +{ + TR_DATA_OFFSET = 0x000001, /* data-offset-present */ + TR_FIRST_SAMPLE_FLAGS = 0x000004, /* first-sample-flags-present */ + TR_SAMPLE_DURATION = 0x000100, /* sample-duration-present */ + TR_SAMPLE_SIZE = 0x000200, /* sample-size-present */ + TR_SAMPLE_FLAGS = 0x000400, /* sample-flags-present */ + TR_COMPOSITION_TIME_OFFSETS = 0x000800 /* sample-composition-time-offsets-presents */ +}; + +enum SEFlags +{ + SE_OVERRIDE_TE_FLAGS = 0x000001, /* override existing track encryption parameters */ + SE_USE_SUBSAMPLE_ENCRYPTION = 0x000002, /* Use SubSample Encryption */ +}; +const PiffNodeType *piffdemux_type_get (guint32 fourcc); + +G_END_DECLS + +#endif /* __GST_PIFFDEMUX_TYPES_H__ */ diff --git a/piffdemux/src/piffpalette.h b/piffdemux/src/piffpalette.h new file mode 100755 index 0000000..d3b4552 --- /dev/null +++ b/piffdemux/src/piffpalette.h @@ -0,0 +1,137 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_PIFFPALLETE_H__ +#define __GST_PIFFPALLETE_H__ + +#include + +G_BEGIN_DECLS + +static const guint32 ff_piff_default_palette_2[2] = { + 0xffffff, 0x000000 +}; + +static const guint32 ff_piff_default_palette_4[4] = { + 0x93655e, 0xffffff, 0xdfd0ab, 0x000000 +}; + +static const guint32 ff_piff_default_palette_16[16] = { + 0xfffbff, 0xefd9bb, 0xe8c9b1, 0x93655e, + 0xfcdee8, 0x9d8891, 0xffffff, 0xffffff, + 0xffffff, 0x474837, 0x7a5e55, 0xdfd0ab, + 0xfffbf9, 0xe8cac5, 0x8a7c77, 0x000000 +}; +static const guint32 ff_piff_default_palette_256[256] = { + 0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00, + 0xFFCCFF, 0xFFCCCC, 0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00, + 0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966, 0xFF9933, 0xFF9900, + 0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600, + 0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300, + 0xFF00FF, 0xFF00CC, 0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000, + 0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66, 0xCCFF33, 0xCCFF00, + 0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00, + 0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900, + 0xCC66FF, 0xCC66CC, 0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600, + 0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366, 0xCC3333, 0xCC3300, + 0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000, + 0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00, + 0x99CCFF, 0x99CCCC, 0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00, + 0x9999FF, 0x9999CC, 0x999999, 0x999966, 0x999933, 0x999900, + 0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600, + 0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300, + 0x9900FF, 0x9900CC, 0x990099, 0x990066, 0x990033, 0x990000, + 0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66, 0x66FF33, 0x66FF00, + 0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00, + 0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900, + 0x6666FF, 0x6666CC, 0x666699, 0x666666, 0x666633, 0x666600, + 0x6633FF, 0x6633CC, 0x663399, 0x663366, 0x663333, 0x663300, + 0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000, + 0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00, + 0x33CCFF, 0x33CCCC, 0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00, + 0x3399FF, 0x3399CC, 0x339999, 0x339966, 0x339933, 0x339900, + 0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600, + 0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300, + 0x3300FF, 0x3300CC, 0x330099, 0x330066, 0x330033, 0x330000, + 0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66, 0x00FF33, 0x00FF00, + 0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00, + 0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900, + 0x0066FF, 0x0066CC, 0x006699, 0x006666, 0x006633, 0x006600, + 0x0033FF, 0x0033CC, 0x003399, 0x003366, 0x003333, 0x003300, + 0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033, 0xEE0000, + 0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000, + 0x440000, 0x220000, 0x110000, 0x00EE00, 0x00DD00, 0x00BB00, + 0x00AA00, 0x008800, 0x007700, 0x005500, 0x004400, 0x002200, + 0x001100, 0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088, + 0x000077, 0x000055, 0x000044, 0x000022, 0x000011, 0xEEEEEE, + 0xDDDDDD, 0xBBBBBB, 0xAAAAAA, 0x888888, 0x777777, 0x555555, + 0x444444, 0x222222, 0x111111, 0x000000 +}; + +static const guint32 ff_piff_grayscale_palette_16[16] = { + 0xffffff, 0xeeeeee, 0xdddddd, 0xcccccc, + 0xbbbbbb, 0xaaaaaa, 0x999999, 0x888888, + 0x777777, 0x666666, 0x555555, 0x444444, + 0x333333, 0x222222, 0x111111, 0x000000 +}; + +static const guint32 ff_piff_grayscale_palette_256[256] = { + 0xffffff, 0xfefefe, 0xfdfdfd, 0xfcfcfc, 0xfbfbfb, 0xfafafa, 0xf9f9f9, + 0xf8f8f8, 0xf7f7f7, 0xf6f6f6, 0xf5f5f5, 0xf4f4f4, 0xf3f3f3, 0xf2f2f2, + 0xf1f1f1, 0xf0f0f0, 0xefefef, 0xeeeeee, 0xededed, 0xececec, 0xebebeb, + 0xeaeaea, 0xe9e9e9, 0xe8e8e8, 0xe7e7e7, 0xe6e6e6, 0xe5e5e5, 0xe4e4e4, + 0xe3e3e3, 0xe2e2e2, 0xe1e1e1, 0xe0e0e0, 0xdfdfdf, 0xdedede, 0xdddddd, + 0xdcdcdc, 0xdbdbdb, 0xdadada, 0xd9d9d9, 0xd8d8d8, 0xd7d7d7, 0xd6d6d6, + 0xd5d5d5, 0xd4d4d4, 0xd3d3d3, 0xd2d2d2, 0xd1d1d1, 0xd0d0d0, 0xcfcfcf, + 0xcecece, 0xcdcdcd, 0xcccccc, 0xcbcbcb, 0xcacaca, 0xc9c9c9, 0xc8c8c8, + 0xc7c7c7, 0xc6c6c6, 0xc5c5c5, 0xc4c4c4, 0xc3c3c3, 0xc2c2c2, 0xc1c1c1, + 0xc0c0c0, 0xbfbfbf, 0xbebebe, 0xbdbdbd, 0xbcbcbc, 0xbbbbbb, 0xbababa, + 0xb9b9b9, 0xb8b8b8, 0xb7b7b7, 0xb6b6b6, 0xb5b5b5, 0xb4b4b4, 0xb3b3b3, + 0xb2b2b2, 0xb1b1b1, 0xb0b0b0, 0xafafaf, 0xaeaeae, 0xadadad, 0xacacac, + 0xababab, 0xaaaaaa, 0xa9a9a9, 0xa8a8a8, 0xa7a7a7, 0xa6a6a6, 0xa5a5a5, + 0xa4a4a4, 0xa3a3a3, 0xa2a2a2, 0xa1a1a1, 0xa0a0a0, 0x9f9f9f, 0x9e9e9e, + 0x9d9d9d, 0x9c9c9c, 0x9b9b9b, 0x9a9a9a, 0x999999, 0x989898, 0x979797, + 0x969696, 0x959595, 0x949494, 0x939393, 0x929292, 0x919191, 0x909090, + 0x8f8f8f, 0x8e8e8e, 0x8d8d8d, 0x8c8c8c, 0x8b8b8b, 0x8a8a8a, 0x898989, + 0x888888, 0x878787, 0x868686, 0x858585, 0x848484, 0x838383, 0x828282, + 0x818181, 0x808080, 0x7f7f7f, 0x7e7e7e, 0x7d7d7d, 0x7c7c7c, 0x7b7b7b, + 0x7a7a7a, 0x797979, 0x787878, 0x777777, 0x767676, 0x757575, 0x747474, + 0x737373, 0x727272, 0x717171, 0x707070, 0x6f6f6f, 0x6e6e6e, 0x6d6d6d, + 0x6c6c6c, 0x6b6b6b, 0x6a6a6a, 0x696969, 0x686868, 0x676767, 0x666666, + 0x656565, 0x646464, 0x636363, 0x626262, 0x616161, 0x606060, 0x5f5f5f, + 0x5e5e5e, 0x5d5d5d, 0x5c5c5c, 0x5b5b5b, 0x5a5a5a, 0x595959, 0x585858, + 0x575757, 0x565656, 0x555555, 0x545454, 0x535353, 0x525252, 0x515151, + 0x505050, 0x4f4f4f, 0x4e4e4e, 0x4d4d4d, 0x4c4c4c, 0x4b4b4b, 0x4a4a4a, + 0x494949, 0x484848, 0x474747, 0x464646, 0x454545, 0x444444, 0x434343, + 0x424242, 0x414141, 0x404040, 0x3f3f3f, 0x3e3e3e, 0x3d3d3d, 0x3c3c3c, + 0x3b3b3b, 0x3a3a3a, 0x393939, 0x383838, 0x373737, 0x363636, 0x353535, + 0x343434, 0x333333, 0x323232, 0x313131, 0x303030, 0x2f2f2f, 0x2e2e2e, + 0x2d2d2d, 0x2c2c2c, 0x2b2b2b, 0x2a2a2a, 0x292929, 0x282828, 0x272727, + 0x262626, 0x252525, 0x242424, 0x232323, 0x222222, 0x212121, 0x202020, + 0x1f1f1f, 0x1e1e1e, 0x1d1d1d, 0x1c1c1c, 0x1b1b1b, 0x1a1a1a, 0x191919, + 0x181818, 0x171717, 0x161616, 0x151515, 0x141414, 0x131313, 0x121212, + 0x111111, 0x101010, 0x0f0f0f, 0x0e0e0e, 0x0d0d0d, 0x0c0c0c, 0x0b0b0b, + 0x0a0a0a, 0x090909, 0x080808, 0x070707, 0x060606, 0x050505, 0x040404, + 0x030303, 0x020202, 0x010101, 0x000000 +}; + +G_END_DECLS + +#endif /* __GST_PIFFPALETTE_H__ */ diff --git a/ssdemux/src/gstssdemux.c b/ssdemux/src/gstssdemux.c index 6bec15f..ca7c503 100755 --- a/ssdemux/src/gstssdemux.c +++ b/ssdemux/src/gstssdemux.c @@ -16,7 +16,9 @@ enum PROP_0, PROP_COOKIES, PROP_ALLOW_AUDIO_ONLY, - PROP_FRAGMENTS_CACHE, + PROP_CACHE_TIME, + PROP_LOW_PERCENTAGE, + PROP_HIGH_PERCENTAGE, PROP_BITRATE_SWITCH_TOLERANCE, PROP_LAST }; @@ -53,15 +55,16 @@ GST_DEBUG_CATEGORY_STATIC (gst_ss_demux_debug); static void _do_init (GType type) { - GST_DEBUG_CATEGORY_INIT (gst_ss_demux_debug, "ssdemux", 0, - "ssdemux element"); + GST_DEBUG_CATEGORY_INIT (gst_ss_demux_debug, "ssdemux", 0, "ssdemux element"); } GST_BOILERPLATE_FULL (GstSSDemux, gst_ss_demux, GstElement, GST_TYPE_ELEMENT, _do_init); -#define DEFAULT_FRAGMENTS_CACHE 0 +#define DEFAULT_CACHE_TIME 6*GST_SECOND #define DEFAULT_BITRATE_SWITCH_TOLERANCE 0.4 +#define DEFAULT_LOW_PERCENTAGE 1 +#define DEFAULT_HIGH_PERCENTAGE 99 struct _GstSSDemuxStream { @@ -79,6 +82,9 @@ struct _GstSSDemuxStream GstBus *bus; GMutex *lock; GCond *cond; + GMutex *queue_lock; + GCond *queue_full; + GCond *queue_empty; guint frag_cnt; GQueue *queue; gchar *uri; @@ -87,6 +93,17 @@ struct _GstSSDemuxStream GstCaps *caps; guint64 switch_ts; guint64 avg_dur; + gboolean is_buffering; + gint64 percent; + gboolean rcvd_percent; + guint64 push_block_time; + gint64 cached_duration; + + /* for fragment download rate calculation */ + guint64 download_start_ts; + guint64 download_stop_ts; + guint64 download_size; + }; static void gst_ss_demux_set_property (GObject * object, guint prop_id, @@ -107,20 +124,18 @@ static gboolean gst_ss_demux_create_download_pipe (GstSSDemux * demux, GstSSDemu static void gst_ss_demux_stop (GstSSDemux * demux, GstSSDemuxStream *stream); static gboolean gst_ss_demux_create_dummy_pipe (GstSSDemux * demux, GstSSDemuxStream *stream); static gboolean gst_ss_demux_create_dummy_sender(GstSSDemux *demux, GstSSDemuxStream *stream); +static void gst_ss_demux_push_loop (GstSSDemuxStream *stream); +static void gst_ss_demux_update_buffering (GstSSDemuxStream *stream, guint64 percent); static void gst_ss_demux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&ssdemux_videosrc_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&ssdemux_audiosrc_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&ssdemux_subsrc_template)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&ssdemux_sink_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&ssdemux_videosrc_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&ssdemux_audiosrc_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&ssdemux_subsrc_template)); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&ssdemux_sink_template)); gst_element_class_set_details_simple (element_class, "SS Demuxer", @@ -153,11 +168,22 @@ gst_ss_demux_class_init (GstSSDemuxClass * klass) "Allow audio only stream download in live case when download rate is less", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /* no.of fragments to cache */ - g_object_class_install_property (gobject_class, PROP_FRAGMENTS_CACHE, - g_param_spec_uint ("fragments-cache", "Fragments cache", - "Number of fragments needed to be cached to start playing", - 0, G_MAXUINT, DEFAULT_FRAGMENTS_CACHE, + g_object_class_install_property (gobject_class, PROP_CACHE_TIME, + g_param_spec_uint64 ("max-cache-time", "caching time", + "amount of data that can be cached in seconds", 0, G_MAXUINT64, + DEFAULT_CACHE_TIME, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_LOW_PERCENTAGE, + g_param_spec_int ("low-percent", "Low Percent", + "Low threshold to start buffering", + 1, 100, DEFAULT_LOW_PERCENTAGE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_HIGH_PERCENTAGE, + g_param_spec_int ("high-percent", "High percent", + "High threshold to complete buffering", + 2, 100, DEFAULT_HIGH_PERCENTAGE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_BITRATE_SWITCH_TOLERANCE, @@ -183,12 +209,15 @@ gst_ss_demux_init (GstSSDemux * demux, GstSSDemuxClass * klass) GST_DEBUG_FUNCPTR (gst_ss_demux_sink_event)); gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); - demux->need_cache = TRUE; - demux->fragments_cache = DEFAULT_FRAGMENTS_CACHE; - demux->cancelled = FALSE; + demux->max_cache_time = DEFAULT_CACHE_TIME; demux->cookies = NULL; demux->ss_mode = SS_MODE_NO_SWITCH; demux->switch_eos = FALSE; + demux->allow_audio_only = FALSE; + demux->percent = 100; + demux->low_percent = DEFAULT_LOW_PERCENTAGE; + demux->high_percent = DEFAULT_HIGH_PERCENTAGE; + demux->eos = FALSE; } static void @@ -199,6 +228,8 @@ gst_ss_demux_dispose (GObject * obj) for (n = 0; n < SS_STREAM_NUM; n++) { if (demux->streams[n]) { + gst_pad_stop_task ((demux->streams[n])->pad); + g_print ("\n\n\nstopped the TASK\n\n\n"); gst_ss_demux_stream_free (demux, demux->streams[n]); demux->streams[n] = NULL; } @@ -226,8 +257,14 @@ gst_ss_demux_set_property (GObject * object, guint prop_id, case PROP_ALLOW_AUDIO_ONLY: demux->allow_audio_only = g_value_get_boolean (value); break; - case PROP_FRAGMENTS_CACHE: - demux->fragments_cache = g_value_get_uint (value); + case PROP_CACHE_TIME: + demux->max_cache_time = g_value_get_uint64 (value); + break; + case PROP_LOW_PERCENTAGE: + demux->low_percent = g_value_get_int (value); + break; + case PROP_HIGH_PERCENTAGE: + demux->high_percent = g_value_get_int (value); break; case PROP_BITRATE_SWITCH_TOLERANCE: demux->bitrate_switch_tol = g_value_get_float (value); @@ -251,8 +288,14 @@ gst_ss_demux_get_property (GObject * object, guint prop_id, GValue * value, case PROP_ALLOW_AUDIO_ONLY: g_value_set_boolean (value, demux->allow_audio_only); break; - case PROP_FRAGMENTS_CACHE: - g_value_set_uint (value, demux->fragments_cache); + case PROP_CACHE_TIME: + g_value_set_uint64 (value, demux->max_cache_time); + break; + case PROP_LOW_PERCENTAGE: + g_value_set_int (value, demux->low_percent); + break; + case PROP_HIGH_PERCENTAGE: + g_value_set_int (value, demux->high_percent); break; case PROP_BITRATE_SWITCH_TOLERANCE: g_value_set_float (value, demux->bitrate_switch_tol); @@ -267,7 +310,7 @@ static gboolean gst_ss_demux_sink_event (GstPad * pad, GstEvent * event) { GstSSDemux *demux = GST_SS_DEMUX (gst_pad_get_parent (pad)); - GstQuery *query; + GstQuery *query = NULL; gboolean ret; gchar *uri; @@ -275,8 +318,8 @@ gst_ss_demux_sink_event (GstPad * pad, GstEvent * event) case GST_EVENT_EOS: { int i = 0; if (demux->manifest == NULL) { - GST_WARNING_OBJECT (demux, "Received EOS without a manifest."); - break; + GST_ERROR_OBJECT (demux, "Received EOS without a manifest."); + goto error; } GST_DEBUG_OBJECT (demux, "Got EOS on the sink pad: mainifest file fetched"); @@ -289,18 +332,38 @@ gst_ss_demux_sink_event (GstPad * pad, GstEvent * event) g_free (uri); } else { GST_ERROR_OBJECT (demux, "failed to query URI from upstream"); - return FALSE; + goto error; } gst_query_unref (query); + query = NULL; GST_LOG_OBJECT (demux, "data = %p & size = %d", GST_BUFFER_DATA(demux->manifest), GST_BUFFER_SIZE(demux->manifest)); if (!gst_ssm_parse_manifest (demux->parser, (char *)GST_BUFFER_DATA(demux->manifest), GST_BUFFER_SIZE(demux->manifest))) { /* In most cases, this will happen if we set a wrong url in the * source element and we have received the 404 HTML response instead of * the playlist */ - GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), - (NULL)); - return FALSE; + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); + goto error; + } + + { + unsigned char *protection_data = NULL; + unsigned int protection_len = 0; + + /* get protection-header from manifest parser */ + ret = gst_ssm_parse_get_protection_header (demux->parser, &protection_data, &protection_len); + if (!ret) { + GST_ERROR_OBJECT (demux, "failed to get protection header..."); + GST_ELEMENT_ERROR (demux, RESOURCE, NO_SPACE_LEFT, ("fragment allocation failed..."), (NULL)); + goto error; + } + + if (protection_data && protection_len) { + g_print ("Got the protection header...\n"); + demux->protection_header = gst_buffer_new (); + GST_BUFFER_DATA (demux->protection_header) = GST_BUFFER_MALLOCDATA (demux->protection_header) = protection_data; + GST_BUFFER_SIZE (demux->protection_header) = protection_len; + } } for( i = 0; i < SS_STREAM_NUM; i++) { @@ -309,9 +372,30 @@ gst_ss_demux_sink_event (GstPad * pad, GstEvent * event) // Add pad emission of the stream gst_ss_demux_stream_init (demux, stream, i); + + if (!gst_pad_is_linked (stream->pad)) { + GST_WARNING_OBJECT (demux, "%s - stream pad is not linked...clean up", ssm_parse_get_stream_name(i)); + gst_ss_demux_stream_free (demux, stream); + continue; + } + + /* create stream task */ g_static_rec_mutex_init (&stream->stream_lock); stream->stream_task = gst_task_create ((GstTaskFunction) gst_ss_demux_stream_loop, demux); + if (NULL == stream->stream_task) { + GST_ERROR_OBJECT (demux, "failed to create stream task..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to create stream task"), (NULL)); + goto error; + } gst_task_set_lock (stream->stream_task, &stream->stream_lock); + + /* create stream push loop */ + if (!gst_pad_start_task (stream->pad, (GstTaskFunction) gst_ss_demux_push_loop, stream)) { + GST_ERROR_OBJECT (demux, "failed to create push loop..."); + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to create push loop"), (NULL)); + goto error; + } + demux->streams[i] = stream; g_print ("Starting stream - %d task loop...\n", i); gst_task_start (stream->stream_task); @@ -319,17 +403,33 @@ gst_ss_demux_sink_event (GstPad * pad, GstEvent * event) } gst_event_unref (event); + gst_object_unref (demux); return TRUE; } case GST_EVENT_NEWSEGMENT: /* Swallow newsegments, we'll push our own */ gst_event_unref (event); + gst_object_unref (demux); return TRUE; default: break; } return gst_pad_event_default (pad, event); + +error: + // TODO: add closing + + //gst_ss_demux_stop (demux); + gst_event_unref (event); + gst_object_unref (demux); + + if (query) + gst_query_unref (query); + + g_print ("Returning from sink event...\n"); + return FALSE; + } static gboolean @@ -517,19 +617,105 @@ gst_ss_demux_get_next_fragment (GstSSDemux * demux, SS_STREAM_TYPE stream_type) error: { + GST_ELEMENT_ERROR (demux, RESOURCE, FAILED, ("failed to download fragment"), (NULL)); gst_ss_demux_stop (demux, stream); return FALSE; } end_of_list: { GST_INFO_OBJECT (demux, "Reached end of playlist, sending EOS"); - gst_pad_push_event (stream->pad, gst_event_new_eos ()); + demux->eos = TRUE; gst_ss_demux_stop (demux, stream); return TRUE; } } static void +gst_ss_demux_push_loop (GstSSDemuxStream *stream) +{ + GstBuffer *outbuf = NULL; + GstSSDemux *demux = stream->parent; + GstFlowReturn fret = GST_FLOW_OK; + + // TODO: need to take care of EOS handling.... + + g_mutex_lock (stream->queue_lock); + + if (g_queue_is_empty (stream->queue)) { + GST_DEBUG_OBJECT (stream->pad,"queue is empty wait till, some buffers are available..."); + if (demux->eos) { + GST_INFO_OBJECT (stream->pad, "stream EOS, pause the task"); + gst_pad_push_event (stream->pad, gst_event_new_eos ()); + gst_pad_pause_task (stream->pad); + g_print ("Paused the task"); + return; + } + g_cond_wait (stream->queue_empty, stream->queue_lock); + } + + outbuf = g_queue_pop_head (stream->queue); + + if (GST_BUFFER_DURATION_IS_VALID (outbuf)) { + stream->cached_duration -= GST_BUFFER_DURATION(outbuf); + } else { + g_print ("\nDuration field is not valid.. check this issue !!!!!!!!\n"); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid duration of a frame"), (NULL)); + g_mutex_unlock (stream->queue_lock); + return; + } + + g_cond_signal (stream->queue_full); + //g_print ("[%s] Signalled full condition...\n", ssm_parse_get_stream_name(stream->type)); + g_mutex_unlock (stream->queue_lock); + + if (!stream->sent_ns) { + guint64 duration = GST_CLOCK_TIME_NONE; + guint64 start = GST_CLOCK_TIME_NONE; + GstEvent *event = NULL; + + duration = gst_util_uint64_scale (GST_SSM_PARSE_GET_DURATION(demux->parser), GST_SECOND, + GST_SSM_PARSE_GET_TIMESCALE(demux->parser)); + + start = gst_util_uint64_scale (GST_SSM_PARSE_NS_START(demux->parser), GST_SECOND, + GST_SSM_PARSE_GET_TIMESCALE(demux->parser)); + + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, start, duration, start); + + GST_DEBUG_OBJECT(demux," new_segment start = %"GST_TIME_FORMAT, GST_TIME_ARGS(start)); + + if (!gst_pad_push_event (stream->pad, event)) { + GST_ERROR_OBJECT (demux, "failed to push newsegment event"); + return; // No need to close task for this, because sometimes pad can unlined + } + stream->sent_ns = TRUE; + } + + if (stream->type == SS_STREAM_VIDEO && demux->ss_mode == SS_MODE_AONLY) { + GST_BUFFER_TIMESTAMP (outbuf) = stream->switch_ts; + GST_BUFFER_DURATION (outbuf) = ((float)1/25) * GST_SECOND; + stream->switch_ts = GST_BUFFER_TIMESTAMP (outbuf) + GST_BUFFER_DURATION (outbuf); + g_print ("Dummy buffers ts : %"GST_TIME_FORMAT" and dur : %"GST_TIME_FORMAT"\n", + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (outbuf)), GST_TIME_ARGS(GST_BUFFER_DURATION (outbuf))); + gchar *caps_string = gst_caps_to_string(GST_BUFFER_CAPS(outbuf)); + g_print ("caps : %s\n", caps_string); + g_free(caps_string); + caps_string = NULL; + } + + /* push data to downstream*/ + fret = gst_pad_push (stream->pad, outbuf); + if (fret != GST_FLOW_OK) { + GST_ERROR_OBJECT (demux, "failed to push data, reason : %s", gst_flow_get_name (fret)); + goto error; + } + + //g_print ("[%s] pushed buffer\n", ssm_parse_get_stream_name(stream->type)); +error: + // TODO: need to close task & post error to bus + return; +} + +static void gst_ss_demux_stream_loop (GstSSDemux * demux) { GThread *self = NULL; @@ -539,16 +725,18 @@ gst_ss_demux_stream_loop (GstSSDemux * demux) self = g_thread_self (); for (stream_type = 0; stream_type < SS_STREAM_NUM; stream_type++) { - if (demux->streams[stream_type]->stream_task->abidata.ABI.thread == self) { + if (demux->streams[stream_type] && demux->streams[stream_type]->stream_task->abidata.ABI.thread == self) { stream = demux->streams[stream_type]; break; } } - /* download next fragment of stream_type */ - if (!gst_ss_demux_get_next_fragment (demux, stream_type)) { - GST_ERROR_OBJECT (demux, "failed to get next fragment..."); - goto error; + if (stream) { + /* download next fragment of stream_type */ + if (!gst_ss_demux_get_next_fragment (demux, stream_type)) { + GST_ERROR_OBJECT (demux, "failed to get next fragment..."); + goto error; + } } return; @@ -567,6 +755,7 @@ static gboolean gst_ss_demux_download_fragment (GstSSDemux *demux, GstSSDemuxStream *stream, const gchar * uri, guint64 start_ts) { GstStateChangeReturn ret; + GTimeVal time = {0, }; g_print ("Going to download fragment : %s\n", uri); if (!gst_ss_demux_create_download_pipe (demux, stream, uri, start_ts)) { @@ -574,6 +763,10 @@ gst_ss_demux_download_fragment (GstSSDemux *demux, GstSSDemuxStream *stream, con return FALSE; } + /* download rate calculation : note down start time*/ + g_get_current_time (&time); + stream->download_start_ts = (time.tv_sec * 1000000)+ time.tv_usec; + ret = gst_element_set_state (stream->pipe, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { GST_ERROR_OBJECT (demux, "set_state failed..."); @@ -711,7 +904,7 @@ static gboolean gst_ss_demux_create_download_pipe (GstSSDemux * demux, GstSSDemuxStream *stream, const gchar * uri, guint64 start_ts) { gchar *name = NULL; - GstCaps *caps = NULL; + gchar *caps_string = NULL; if (!gst_uri_is_valid (uri)) return FALSE; @@ -721,18 +914,19 @@ gst_ss_demux_create_download_pipe (GstSSDemux * demux, GstSSDemuxStream *stream, stream->pipe = gst_pipeline_new (name); if (!stream->pipe) { GST_ERROR_OBJECT (demux, "failed to create pipeline"); + g_free(name); + name = NULL; return FALSE; } - g_free(name); name = g_strdup_printf("%s-%s", stream->name, "httpsrc"); GST_DEBUG ("Creating source element for the URI:%s", uri); stream->urisrc = gst_element_make_from_uri (GST_URI_SRC, uri, name); if (!stream->urisrc) { GST_ERROR_OBJECT (demux, "failed to create urisrc"); + g_free(name); return FALSE; } - g_free(name); if (GST_SSM_PARSE_IS_LIVE_PRESENTATION(demux->parser)) g_object_set (G_OBJECT (stream->urisrc), "is-live", TRUE, NULL); @@ -743,32 +937,40 @@ gst_ss_demux_create_download_pipe (GstSSDemux * demux, GstSSDemuxStream *stream, stream->parser = gst_element_factory_make ("piffdemux", name); if (!stream->parser) { GST_ERROR_OBJECT (demux, "failed to create piffdemux element"); + g_free(name); + name = NULL; return FALSE; } - caps = ssm_parse_get_stream_caps (demux->parser, stream->type); - GST_INFO_OBJECT (stream->pad, "prepare caps = %s", gst_caps_to_string(caps)); + if (stream->caps) + gst_caps_unref (stream->caps); - g_object_set (G_OBJECT (stream->parser), "caps", caps, NULL); + stream->caps = ssm_parse_get_stream_caps (demux->parser, stream->type); + caps_string = gst_caps_to_string(stream->caps); + GST_INFO_OBJECT (stream->pad, "prepare caps = %s", caps_string); + g_free(caps_string); + caps_string = NULL; + + g_object_set (G_OBJECT (stream->parser), "caps", stream->caps, NULL); g_object_set (G_OBJECT (stream->parser), "start-ts", start_ts, NULL); g_object_set (G_OBJECT (stream->parser), "duration", GST_SSM_PARSE_GET_DURATION(demux->parser), NULL); g_object_set (G_OBJECT (stream->parser), "is-live", GST_SSM_PARSE_IS_LIVE_PRESENTATION(demux->parser), NULL); g_object_set (G_OBJECT (stream->parser), "lookahead-count", GST_SSM_PARSE_LOOKAHEAD_COUNT(demux->parser), NULL); + if (demux->protection_header) + g_object_set (G_OBJECT (stream->parser), "protection-header", demux->protection_header, NULL); g_signal_connect (stream->parser, "live-param", G_CALLBACK (gst_ss_demux_append_live_params), stream); - g_free(name); - name = g_strdup_printf("%s-%s", stream->name, "sink"); stream->sink = gst_element_factory_make ("appsink", name); if (!stream->sink) { GST_ERROR_OBJECT (demux, "failed to create appsink element"); + g_free(name); + name = NULL; return FALSE; } g_object_set (G_OBJECT (stream->sink), "emit-signals", TRUE, "sync", FALSE, NULL); g_signal_connect (stream->sink, "new-buffer", G_CALLBACK (gst_ssm_demux_on_new_buffer), stream); - g_free(name); - gst_bin_add_many (GST_BIN (stream->pipe), stream->urisrc, stream->parser, stream->sink, NULL); if (!gst_element_link_many (stream->urisrc, stream->parser, stream->sink, NULL)) { GST_ERROR ("failed to link elements..."); @@ -779,6 +981,9 @@ gst_ss_demux_create_download_pipe (GstSSDemux * demux, GstSSDemuxStream *stream, gst_bus_add_watch (stream->bus, (GstBusFunc)gst_ss_demux_download_bus_cb, stream); gst_object_unref (stream->bus); + g_free(name); + name = NULL; + return TRUE; } @@ -937,20 +1142,27 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: { - guint64 download_rate = -1; + GTimeVal time = {0, }; + gint idx = 0; + guint64 total_push_time = 0; + guint64 download_rate = 0; GST_INFO_OBJECT (stream->pad, "received EOS on download pipe.."); // increase the fragment count on EOS stream->frag_cnt++; - if (g_strrstr (gst_element_get_name(stream->urisrc), "http")) { - g_object_get (stream->urisrc, "download-rate", &download_rate, NULL); - g_print("*********** '%s' download rate = %d bps **************\n", stream->name, download_rate); - } + /* download rate calculation : note down start time*/ + g_get_current_time (&time); + stream->download_stop_ts = (time.tv_sec * 1000000)+ time.tv_usec; - // TODO: need to remove download_rate> 0 check.. make it generic - if ((stream->type == SS_STREAM_VIDEO) && (demux->ss_mode != SS_MODE_AONLY) && (download_rate >= 0)) { - if (stream->frag_cnt >= demux->fragments_cache) { + download_rate = ((stream->download_size * 8 * 1000000) / (stream->download_stop_ts - stream->download_start_ts - stream->push_block_time)); + g_print("*********** '%s' download rate = %"G_GUINT64_FORMAT" bpssss **************\n", stream->name, download_rate); + stream->download_size = 0; + stream->download_stop_ts = stream->download_start_ts = 0; + stream->push_block_time = 0; + + if ((stream->type == SS_STREAM_VIDEO) && (demux->ss_mode != SS_MODE_AONLY)) { + if (!stream->is_buffering) { /* for switching, we are considering video download rate only */ demux->ss_mode = gst_ssm_parse_switch_qualitylevel (demux->parser, download_rate); } @@ -983,19 +1195,21 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) g_print ("Error from %s\n", gst_element_get_name (GST_MESSAGE_SRC(msg))); - gst_message_parse_error( msg, &error, &debug ); + gst_message_parse_error( msg, &error, &debug); if (error) GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: error= %s\n", error->message); GST_ERROR_OBJECT (demux, "GST_MESSAGE_ERROR: debug = %s\n", debug); /* handling error, when client requests url, which is yet to be prepared by server */ - if ((!strncmp(error->message, "Precondition Failed", strlen("Precondition Failed"))) && (5 == error->code)) { + if (GST_IS_URI_HANDLER(GST_MESSAGE_SRC(msg))) { GstStateChangeReturn ret; /* wait for 1sec & request the url again */ // TODO: need to make wait time as generic or Adding loop count to request again & again - GST_INFO_OBJECT (demux, "ERROR : code = %d, msg = %s, NEED to request again", error->code, error->message); + if (error) + GST_INFO_OBJECT (demux, "ERROR : code = %d, msg = %s, NEED to request again", error->code, error->message); + usleep (1000000); // 1 sec /* put the current pipeline to NULL state */ @@ -1008,6 +1222,9 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) GST_ERROR_OBJECT (demux, "failed to create download pipeline"); if (!gst_element_post_message (GST_ELEMENT(demux), msg)) { GST_ERROR_OBJECT (demux, "failed to post error"); + g_free(debug); + debug = NULL; + return FALSE; } } @@ -1028,9 +1245,9 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) if (!gst_element_post_message (GST_ELEMENT(demux), msg)) { GST_ERROR_OBJECT (demux, "failed to post error"); gst_ss_demux_stop (demux, stream); - g_free( debug); + g_free(debug); debug = NULL; - g_error_free( error); + g_error_free(error); return FALSE; } gst_ss_demux_stop (demux, stream); @@ -1041,6 +1258,44 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) g_error_free( error); break; } + case GST_MESSAGE_BUFFERING: { + int n =0; + int total_cache_perc = 0; + int active_stream_cnt = 0; + GstSSDemuxStream *cur_stream = NULL; + int avg_percent = 0; + + /* update buffer percent */ + gst_message_parse_buffering (msg, &stream->rcvd_percent); + gchar *name = gst_element_get_name (GST_MESSAGE_SRC (msg)); + GST_LOG_OBJECT (stream->pad, "Internal bus : Buffering from %s = %d\n", name, stream->rcvd_percent); + g_free(name); + name = NULL; + // TODO: need to check for better logic + for (n = 0; n < SS_STREAM_NUM; n++) { + cur_stream = demux->streams[n]; + if (cur_stream) { + active_stream_cnt++; + total_cache_perc += cur_stream->rcvd_percent; + } + } + + avg_percent = total_cache_perc / active_stream_cnt; + + GST_LOG_OBJECT (demux, "avg buffering completed = %d", avg_percent); + + if (avg_percent > 100) + avg_percent = 100; + + // TODO: need to add mutex for protecting percent + if (avg_percent != demux->percent) { + demux->percent = avg_percent; + GST_LOG_OBJECT (demux, "#########Posting %d buffering msg to main bus ###########", demux->percent); + + gst_element_post_message (GST_ELEMENT (demux), gst_message_new_buffering (GST_OBJECT (demux), avg_percent)); + } + } + break; case GST_MESSAGE_WARNING: { char* debug = NULL; GError* error = NULL; @@ -1061,12 +1316,60 @@ gst_ss_demux_download_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) } static void +gst_ss_demux_update_buffering (GstSSDemuxStream *stream, guint64 percent) +{ + gboolean do_post = FALSE; + GstSSDemux *demux = stream->parent; + + if (stream->is_buffering) { + do_post = TRUE; + if (percent >= demux->high_percent) + stream->is_buffering = FALSE; + } else { + if (percent < demux->low_percent) { + stream->is_buffering = TRUE; + do_post = TRUE; + } + } + + if (do_post) { + GstMessage *message; + GstBufferingMode mode; + gint64 buffering_left = -1; + + percent = percent * 100 / demux->high_percent; + + if (percent > 100) + percent = 100; + + if (percent != stream->percent) { + stream->percent = percent; + + GST_DEBUG_OBJECT (stream->pad, "buffering %d percent", (gint) percent); + g_print ("'%s' buffering %d percent done\n", stream->name, (gint) percent); + + /* posting buffering to internal bus, which will take average & post to main bus */ + message = gst_message_new_buffering (GST_OBJECT_CAST (stream->sink), (gint) percent); + gst_element_post_message (GST_ELEMENT_CAST (stream->sink), message); + } + } + +} + +static void gst_ssm_demux_on_new_buffer (GstElement * appsink, void* data) { GstSSDemuxStream *stream = (GstSSDemuxStream *)data; GstSSDemux *demux = stream->parent; GstBuffer *inbuf = NULL; GstFlowReturn fret = GST_FLOW_OK; + GstBuffer *headbuf = NULL; + gint64 diff = 0; + gint64 percent = 0; + GTimeVal start = {0, }; + GTimeVal stop = {0, }; + guint64 push_start_time = 0; + guint64 push_end_time =0; inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); if (!inbuf) { @@ -1074,67 +1377,57 @@ gst_ssm_demux_on_new_buffer (GstElement * appsink, void* data) return; } - GST_LOG_OBJECT (stream->pad, "Inbuf : size = %d, ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, - GST_BUFFER_SIZE(inbuf), GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf)), GST_TIME_ARGS(GST_BUFFER_DURATION(inbuf))); - - // Queue the buffers till fragment cache reached... after reaching start pushing data to respective port - if ( stream->frag_cnt < demux->fragments_cache ) { - GST_LOG_OBJECT (demux, "queuing data till caching finished..."); - g_queue_push_tail (stream->queue, inbuf); - return; - } - - if (!stream->sent_ns) { - guint64 duration = GST_CLOCK_TIME_NONE; - guint64 start = GST_CLOCK_TIME_NONE; - GstEvent *event = NULL; + g_mutex_lock (stream->queue_lock); - duration = gst_util_uint64_scale (GST_SSM_PARSE_GET_DURATION(demux->parser), GST_SECOND, - GST_SSM_PARSE_GET_TIMESCALE(demux->parser)); + stream->download_size += GST_BUFFER_SIZE(inbuf); - start = gst_util_uint64_scale (GST_SSM_PARSE_NS_START(demux->parser), GST_SECOND, - GST_SSM_PARSE_GET_TIMESCALE(demux->parser)); + /* download rate calculation : note push_start_ts */ + g_get_current_time (&start); + push_start_time = (start.tv_sec * 1000000)+ start.tv_usec; - event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, - start, duration, start); + GST_LOG_OBJECT (stream->pad, "Inbuf : size = %d, ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_BUFFER_SIZE(inbuf), GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf)), GST_TIME_ARGS(GST_BUFFER_DURATION(inbuf))); - GST_DEBUG_OBJECT(demux," new_segment start = %"GST_TIME_FORMAT, GST_TIME_ARGS(start)); + g_queue_push_tail (stream->queue, inbuf); - if (!gst_pad_push_event (stream->pad, event)) { - GST_ERROR_OBJECT (demux, "failed to push newsegment event"); - return; - } - stream->sent_ns = TRUE; + if (GST_BUFFER_DURATION_IS_VALID (inbuf)) { + stream->cached_duration += GST_BUFFER_DURATION(inbuf); + } else { + g_print ("\nDuration field is not valid.. check this issue !!!!!!!!\n"); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid duration of a frame"), (NULL)); + g_mutex_unlock (stream->queue_lock); + return; } - while (!g_queue_is_empty(stream->queue)) { - GstBuffer *cache_buf = g_queue_pop_head (stream->queue); + if (stream->cached_duration >= 0) { + percent = (stream->cached_duration * 100) / demux->max_cache_time; + //g_print ("[%s] percent done = %d[%"G_GINT64_FORMAT"]\n", ssm_parse_get_stream_name(stream->type), percent, percent); - fret = gst_pad_push (stream->pad, cache_buf); - if (fret != GST_FLOW_OK) { - GST_ERROR_OBJECT (demux, "failed to push data, reason : %s", gst_flow_get_name (fret)); - goto error; + // TODO: need to decide, whther to call before wait or after ?? + gst_ss_demux_update_buffering (stream, percent); + + if (percent > 100) { + /* update buffering & wait if space is not available */ + GST_DEBUG_OBJECT (stream->pad, "Reached more than 100 percent, queue full & wait till free"); + g_cond_wait(stream->queue_full, stream->queue_lock); + GST_DEBUG_OBJECT (stream->pad,"Received signal to add more data..."); } + } else { + g_print ("cached duration can not be negative\n\n\n"); + GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid cached duration"), (NULL)); + g_mutex_unlock (stream->queue_lock); + return; } - if (stream->type == SS_STREAM_VIDEO && demux->ss_mode == SS_MODE_AONLY) { - GST_BUFFER_TIMESTAMP (inbuf) = stream->switch_ts; - GST_BUFFER_DURATION (inbuf) = ((float)1/25) * GST_SECOND; - stream->switch_ts = GST_BUFFER_TIMESTAMP (inbuf) + GST_BUFFER_DURATION (inbuf); - g_print ("Dummy buffers ts : %"GST_TIME_FORMAT" and dur : %"GST_TIME_FORMAT"\n", - GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (inbuf)), GST_TIME_ARGS(GST_BUFFER_DURATION (inbuf))); + /* download rate calculation : note push_stop_ts */ + g_get_current_time (&stop); + push_end_time = (stop.tv_sec * 1000000)+ stop.tv_usec; - g_print ("caps : %s\n", gst_caps_to_string(GST_BUFFER_CAPS(inbuf))); - } + stream->push_block_time += push_end_time - push_start_time; - /* push data to downstream*/ - fret = gst_pad_push (stream->pad, inbuf); - if (fret != GST_FLOW_OK) { - GST_ERROR_OBJECT (demux, "failed to push data, reason : %s", gst_flow_get_name (fret)); - goto error; - } + g_cond_signal (stream->queue_empty); -error: + g_mutex_unlock (stream->queue_lock); return; } @@ -1151,6 +1444,9 @@ gst_ss_demux_stream_init (GstSSDemux *demux, GstSSDemuxStream *stream, SS_STREAM stream->cond = g_cond_new (); stream->lock = g_mutex_new (); stream->queue = g_queue_new (); + stream->queue_full = g_cond_new (); + stream->queue_empty = g_cond_new (); + stream->queue_lock = g_mutex_new (); stream->parent = demux; stream->pipe = NULL; stream->urisrc = NULL; @@ -1163,6 +1459,13 @@ gst_ss_demux_stream_init (GstSSDemux *demux, GstSSDemuxStream *stream, SS_STREAM stream->sent_ns = FALSE; stream->switch_ts = GST_CLOCK_TIME_NONE; stream->avg_dur = GST_CLOCK_TIME_NONE; + stream->percent = 100; + stream->rcvd_percent = 0; + stream->push_block_time = 0; + stream->cached_duration = 0; + stream->download_start_ts = 0; + stream->download_stop_ts = 0; + stream->download_size = 0; if (stream->type == SS_STREAM_VIDEO) { stream->pad = gst_pad_new_from_static_template (&ssdemux_videosrc_template, "video"); @@ -1182,7 +1485,9 @@ gst_ss_demux_stream_init (GstSSDemux *demux, GstSSDemuxStream *stream, SS_STREAM gst_pad_set_query_function (stream->pad, gst_ss_demux_handle_src_query); stream->caps = ssm_parse_get_stream_caps (demux->parser, stream->type); - g_print ("prepare video caps = %s", gst_caps_to_string(stream->caps)); + gchar *caps_name = gst_caps_to_string(stream->caps); + g_print ("prepare video caps = %s", caps_name); + g_free(caps_name); GST_DEBUG_OBJECT (demux, "setting caps %" GST_PTR_FORMAT, stream->caps); gst_pad_set_caps (stream->pad, stream->caps); @@ -1215,7 +1520,18 @@ gst_ss_demux_stream_free (GstSSDemux * demux, GstSSDemuxStream * stream) g_mutex_free (stream->lock); stream->lock = NULL; } - + if (stream->queue_lock) { + g_mutex_free (stream->queue_lock); + stream->queue_lock = NULL; + } + if (stream->queue_full) { + g_cond_free (stream->queue_full); + stream->queue_full = NULL; + } + if (stream->queue_empty) { + g_cond_free (stream->queue_empty); + stream->queue_empty= NULL; + } g_free (stream); } static gboolean diff --git a/ssdemux/src/gstssdemux.h b/ssdemux/src/gstssdemux.h index adc5525..b230b96 100755 --- a/ssdemux/src/gstssdemux.h +++ b/ssdemux/src/gstssdemux.h @@ -40,17 +40,18 @@ struct _GstSSDemux /* Properties */ gchar **cookies; /* HTTP request cookies. */ gboolean allow_audio_only; /*In LIVE case, allow audio only download when downloadrate is less */ - guint fragments_cache; /* number of fragments needed to be cached to start playing */ + guint64 max_cache_time; gfloat bitrate_switch_tol; /* tolerance with respect to the fragment duration to switch the bitarate*/ - gboolean need_cache; - gboolean cancelled; - guint download_rate; GstBuffer *manifest; GstSSMParse *parser; /* manifest parser */ - + gint percent; + gint low_percent; + gint high_percent; + gboolean eos; GstSSDemuxStream *streams[SS_STREAM_NUM]; SS_BW_MODE ss_mode; gboolean switch_eos; + GstBuffer *protection_header; }; struct _GstSSDemuxClass diff --git a/ssdemux/src/ssmanifestparse.c b/ssdemux/src/ssmanifestparse.c index fd0f41c..45268ab 100755 --- a/ssdemux/src/ssmanifestparse.c +++ b/ssdemux/src/ssmanifestparse.c @@ -30,7 +30,7 @@ int_from_string (gchar * ptr, gchar ** endptr, gint * val, gint base) errno = 0; *val = strtol (ptr, &end, base); if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) || (errno != 0 && *val == 0)) { - g_print ("Error in strtol : %s\n", strerror(errno)); + g_print ("Error in strtol : %s\n", g_strerror(errno)); return FALSE; } @@ -105,7 +105,7 @@ ssm_parse_get_xml_prop_uint64 (GstSSMParse *parser, } return prop_uint64; -} +} static gint ssm_parser_sort_qualitylevels_by_bitrate (gconstpointer a, gconstpointer b) @@ -235,6 +235,22 @@ gst_ssm_parse_free (GstSSMParse *parser) static gboolean +gst_ssm_parse_confirm_audiotag(gchar ** fourcc, gint audio_tag) +{ + switch(audio_tag){ + case 255: + *fourcc = (gchar *)strdup("AACL"); + break; + case 353: + *fourcc = (gchar *)strdup("WMAP"); + break; + default: + return FALSE; + } + return TRUE; +} + +static gboolean ssm_parse_quality_node (GstSSMParse *parser, GstSSMStreamNode *stream, xmlNodePtr quality_node) { GstSSMQualityNode *quality_level = NULL; @@ -326,19 +342,24 @@ ssm_parse_quality_node (GstSSMParse *parser, GstSSMStreamNode *stream, xmlNodePt /* MANDATORY for audio & video: parsing FourCC attribute */ quality_level->fourcc = (gchar *)xmlGetProp(quality_node, (const xmlChar *) "FourCC"); + if(!quality_level->fourcc && SS_STREAM_AUDIO == stream->type){ + if(!gst_ssm_parse_confirm_audiotag(&quality_level->fourcc, quality_level->audio_tag)){ + GST_ERROR ("failed to set fourcc from audio tag %d",quality_level->audio_tag); + return FALSE; + } + } + if (!quality_level->fourcc && ((SS_STREAM_AUDIO == stream->type) || (SS_STREAM_VIDEO == stream->type))) { GST_ERROR ("failed to parse fourcc from quality node"); return FALSE; } - if (!((!strncmp ((char *)quality_level->fourcc, "AACL", 4)) || !strncmp ((char *)quality_level->fourcc, "WMAP", 4) || + if (quality_level->fourcc && !((!strncmp ((char *)quality_level->fourcc, "AACL", 4)) || !strncmp ((char *)quality_level->fourcc, "WMAP", 4) || (!strncmp ((char *)quality_level->fourcc, "H264", 4)) || !strncmp ((char *)quality_level->fourcc, "WVC1", 4) || (!strncmp ((char *)quality_level->fourcc, "TTML", 4)))) { GST_INFO ("Not a proper Fourcc Code...If possible take from SubType\n\n\n"); - if (quality_level->fourcc) { - free (quality_level->fourcc); - quality_level->fourcc = NULL; - } + free (quality_level->fourcc); + GST_DEBUG ("Subtype = %s\n\n",stream->StreamSubType); quality_level->fourcc = g_strdup (stream->StreamSubType); if (!((!strncmp ((char *)quality_level->fourcc, "AACL", 4)) || !strncmp ((char *)quality_level->fourcc, "WMAP", 4) || @@ -1059,9 +1080,11 @@ ssm_prepare_video_caps (GstSSMParse *parser, GstSSMStreamNode *stream) NULL); } - GST_INFO ( "prepared video caps : %s\n", gst_caps_to_string(caps)); + gchar *caps_name = gst_caps_to_string(caps); + GST_INFO ( "prepared video caps : %s\n", caps_name); + g_free(caps_name); - return caps; + return caps; } GstCaps * @@ -1071,8 +1094,10 @@ ssm_prepare_audio_caps (GstSSMParse *parser, GstSSMStreamNode *stream) GstBuffer *codec_data = NULL; GstCaps *caps = NULL; - if ((!strncmp ((char *)cur_quality_node->fourcc, "AACL", 4)) || !strncmp ((char *)cur_quality_node->fourcc, "WMAP", 4)) { + if (cur_quality_node->codec_data && + ((!strncmp ((char *)cur_quality_node->fourcc, "AACL", 4)) || !strncmp ((char *)cur_quality_node->fourcc, "WMAP", 4))) { guint DCI_len = strlen ((char *)cur_quality_node->codec_data); + gchar *dci = cur_quality_node->codec_data; gchar tmp[3] = {0, }; gint val = 0; gint codec_data_len = (DCI_len >>1); @@ -1087,14 +1112,14 @@ ssm_prepare_audio_caps (GstSSMParse *parser, GstSSMStreamNode *stream) /* copy codec data */ while (DCI_len) { memset (tmp, 0x00, 3); - strncpy ((char*)tmp, (char*)cur_quality_node->codec_data, 2); + strncpy ((char*)tmp, (char*)dci, 2); tmp[2] = '\0'; if (!int_from_string ((char*)tmp, NULL, &val , 16)) { GST_ERROR ("Failed to int from string..."); return NULL; } (GST_BUFFER_DATA(codec_data))[idx] = val; - cur_quality_node->codec_data += 2; + dci += 2; DCI_len = DCI_len - 2; //g_print ("val = 0x%02x, codec_data_length = %d, idx = %d\n", val, DCI_len, idx); idx++; @@ -1111,6 +1136,7 @@ ssm_prepare_audio_caps (GstSSMParse *parser, GstSSMStreamNode *stream) "stream-format", G_TYPE_STRING, "raw", "rate", G_TYPE_INT, (int) cur_quality_node->samplingrate, "channels", G_TYPE_INT, cur_quality_node->channels, + "codec_data", GST_TYPE_BUFFER, codec_data, NULL); } else if (!strncmp ((char *)cur_quality_node->fourcc, "WMAP", 4)) { caps = gst_caps_new_simple ("audio/x-wma", @@ -1127,7 +1153,9 @@ ssm_prepare_audio_caps (GstSSMParse *parser, GstSSMStreamNode *stream) "channels", G_TYPE_INT, cur_quality_node->channels, NULL); } - GST_INFO ( "prepared video caps : %s\n", gst_caps_to_string(caps)); + gchar *caps_name = gst_caps_to_string(caps); + GST_INFO ( "prepared video caps : %s\n", caps_name); + g_free(caps_name); return caps; } @@ -1152,6 +1180,7 @@ gst_ssm_parse_switch_qualitylevel (GstSSMParse *parser, guint drate) stream->quality_lists = g_list_next (stream->quality_lists); g_print ("Move to next quality level : drate = %d and bitrate = %d\n", drate, ((GstSSMQualityNode *)stream->quality_lists->data)->bitrate); ret = SS_MODE_AV; + bitrate = ((GstSSMQualityNode *)stream->quality_lists->data)->bitrate; } else { g_print ("Already at MAX Bitrate possible...\n"); ret = SS_MODE_NO_SWITCH; @@ -1164,6 +1193,7 @@ gst_ssm_parse_switch_qualitylevel (GstSSMParse *parser, guint drate) if (g_list_previous (stream->quality_lists)) { stream->quality_lists = g_list_previous (stream->quality_lists); g_print ("Move to previous quality level : drate = %d and bitrate = %d\n", drate, ((GstSSMQualityNode *)stream->quality_lists->data)->bitrate); + bitrate = ((GstSSMQualityNode *)stream->quality_lists->data)->bitrate; } else { g_print ("Reached MIN video bitrate possible...\n"); if (GST_SSM_PARSE_IS_LIVE_PRESENTATION(parser)) { @@ -1271,25 +1301,30 @@ gst_ssm_parse_seek_manifest (GstSSMParse *parser, guint64 seek_time) /* forward seek */ while (seek_time > stream_time) { stream->fragment_lists = g_list_next (stream->fragment_lists); - stream_time = gst_util_uint64_scale (((GstSSMFragmentNode *)stream->fragment_lists->data)->time, GST_SECOND, + if(stream->fragment_lists && stream->fragment_lists->data) { + stream_time = gst_util_uint64_scale (((GstSSMFragmentNode *)stream->fragment_lists->data)->time, GST_SECOND, GST_SSM_PARSE_GET_TIMESCALE(parser)); - GST_LOG ("seek time = %"GST_TIME_FORMAT", cur_time = %"GST_TIME_FORMAT, + GST_LOG ("seek time = %"GST_TIME_FORMAT", cur_time = %"GST_TIME_FORMAT, GST_TIME_ARGS(seek_time), GST_TIME_ARGS(stream_time)); + } } /* moving to fragment before our seeked time */ stream->fragment_lists = g_list_previous (stream->fragment_lists); - start_ts = ((GstSSMFragmentNode *)stream->fragment_lists->data)->time; + if(stream->fragment_lists && stream->fragment_lists->data) + start_ts = ((GstSSMFragmentNode *)stream->fragment_lists->data)->time; } else if (seek_time < stream_time) { /* backward seek */ while (seek_time < stream_time) { stream->fragment_lists = g_list_previous (stream->fragment_lists); - stream_time = gst_util_uint64_scale (((GstSSMFragmentNode *)stream->fragment_lists->data)->time, GST_SECOND, + if(stream->fragment_lists && stream->fragment_lists->data) { + stream_time = gst_util_uint64_scale (((GstSSMFragmentNode *)stream->fragment_lists->data)->time, GST_SECOND, GST_SSM_PARSE_GET_TIMESCALE(parser)); - GST_LOG ("seek time = %"GST_TIME_FORMAT", cur_time = %"GST_TIME_FORMAT, - GST_TIME_ARGS(seek_time), GST_TIME_ARGS(stream_time)); + GST_LOG ("seek time = %"GST_TIME_FORMAT", cur_time = %"GST_TIME_FORMAT, + GST_TIME_ARGS(seek_time), GST_TIME_ARGS(stream_time)); + start_ts = ((GstSSMFragmentNode *)stream->fragment_lists->data)->time; + } } - start_ts = ((GstSSMFragmentNode *)stream->fragment_lists->data)->time; } else { /* rare case */ start_ts = ((GstSSMFragmentNode *)stream->fragment_lists->data)->time; @@ -1313,6 +1348,29 @@ gst_ssm_parse_seek_manifest (GstSSMParse *parser, guint64 seek_time) return TRUE; } +gboolean +gst_ssm_parse_get_protection_header (GstSSMParse *parser, unsigned char **protection_header, unsigned int *protection_header_len) +{ + if (!parser->RootNode->ProtectNode) { + *protection_header = NULL; + *protection_header_len = 0; + return TRUE; + } + + if (parser->RootNode->ProtectNode->ContentSize && parser->RootNode->ProtectNode->Content) { + *protection_header = g_malloc0 (parser->RootNode->ProtectNode->ContentSize); + if (*protection_header == NULL) { + GST_ERROR ("failed to allocate memory..."); + return FALSE; + } + + memcpy (*protection_header, parser->RootNode->ProtectNode->Content, parser->RootNode->ProtectNode->ContentSize); + *protection_header_len = parser->RootNode->ProtectNode->ContentSize; + } + + return TRUE; +} + static gboolean convert_NALUnitDCI_to_PacktizedDCI (unsigned char *nalu_dci, unsigned char **packetized_dci, unsigned int *packetized_dci_len) { @@ -1605,3 +1663,6 @@ exit: #endif } + + + diff --git a/ssdemux/src/ssmanifestparse.h b/ssdemux/src/ssmanifestparse.h index 21c4042..16c900c 100755 --- a/ssdemux/src/ssmanifestparse.h +++ b/ssdemux/src/ssmanifestparse.h @@ -121,9 +121,9 @@ gboolean gst_ssm_parse_manifest (GstSSMParse *parser, char *data, unsigned int s gboolean gst_ssm_parse_get_next_fragment_url (GstSSMParse *parser, SS_STREAM_TYPE stream_type, gchar **uri, guint64 *start_ts); gboolean gst_ssm_parse_append_next_fragment (GstSSMParse *parser, SS_STREAM_TYPE stream_type, guint64 timestamp, guint64 duration); GstCaps *ssm_parse_get_stream_caps (GstSSMParse *parser, SS_STREAM_TYPE stream_type); -SS_BW_MODE -gst_ssm_parse_switch_qualitylevel (GstSSMParse *parser, guint drate); +SS_BW_MODE gst_ssm_parse_switch_qualitylevel (GstSSMParse *parser, guint drate); gboolean gst_ssm_parse_seek_manifest (GstSSMParse *parser, guint64 seek_time); +gboolean gst_ssm_parse_get_protection_header (GstSSMParse *parser, unsigned char **protection_header, unsigned int *protection_header_len); G_END_DECLS #endif /* __SS_MANIFEST_PARSE_H__ */ diff --git a/submux/Makefile.am b/submux/Makefile.am new file mode 100755 index 0000000..308a09c --- /dev/null +++ b/submux/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/submux/src/Makefile.am b/submux/src/Makefile.am new file mode 100755 index 0000000..695e81c --- /dev/null +++ b/submux/src/Makefile.am @@ -0,0 +1,27 @@ +# plugindir is set in configure + +############################################################################## +# change libgstplugin.la to something more suitable, e.g. libmysomething.la # +############################################################################## +plugin_LTLIBRARIES = libgstsubmux.la + +############################################################################## +# for the next set of variables, rename the prefix if you renamed the .la, # +# e.g. libgstplugin_la_SOURCES => libmysomething_la_SOURCES # +# libgstplugin_la_CFLAGS => libmysomething_la_CFLAGS # +# libgstplugin_la_LIBADD => libmysomething_la_LIBADD # +# libgstplugin_la_LDFLAGS => libmysomething_la_LDFLAGS # +############################################################################## + +# sources used to compile this plug-in +libgstsubmux_la_SOURCES = gstsubmux.c + +# flags used to compile this plugin +# add other _CFLAGS and _LIBS as needed +libgstsubmux_la_CFLAGS = $(GST_CFLAGS) $(DRM_CLIENT_CFLAGS) $(DRM_TRUSTED_CFLAGS) $(MMTA_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +libgstsubmux_la_LIBADD = $(GST_LIBS) $(DRM_CLIENT_LIBS)$(DRM_TRUSTED_LIBS) $(GST_BASE_LIBS) $(MMTA_LIBS)$(GST_APP_LIBS) -lgstapp-0.10 $(CRYPTO_LIBS) +libgstsubmux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +# headers we need but don't want installed +noinst_HEADERS = gstsubmux.h + diff --git a/submux/src/gstsubmux.c b/submux/src/gstsubmux.c new file mode 100755 index 0000000..98566e2 --- /dev/null +++ b/submux/src/gstsubmux.c @@ -0,0 +1,2451 @@ +/* + * SLP2.0 + * Copyright (c) 2011 Samsung Electronics, Inc. + * All rights reserved. + * + * This software is a confidential and proprietary information + * of Samsung Electronics, Inc. ("Confidential Information"). You + * shall not disclose such Confidential Information and shall use + * it only in accordance with the terms of the license agreement + * you entered into with Samsung Electronics. + */ + +/* + * @file gstsubmux.c + * @author Deepak Singh (deep.singh@samsung.com) + * @version 1.0 + * @brief This source code implements the gstreamer plugin for subtitle muxing requirement in media player. + * + */ + +/*! Revision History: + *! --------------------------------------------------------------------------- + *! DATE | AUTHOR | COMMENTS + *! --------------------------------------------------------------------------- + *! 1-4-2014 deep.singh@samsung.com created. + */ + +#include "config.h" +#include "gstsubmux.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +static const GstElementDetails gst_submux_plugin_details = GST_ELEMENT_DETAILS( + "submux", + "Codec/Parser/Subtitle", + "muxing of different subtitle stream", + "Samsung Electronics " +); +static GstStaticPadTemplate gst_submux_sink_template = GST_STATIC_PAD_TEMPLATE( + "sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS("video/x-dvd-subpicture; text/plain; application/x-subtitle; " + "text/x-pango-markup;" + "application/x-usf; subpicture/x-pgs; subtitle/x-kate; application/x-subtitle; " + "application/x-subtitle-sami; application/x-subtitle-tmplayer; " + "application/x-subtitle-mpl2; application/x-subtitle-dks; " + "application/x-subtitle-qttext") +); +static GstStaticPadTemplate gst_submux_src_template = GST_STATIC_PAD_TEMPLATE( + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/plain; text/x-pango-markup") +); +enum +{ + PROP_0, + PROP_ENCODING, + PROP_VIDEOFPS, + PROP_EXTSUB_CURRENT_LANGUAGE, + PROP_IS_INTERNAL, + PROP_LANG_LIST + +}; + +GST_DEBUG_CATEGORY_STATIC (gst_submux_debug); +#define GST_CAT_DEFAULT gst_submux_debug +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (gst_submux_debug, "submux", 0, "submux"); +//////////////////////////////////////////////////////// +// Gstreamer Base Prototype // +//////////////////////////////////////////////////////// + +GST_BOILERPLATE_FULL(Gstsubmux, gst_submux, GstElement, GST_TYPE_ELEMENT, _do_init); + +#define GST_SUBMUX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_SUBMUX, GstsubmuxPrivate)) +#define MAX_LANGUAGE 10 +static gint gst_submux_buffer_list_sorting (gconstpointer a, gconstpointer b); +static gboolean gst_submux_create_pipelines(Gstsubmux *self,GstPad * pad); +static GstPad* gst_submux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * req_name); +static void gst_submux_release_pad (GstElement * element, GstPad * pad); +static gchar* gst_submux_extract_data (Gstsubmux *submux); +static gboolean gst_create_own_language_list (Gstsubmux *submux) ; +static GstSubMuxFormat gst_submux_data_format_autodetect (gchar * match_str); +static gpointer gst_submux_data_format_autodetect_regex_once (GstSubMuxRegex regtype); +static gboolean gst_submux_format_autodetect (Gstsubmux * self); +static void gst_submux_base_init(gpointer klass); +static void gst_submux_class_init(GstsubmuxClass *klass); +static GstStateChangeReturn gst_submux_change_state (GstElement * element, GstStateChange transition); +static void gst_submux_init(Gstsubmux *submux, GstsubmuxClass *klass); +static gboolean gst_submux_setcaps(GstPad *pad, GstCaps *caps); +static GstFlowReturn gst_submux_chain (GstPad *pad, GstBuffer *buffer); +static void gst_submux_dispose(GObject *object); +static void gst_submux_loop (Gstsubmux * submux); +static gboolean gst_submux_stream_init(GstSubmuxStream * stream); +static void gst_submux_stream_deinit(GstSubmuxStream * stream,Gstsubmux * submux); +static void gst_submux_on_new_buffer (GstElement *appsink, void *data); +static gboolean gst_submux_handle_src_event (GstPad * pad, GstEvent * event); + +static gboolean gst_submux_handle_sink_event (GstPad * pad, GstEvent * event); +//////////////////////////////////////////////////////// +// Plugin Utility Prototype // +//////////////////////////////////////////////////////// +static void gst_submux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_submux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean gst_submux_deinit_private_values(Gstsubmux *submux); +static gchar *convert_to_utf8 (const gchar * str, gsize len, const gchar * encoding, + gsize * consumed, GError ** err, Gstsubmux * self); +#define DEFAULT_ENCODING NULL +#define DEFAULT_CURRENT_LANGUAGE NULL + +//////////////////////////////////////////////////////// +// Gstreamer Base Functions // +//////////////////////////////////////////////////////// + +/* +** +** Description : base init +** Params : (1) instance of gclass +** return : none +** Comments : The following code registers templates for src and sink pad. +** +*/ +static void +gst_submux_base_init(gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&gst_submux_sink_template)); + gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&gst_submux_src_template)); + gst_element_class_set_details(element_class, &gst_submux_plugin_details); +} + +/* +** +** Description : Initilizes the Gstsubmux's class +** Params : @ klass instance of submux plugin's class +** return : None +** Comments : Declaring properties and over-writing function pointers +** +*/ +static void +gst_submux_class_init(GstsubmuxClass *klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS(klass); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + g_type_class_add_private (klass, sizeof (GstsubmuxPrivate)); + gobject_class->set_property = gst_submux_set_property; + gobject_class->get_property = gst_submux_get_property; + g_object_class_install_property (gobject_class, PROP_ENCODING, + g_param_spec_string ("subtitle-encoding", "subtitle charset encoding", + "Encoding to assume if input subtitles are not in UTF-8 or any other " + "Unicode encoding. If not set, the GST_SUBTITLE_ENCODING environment " + "variable will be checked for an encoding to use. If that is not set " + "either, ISO-8859-15 will be assumed.", DEFAULT_ENCODING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_VIDEOFPS, + gst_param_spec_fraction ("video-fps", "Video framerate", + "Framerate of the video stream. This is needed by some subtitle " + "formats to synchronize subtitles and video properly. If not set " + "and the subtitle format requires it subtitles may be out of sync.", + 0, 1, G_MAXINT, 1, 24000, 1001, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXTSUB_CURRENT_LANGUAGE, + g_param_spec_string ("current-language", "Current language", + "Current language of the subtitle in external subtitle case.", + DEFAULT_CURRENT_LANGUAGE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_IS_INTERNAL, + g_param_spec_boolean ("is-internal", "is internal", + "TRUE for internal subtitle case", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_LANG_LIST, + g_param_spec_pointer ("lang-list", "language list", "List of languages selected/not selected", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + parent_class = g_type_class_peek_parent (klass); + gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_submux_request_new_pad); + gobject_class->dispose = GST_DEBUG_FUNCPTR(gst_submux_dispose); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_submux_change_state); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_submux_release_pad); +} + +/* +** +** Description : Initilizes the submux element +** Params : (1)instance of submux (2) instance of submux class +** return : None +** Comments : instantiate pads and add them to element, set pad calback functions +** +*/ +static void +gst_submux_init(Gstsubmux *submux, GstsubmuxClass *klass) +{ + GST_DEBUG_OBJECT (submux, "Entering in init"); + submux->priv = GST_SUBMUX_GET_PRIVATE(submux); + submux->srcpad = gst_pad_new_from_static_template(&gst_submux_src_template, "src"); + gst_pad_set_event_function (submux->srcpad, + GST_DEBUG_FUNCPTR (gst_submux_handle_src_event)); + submux->priv->first_buffer = FALSE; + gst_segment_init (&submux->segment, GST_FORMAT_TIME); + submux->flushing = FALSE; + submux->msl_streams = NULL; + submux->stop_loop = FALSE; + submux->need_segment = TRUE; + submux->pipeline_made = FALSE; + submux->external_sinkpad = FALSE; + submux->detected_encoding = NULL; + submux->encoding = NULL; + submux->seek_came = FALSE; + submux->sinkpads_count = 0; + submux->langlist_msg_posted = FALSE; + submux->cur_buf_array = NULL; + GST_DEBUG_OBJECT (submux, "Making flushing FALSE"); + submux->priv->is_internal = FALSE; + submux->external_filepath = NULL; + gst_element_add_pad (GST_ELEMENT (submux), submux->srcpad); + GST_DEBUG_OBJECT (submux, "Exiting in init"); +} + +/* +** +** Description : for setting the property of submux +** return : None +** Comments : To set the various properties of submux +** +*/ +static void +gst_submux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + Gstsubmux *submux = GST_SUBMUX (object); + guint length = 0; + gint i = 0; + GstLangStruct *cur_language=NULL; + GstSubmuxStream *cur_stream = NULL; + GST_OBJECT_LOCK (submux); + length = g_list_length(submux->priv->lang_list); + switch (prop_id) { + case PROP_ENCODING: + g_free (submux->encoding); + submux->encoding = g_value_dup_string (value); + GST_DEBUG_OBJECT (submux, "subtitle encoding set to %s", + GST_STR_NULL (submux->encoding)); + for(i = 0;i < length;i++) { + cur_stream = g_list_nth_data(submux->streams,i); + GST_DEBUG_OBJECT (submux, "setting the subtitle-encoding to %s", submux->encoding); + g_object_set (G_OBJECT (cur_stream->pipe_struc.parser), "subtitle-encoding", submux->encoding, NULL); + } + break; + case PROP_VIDEOFPS: + { + submux->fps_n = gst_value_get_fraction_numerator (value); + submux->fps_d = gst_value_get_fraction_denominator (value); + GST_DEBUG_OBJECT (submux, "video framerate set to %d/%d", submux->fps_n, submux->fps_d); + break; + } + case PROP_EXTSUB_CURRENT_LANGUAGE: { + for (i = 0; i < length; i++) { + cur_stream = g_list_nth_data(submux->streams, i); + cur_language = g_list_nth_data(submux->priv->lang_list, i); + GST_DEBUG_OBJECT (submux, "value of current-language key is %s", cur_language->language_key); + g_object_set (G_OBJECT (cur_stream->pipe_struc.parser), "current-language", + cur_language->language_key, NULL); + } + gchar *dup = g_value_dup_string (value); + GST_DEBUG_OBJECT (submux, "Setting property to %s", dup); + g_free(dup); + } + break; + case PROP_IS_INTERNAL: { + submux->priv->is_internal = g_value_get_boolean (value); + GST_DEBUG_OBJECT (submux, "Setting the is_internal prop to %d", submux->priv->is_internal); + break; + } + case PROP_LANG_LIST: { + submux->priv->lang_list = (GList*) g_value_get_pointer (value); + GST_DEBUG_OBJECT (submux, "updating the languages list and length is %d", g_list_length (submux->priv->lang_list)); + submux->msl_streams = g_list_copy (submux->priv->lang_list); + + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (submux); +} + +/* +** +** Description : for getting the property of submux +** return : None +** Comments : To get the various properties of submux in case called by MSL +** +*/ +static void +gst_submux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + Gstsubmux *submux = GST_SUBMUX (object); + + GST_OBJECT_LOCK (submux); + switch (prop_id) { + case PROP_ENCODING: + g_value_set_string (value, submux->encoding); + break; + case PROP_VIDEOFPS: + gst_value_set_fraction (value, submux->fps_n, submux->fps_d); + break; + case PROP_EXTSUB_CURRENT_LANGUAGE: + GST_DEBUG_OBJECT (submux, "Getting the current language"); + break; + case PROP_IS_INTERNAL: { + g_value_set_boolean(value,submux->priv->is_internal); + break; + } + case PROP_LANG_LIST: { + g_value_set_pointer(value,(gpointer)(submux->priv->lang_list)); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (submux); +} + +static void +gst_submux_dispose (GObject *object) +{ + Gstsubmux *submux = GST_SUBMUX(object); + int i = 0; + gchar *pad_name = gst_pad_get_name (submux->srcpad); + if (submux && GST_PAD_TASK(submux->srcpad)) { + GST_INFO_OBJECT (submux, "Stopping pad task : %s", pad_name); + GST_DEBUG_OBJECT (submux, "Stopping pad task : on src pad %p", submux->srcpad); + gst_pad_stop_task (submux->srcpad); + GST_INFO_OBJECT (submux, "stopped pad task : %s", pad_name); + } + g_free(pad_name); + if (submux->srcpad) { + gst_element_remove_pad (GST_ELEMENT_CAST (submux), submux->srcpad); + submux->srcpad = NULL; + } + if (submux->priv->is_internal) { + for (i = 0; i < (submux->sinkpads_count); i++){ + gst_submux_stream_deinit (g_list_nth_data (submux->streams, i), submux); + } + } else { + for (i = 0; i < g_list_length (submux->priv->lang_list); i++) { + gst_submux_stream_deinit (g_list_nth_data (submux->streams, i), submux); + } + } + gst_submux_deinit_private_values (submux); + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); + GST_DEBUG_OBJECT (submux, "Returning from finalize"); +} + +static void +gst_submux_stop (Gstsubmux* submux) +{ + GstSubmuxStream *new_stream = NULL; + guint i = 0; + submux->stop_loop = TRUE; + GST_INFO_OBJECT (submux, "stopping the loop"); + if (submux->priv->is_internal) { + for (i = 0; i < (submux->sinkpads_count); i++) { + new_stream = g_list_nth_data (submux->streams, i); + if (new_stream) { + g_mutex_lock (new_stream->queue_lock); + g_cond_signal (new_stream->queue_empty); + g_mutex_unlock (new_stream->queue_lock); + } + } + } else { + for (i = 0; i < g_list_length (submux->priv->lang_list); i++) { + new_stream = g_list_nth_data (submux->streams, i); + if (new_stream) { + g_mutex_lock (new_stream->queue_lock); + g_cond_signal (new_stream->queue_empty); + g_mutex_unlock (new_stream->queue_lock); + } + } + } +} + +static GstStateChangeReturn +gst_submux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + Gstsubmux *submux = GST_SUBMUX (element); + gboolean bret = FALSE; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_INFO_OBJECT (submux,"PAUSED->PLAYING"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_INFO_OBJECT (submux,"PAUSED->READY"); + gst_submux_stop (submux); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_INFO_OBJECT (submux,"PLAYING->PAUSED"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_INFO_OBJECT (submux,"PAUSED->READY"); + if(submux->msl_streams) { + g_list_free(submux->msl_streams); + submux->msl_streams = NULL; + } + if (submux->priv->lang_list && !submux->priv->is_internal) { + g_list_free (submux->priv->lang_list); + submux->priv->lang_list = NULL; + } + if (submux->external_filepath) { + g_free (submux->external_filepath); + submux->external_filepath = NULL; + } + GST_WARNING_OBJECT(submux,"stopping has been called ...Moved after change_state"); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + GST_INFO_OBJECT (submux,"READY->NULL"); + break; + default: + break; + } + return ret; +} + +/* +** +** Description : Setting the caps on sink pad based on upstream element's src pad +** Params : (1) GstPad to set the capabilities of +** (2) caps to be set +** return : TRUE on success +** Comments : this function handles the link with other elements +** +*/ +static gboolean +gst_submux_setcaps (GstPad *pad, GstCaps *caps) +{ + return TRUE; +} + +/*extracting data for file format detection*/ +static gchar* gst_submux_extract_data (Gstsubmux *submux){ + gchar * file_path_type = NULL; + gchar * file_path = NULL; + gchar * temp_path = NULL; + gchar *line = NULL; + gboolean is_converted = FALSE; + gchar *converted = NULL; + FILE * fp = NULL; + guint charCount = 0; + GError *err = NULL; + gsize * consumed = NULL; + + GstQuery *cquery; + GstStructure *structure; + const GValue *value; + GstPad *sinkpad = (GstPad *)g_list_nth_data (submux->sinkpad, 0); + structure = gst_structure_new ("FileSrcURI", + "file-uri", G_TYPE_STRING, NULL, NULL); + + cquery = gst_query_new_application (GST_QUERY_CUSTOM, structure); + + if (!gst_pad_peer_query (sinkpad, cquery)) + { + GST_ERROR_OBJECT (submux, "Failed to query SMI file path"); + gst_query_unref (cquery); + return NULL; + } + structure = gst_query_get_structure (cquery); + value = gst_structure_get_value (structure, "file-uri"); + file_path = g_strdup (g_value_get_string (value)); + + if (file_path == NULL){ + GST_ERROR_OBJECT (submux, "Could not parse the SMI file path"); + gst_query_unref (cquery); + return NULL; + } + + gst_query_unref (cquery); + temp_path = file_path; + GST_INFO_OBJECT (submux, "File path comes as %s", file_path); + + file_path_type = g_strndup ((gchar *) file_path, 4); + GST_INFO_OBJECT (submux, "Received file path by query = %s, %s", file_path, file_path_type); + if (!g_strcmp0(file_path_type, "file")){ + file_path += 7; + GST_INFO_OBJECT (submux, "File path comes as %s", file_path); + + fp = fopen (file_path, "r"); + if (!fp){ + GST_ERROR_OBJECT (submux, "Failed to open file"); + g_free(file_path_type); + g_free(temp_path); + return NULL; + } + } else { + GST_ERROR_OBJECT (submux, "File is not local"); + g_free(file_path_type); + g_free(temp_path); + return NULL; + } + line = (gchar*)g_malloc (2049); + charCount = fread (line, sizeof(char), 2048, fp); + line[charCount] = '\0'; + if (submux->encoding && strcmp (submux->encoding, "UTF-8")) + converted = convert_to_utf8 (line, charCount, submux->encoding, consumed, &err, submux); + + if (converted) + { + GST_INFO("returned from conversion and length of converted string is[%d]", strlen(converted)); + is_converted = TRUE; + } + if (!charCount) { + GST_WARNING_OBJECT (submux, "fread returned zero bytes"); + fclose (fp); + g_free(file_path_type); + g_free(temp_path); + if(is_converted) { + g_free(converted); + } + g_free(line); + return NULL; + } + g_free(file_path_type); + g_free(temp_path); + fclose (fp); + if(is_converted) { + return converted; + } + return line; +} + +static gpointer +gst_submux_data_format_autodetect_regex_once (GstSubMuxRegex regtype) +{ + gpointer result = NULL; + GError *gerr = NULL; + switch (regtype) { + case GST_SUB_PARSE_REGEX_MDVDSUB: + result = + (gpointer) g_regex_new ("^\\{[0-9]+\\}\\{[0-9]+\\}", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); + if (result == NULL) { + g_warning ("Compilation of mdvd regex failed: %s", gerr->message); + g_error_free (gerr); + } + break; + case GST_SUB_PARSE_REGEX_SUBRIP: + result = (gpointer) g_regex_new ("^ {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a" + " ?[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,3}" + " +--> +[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,2}", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); + if (result == NULL) { + g_warning ("Compilation of subrip regex failed: %s", gerr->message); + g_error_free (gerr); + } + break; + case GST_SUB_PARSE_REGEX_DKS: + result = (gpointer) g_regex_new ("^\\[[0-9]+:[0-9]+:[0-9]+\\].*", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); + if (result == NULL) { + g_warning ("Compilation of dks regex failed: %s", gerr->message); + g_error_free (gerr); + } + break; + default: + GST_WARNING ("Trying to allocate regex of unknown type %u", regtype); + } + return result; +} + +static GstSubMuxFormat +gst_submux_data_format_autodetect (gchar * match_str) +{ + guint n1, n2, n3; + + static GOnce mdvd_rx_once = G_ONCE_INIT; + static GOnce subrip_rx_once = G_ONCE_INIT; + static GOnce dks_rx_once = G_ONCE_INIT; + + GRegex *mdvd_grx; + GRegex *subrip_grx; + GRegex *dks_grx; + + g_once (&mdvd_rx_once, + (GThreadFunc) gst_submux_data_format_autodetect_regex_once, + (gpointer) GST_SUB_PARSE_REGEX_MDVDSUB); + g_once (&subrip_rx_once, + (GThreadFunc) gst_submux_data_format_autodetect_regex_once, + (gpointer) GST_SUB_PARSE_REGEX_SUBRIP); + g_once (&dks_rx_once, + (GThreadFunc) gst_submux_data_format_autodetect_regex_once, + (gpointer) GST_SUB_PARSE_REGEX_DKS); + + mdvd_grx = (GRegex *) mdvd_rx_once.retval; + subrip_grx = (GRegex *) subrip_rx_once.retval; + dks_grx = (GRegex *) dks_rx_once.retval; + + if (g_regex_match (mdvd_grx, match_str, 0, NULL) == TRUE) { + GST_LOG ("MicroDVD (frame based) format detected"); + return GST_SUB_PARSE_FORMAT_MDVDSUB; + } + if (g_regex_match (subrip_grx, match_str, 0, NULL) == TRUE) { + GST_LOG ("SubRip (time based) format detected"); + return GST_SUB_PARSE_FORMAT_SUBRIP; + } + if (g_regex_match (dks_grx, match_str, 0, NULL) == TRUE) { + GST_LOG ("DKS (time based) format detected"); + return GST_SUB_PARSE_FORMAT_DKS; + } + + if (!strncmp (match_str, "FORMAT=TIME", 11)) { + GST_LOG ("MPSub (time based) format detected"); + return GST_SUB_PARSE_FORMAT_MPSUB; + } + if (strstr (match_str, "") != NULL || + strstr (match_str, "") != NULL) { + GST_LOG ("SAMI (time based) format detected"); + return GST_SUB_PARSE_FORMAT_SAMI; + } + /* we're boldly assuming the first subtitle appears within the first hour */ + if (sscanf (match_str, "0:%02u:%02u:", &n1, &n2) == 2 || + sscanf (match_str, "0:%02u:%02u=", &n1, &n2) == 2 || + sscanf (match_str, "00:%02u:%02u:", &n1, &n2) == 2 || + sscanf (match_str, "00:%02u:%02u=", &n1, &n2) == 2 || + sscanf (match_str, "00:%02u:%02u,%u=", &n1, &n2, &n3) == 3) { + GST_LOG ("TMPlayer (time based) format detected"); + return GST_SUB_PARSE_FORMAT_TMPLAYER; + } + if (sscanf (match_str, "[%u][%u]", &n1, &n2) == 2) { + GST_LOG ("MPL2 (time based) format detected"); + return GST_SUB_PARSE_FORMAT_MPL2; + } + if (strstr (match_str, "[INFORMATION]") != NULL) { + GST_LOG ("SubViewer (time based) format detected"); + return GST_SUB_PARSE_FORMAT_SUBVIEWER; + } + if (strstr (match_str, "{QTtext}") != NULL) { + GST_LOG ("QTtext (time based) format detected"); + return GST_SUB_PARSE_FORMAT_QTTEXT; + } + + GST_WARNING ("no subtitle format detected"); + return GST_SUB_PARSE_FORMAT_UNKNOWN; +} + +/*checking the type of subtitle*/ +static gboolean +gst_submux_format_autodetect (Gstsubmux *self) +{ + gchar *data; + GstSubMuxFormat format; + gchar * line = NULL; + if (self->priv->is_internal) { + GST_DEBUG_OBJECT (self, "File is of internal type"); + return TRUE; + } + line = gst_submux_extract_data (self); + if (!line) + return FALSE; + if (strlen (line) < 30) { + GST_WARNING_OBJECT (self, "File too small to be a subtitles file"); + g_free(line); + return FALSE; + } + + data = g_strndup (line, 35); + format = gst_submux_data_format_autodetect (data); + g_free (data); + + self->priv->parser_type = format; + g_free(line); + + return TRUE; +} + +/*to validate the number of languages in case of sami files*/ +static gboolean +gst_calculate_number_languages(Gstsubmux *self) { + gchar* text=NULL; + gchar *start = NULL; + gchar *end = NULL; + gint count = 0; + gchar* found = NULL; + gchar * name_temp = NULL; + int i = 0, j = 0; + + GST_DEBUG_OBJECT (self, "Entering in language number"); + + if ((self->priv->parser_type != GST_SUB_PARSE_FORMAT_SAMI) || self->priv->is_internal) + return TRUE; + + text = gst_submux_extract_data (self); + start = g_strstr_len (text, strlen (text), "!--"); + if (!start) { + GST_ERROR_OBJECT (self, "Could not found the language start code in smi file"); + return gst_create_own_language_list(self); + } + end = g_strstr_len (start, strlen (start), "-->"); + if (!end){ + GST_ERROR_OBJECT (self, "Could not found the language end code in smi file"); + goto error; + } + + found = start + 1; + + while (TRUE) { + found = (gchar*)strcasestr (found, "lang"); + if (!found) + break; + found++; + count++; + } + + if (!count) + { + return gst_create_own_language_list(self); + } + + for (i = 0; i < count; i++) { + gchar *attr_name = NULL, *attr_value = NULL; + GstLangStruct *new = NULL; + + start = (gchar*)strcasestr (start, "lang:"); + attr_value = (gchar*)malloc (3); + if (!attr_value) { + GST_ERROR_OBJECT (self, "memory could not be allocated through malloc call"); + goto error; + } + start = start + 5; + strncpy (attr_value, start, 2); + attr_value[2] = '\0'; + GST_DEBUG_OBJECT (self, "Language value comes as %s", attr_value); + name_temp = start; + while (TRUE) { + if (*name_temp == '{') { + int character_count = 0; + while (TRUE) { + name_temp--; + + if (*name_temp == '.') { + attr_name = (gchar*) malloc (character_count + 1); + break; + } else if (*name_temp != ' ') + character_count++; + } + break; + } + name_temp--; + } + if (!attr_name) { + GST_ERROR_OBJECT (self, "Could not find the languages field in the file"); + free(attr_value); + goto error; + } + name_temp++; + for (j = 0; *(name_temp + j) != ' '; j++) { + attr_name[j] = *(name_temp + j); + } + attr_name[j] = '\0'; + new = g_new0 (GstLangStruct, 1); + if (attr_value) { + new->language_code = g_strdup(attr_value); + } + if (attr_name) { + new->language_key = g_strdup(attr_name); + } + free (attr_name); + free (attr_value); + self->priv->lang_list = g_list_append (self->priv->lang_list, new); + } + g_free(text); + return TRUE; +error: + g_free(text); + return FALSE; +} + +/*to initialize stream*/ +static gboolean gst_submux_stream_init(GstSubmuxStream * stream) +{ + stream->duration = 0; + stream->need_segment = TRUE; + stream->flushing = FALSE; + stream->eos_sent = FALSE; + stream->eos_came = FALSE; + stream->discont_came = FALSE; + stream->eos_ts = -1; + stream->last_ts = -1; + stream->queue = g_queue_new (); + stream->queue_empty = g_cond_new (); + stream->queue_lock = g_mutex_new (); + stream->flush_done = FALSE; + return TRUE; +} + +/*to create pipelines according to internal and external subtitle*/ +gboolean gst_submux_create_pipelines(Gstsubmux *self, GstPad * pad) +{ + int i = 0; + GstStateChangeReturn ret; + GstSubmuxStream *new_stream; + guint length = 0; + + if (!self->priv->is_internal) { + GstLangStruct *cur_language=NULL; + + GST_DEBUG_OBJECT (self, "creating the pipeline for external pipeline"); + if (self->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) { + if (!self->priv->lang_list) { + GST_ERROR_OBJECT(self, "failed to get the lang list"); + return FALSE; + } + length = g_list_length (self->priv->lang_list); + } else { + length = 1; + } + + GST_DEBUG_OBJECT (self, "number of tentative languages present are %d", length); + + for (i = 0; i < length; i++) { + new_stream = g_new0 (GstSubmuxStream, 1); + if (!gst_submux_stream_init(new_stream)) { + GST_ERROR_OBJECT (self, "stream init is failed"); + return FALSE; + } + GST_DEBUG_OBJECT (self, "stream init has been done for stream[%d]", i); + + new_stream->pipe_struc.pipe = gst_pipeline_new ("subtitle-pipeline"); + if (!new_stream->pipe_struc.pipe) { + GST_ERROR_OBJECT (self, "failed to create pipeline"); + return FALSE; + } + GST_DEBUG_OBJECT (self, "creating appsrc"); + + /* creating source element */ + new_stream->pipe_struc.appsrc = gst_element_factory_make ("appsrc", "pipe_appsrc"); + if (!new_stream->pipe_struc.appsrc) { + GST_ERROR_OBJECT (self, "failed to create appsrc"); + return FALSE; + } + + g_object_set (G_OBJECT (new_stream->pipe_struc.appsrc), "block", 1, NULL); + g_object_set (G_OBJECT (new_stream->pipe_struc.appsrc), "max-bytes", (guint64)1, NULL); + /* create sink element */ + new_stream->pipe_struc.appsink = gst_element_factory_make ("appsink", "pipe_appsink"); + if (!new_stream->pipe_struc.appsink) { + GST_ERROR_OBJECT (self, "failed to create appsink"); + return FALSE; + } + g_object_set (G_OBJECT (new_stream->pipe_struc.appsink), "sync", FALSE, "emit-signals", TRUE, NULL); + g_object_set(G_OBJECT (new_stream->pipe_struc.appsrc),"emit-signals", TRUE, NULL); + + + /* create parsing element */ + new_stream->pipe_struc.parser = gst_element_factory_make("subparse","pipe_parser"); + if (!new_stream->pipe_struc.parser) { + GST_ERROR_OBJECT (self, "failed to create parser"); + return FALSE; + } + if (self->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) { + cur_language = g_list_nth_data(self->priv->lang_list, i); + g_object_set (G_OBJECT (new_stream->pipe_struc.parser), "current-language", + cur_language->language_key, NULL); + } + GST_DEBUG_OBJECT (self, "value of subtitle-encoding is %s", self->encoding); + g_object_set (G_OBJECT (new_stream->pipe_struc.parser), "subtitle-encoding", self->encoding, NULL); + g_object_set (G_OBJECT (new_stream->pipe_struc.appsrc), "stream-type",0,"format",GST_FORMAT_TIME, NULL); + gst_bin_add_many (GST_BIN ( new_stream->pipe_struc.pipe), new_stream->pipe_struc.appsink, new_stream->pipe_struc.appsrc,new_stream->pipe_struc.parser, NULL); + if (!gst_element_link_many (new_stream->pipe_struc.appsrc, new_stream->pipe_struc.parser,new_stream->pipe_struc.appsink, NULL)) { + GST_ERROR_OBJECT (self, "failed to link elements"); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "reached here and linking successful"); + + ret = gst_element_set_state (new_stream->pipe_struc.pipe, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (self, "set_state failed..."); + return FALSE; + } + GST_DEBUG_OBJECT (self, "state has been changed succesfully"); + self->streams = g_list_append(self->streams, new_stream); + self->priv->stream_count++; + g_signal_connect (new_stream->pipe_struc.appsink, "new-buffer", G_CALLBACK (gst_submux_on_new_buffer), g_list_nth_data(self->streams,i) ); + } + } else { + length = self->sinkpads_count; + for (i = 0; i < length; i++) { + new_stream = g_new0 (GstSubmuxStream, 1); + if (!gst_submux_stream_init (new_stream)) { + GST_ERROR_OBJECT (self, "stream init is failed"); + return FALSE; + } + + self->streams=g_list_append(self->streams,new_stream); + self->priv->stream_count++; + } + self->pipeline_made = TRUE; + } + + self->cur_buf_array = g_malloc0 (self->priv->stream_count * (sizeof (GstBuffer *))); + if (!self->cur_buf_array) { + GST_ERROR_OBJECT (self, "failed to allocate memory.."); + return FALSE; + } + return TRUE; +} + +/* call back on recieving the new buffer in appsink pad */ +static void +gst_submux_on_new_buffer (GstElement *appsink, void *data) +{ + GstSubmuxStream *stream = (GstSubmuxStream *)data; + GstBuffer *inbuf = NULL; + + if (!stream) { + GST_WARNING("Stream not available..."); + return; + } + g_mutex_lock (stream->queue_lock); + inbuf = gst_app_sink_pull_buffer ((GstAppSink *)appsink); + if (!inbuf) { + GST_WARNING_OBJECT (stream, "Input buffer not available..."); + g_mutex_unlock (stream->queue_lock); + return; + } + if(stream->eos_ts == -1) { + if (!strcmp ((const char*)GST_BUFFER_DATA (inbuf), "eos") && GST_BUFFER_FLAG_IS_SET(inbuf,GST_BUFFER_FLAG_GAP)){ + stream->eos_ts = stream->last_ts; + if (stream->eos_ts <= stream->seek_ts) { + g_queue_push_tail (stream->queue, inbuf); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + GST_INFO_OBJECT (stream, "signaling queue empty signal as we are seeking beyond last subtitle"); + return; + } + gst_buffer_unref(inbuf); + } else { + stream->last_ts = GST_BUFFER_DURATION(inbuf) + GST_BUFFER_TIMESTAMP(inbuf); + } + } else if (stream->eos_ts <= stream->seek_ts) { + gst_buffer_unref(inbuf); + GstBuffer *buf = gst_buffer_new_and_alloc (3 + 1); + GST_DEBUG_OBJECT(stream, "sending EOS buffer to chain\n"); + GST_DEBUG_OBJECT (stream, "EOS. Pushing remaining text (if any)"); + GST_BUFFER_DATA (buf)[0] = 'e'; + GST_BUFFER_DATA (buf)[1] = 'o'; + GST_BUFFER_DATA (buf)[2] = 's'; + GST_BUFFER_DATA (buf)[3] = '\0'; + /* play it safe */ + GST_BUFFER_SIZE (buf) = 3; + GST_BUFFER_FLAG_SET(buf,GST_BUFFER_FLAG_GAP); + g_queue_push_tail (stream->queue, buf); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + GST_INFO_OBJECT (stream,"signaling queue empty signal as we are seeking beyond last subtitle"); + return; + } + if (!stream->discont_came) { + stream->discont_came = GST_BUFFER_IS_DISCONT (inbuf); + if (stream->discont_came) { + GST_DEBUG_OBJECT (stream, "first buffer with discont on new_buffer for stream with ts = %" + GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf)), + GST_TIME_ARGS(GST_BUFFER_DURATION(inbuf))); + } + } + + if (!stream->discont_came) { + GST_DEBUG_OBJECT (stream, "rejecting the buffer in appsink on new_buffer for stream with ts = %" + GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(inbuf)), + GST_TIME_ARGS(GST_BUFFER_DURATION(inbuf))); + gst_buffer_unref(inbuf); + g_mutex_unlock (stream->queue_lock); + return; + } + g_queue_push_tail (stream->queue, inbuf); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + GST_DEBUG_OBJECT (stream, "signaling queue empty signal"); + return; +} + +gchar * +convert_to_utf8 (const gchar * str, gsize len, const gchar * encoding, + gsize * consumed, GError ** err, Gstsubmux * self) +{ + gchar *ret = NULL; + + /* The char cast is necessary in glib < 2.24 */ + ret = + g_convert_with_fallback (str, len, "UTF-8", encoding, (char *) "*", + consumed, NULL, err); + + if (ret == NULL) + { + GST_DEBUG_OBJECT (self, "g_convert_with_fallback returns NULL"); + return ret; + } + + /* + 3 to skip UTF-8 BOM if it was added */ + len = strlen (ret); + if (len >= 3 && (guint8) ret[0] == 0xEF && (guint8) ret[1] == 0xBB + && (guint8) ret[2] == 0xBF) + g_memmove (ret, ret + 3, len + 1 - 3); + + return ret; +} + +static gchar * +detect_encoding (const gchar * str, gsize len) +{ + if (len >= 3 && (guint8) str[0] == 0xEF && (guint8) str[1] == 0xBB + && (guint8) str[2] == 0xBF) + return g_strdup ("UTF-8"); + + if (len >= 2 && (guint8) str[0] == 0xFE && (guint8) str[1] == 0xFF) + return g_strdup ("UTF-16BE"); + + if (len >= 2 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE) + return g_strdup ("UTF-16LE"); + + if (len >= 4 && (guint8) str[0] == 0x00 && (guint8) str[1] == 0x00 + && (guint8) str[2] == 0xFE && (guint8) str[3] == 0xFF) + return g_strdup ("UTF-32BE"); + + if (len >= 4 && (guint8) str[0] == 0xFF && (guint8) str[1] == 0xFE + && (guint8) str[2] == 0x00 && (guint8) str[3] == 0x00) + return g_strdup ("UTF-32LE"); + + return NULL; +} + +/* If language list is not present in smi file, check the body and create our own list */ +static gboolean +gst_create_own_language_list (Gstsubmux *self) +{ + gchar * file_path_type = NULL; + gchar * temp_path = NULL; + gchar * file_path = NULL; + gsize * consumed = NULL; + guint keyCount = 0; + GError *err = NULL; + GstPad *sinkpad = (GstPad *) g_list_nth_data(self->sinkpad, 0); + GstQuery *cquery; + GstStructure *structure; + const GValue *value; + gchar* langkey[MAX_LANG]; + gint langKey_length[MAX_LANG]; + FILE *fp=NULL; + gint i=0; + structure = gst_structure_new ("FileSrcURI", "file-uri", G_TYPE_STRING, NULL, NULL); + + cquery = gst_query_new_application (GST_QUERY_CUSTOM, structure); + + if (!gst_pad_peer_query (sinkpad, cquery)) { + GST_ERROR_OBJECT (self, "Failed to query SMI file path"); + gst_query_unref (cquery); + return FALSE; + } + structure = gst_query_get_structure (cquery); + value = gst_structure_get_value (structure, "file-uri"); + file_path = g_strdup (g_value_get_string (value)); + + if (file_path == NULL){ + GST_ERROR_OBJECT (self, "Could not parse the SMI file path"); + gst_query_unref (cquery); + return FALSE; + } + gst_query_unref (cquery); + temp_path = file_path; + GST_INFO_OBJECT (self, "File path comes as %s", file_path); + + file_path_type = g_strndup ((gchar *) file_path, 4); + GST_INFO_OBJECT (self, "Received file path by query = %s, %s", file_path, file_path_type); + if (!g_strcmp0(file_path_type, "file")){ + file_path += 7; + GST_INFO_OBJECT (self, "File path comes as %s", file_path); + + fp = fopen (file_path, "r"); + if (!fp){ + GST_ERROR_OBJECT (self, "Failed to open file"); + g_free(temp_path); + g_free(file_path_type); + return FALSE; + } + } + for( i=0;idetected_encoding,self->encoding); + if (self->detected_encoding && strcmp (self->detected_encoding, "UTF-8") && conversion){ + result = convert_to_utf8 (line, charCount, self->detected_encoding, consumed, &err, self); + GST_DEBUG_OBJECT (self, " Converted convert_to_utf8 result %d ",result); + } + if (result == NULL) { + result = line; + conversion = FALSE; + } + con_temp = result; + temp = con_temp; + + while (con_temp){ + gchar* tempKey = NULL; + guint i = 0; + + con_temp = strcasestr(con_temp,"class="); + if(con_temp) + delimiter = strcasestr(con_temp, ">"); + GST_DEBUG_OBJECT (self, " Delimiter ...Entering if %s",con_temp); + if (con_temp && (delimiter!=NULL)){ + gchar* tempChar = con_temp + 6; + GST_DEBUG_OBJECT (self, "Has class= reading string %s",tempChar); + GST_DEBUG_OBJECT (self, "Has class= "); + while (*tempChar != '>'){ + keyLength++; + tempChar++; + GST_DEBUG_OBJECT (self, " keyLength %d tempChar %c",keyLength,*tempChar); + } + GST_DEBUG_OBJECT (self, " keyLength %d",keyLength); + tempChar -= keyLength; + tempKey = (gchar*) g_malloc (keyLength + 1); + if(!tempKey){ + GST_DEBUG_OBJECT (self, "Error 1"); + goto error; + } + gchar* temp1 =tempKey; + GST_DEBUG_OBJECT (self, "tempChar %s keyLength %d",tempChar,keyLength); + while (*tempChar != '>'){ + *tempKey = *tempChar; + tempKey++; + tempChar++; + } + tempKey =temp1; + tempKey[keyLength]='\0'; + GST_DEBUG_OBJECT (self, "tempKey %s keyLength %d keyCount %d",tempKey,keyLength,keyCount); + int k =0; + for (k = 0; k < keyCount; k++){ + if(langkey[k]){ + if (!strcasecmp (tempKey,langkey[k])) + { + GST_DEBUG_OBJECT (self, "Has the key already so breaking..Entry %d tempKey %s langkey[i] %s ",k,tempKey,langkey[k]); + lang_found = TRUE; + break; + } + } + } + if(lang_found == FALSE){ + langkey[keyCount] = g_strdup (tempKey); + langKey_length[keyCount]=keyLength; + keyCount++; + } + lang_found =FALSE; + keyLength =0; + if(tempKey){ + g_free(tempKey); + tempKey=NULL; + } + } else { + keyLength =0; + lang_found =FALSE; + break; + } + con_temp+=6; + GST_DEBUG_OBJECT (self, " ..increment con_temp %s",con_temp); + } + } + GST_DEBUG_OBJECT (self, " At end keyCount no of langs is %d ",keyCount); + for(i=0;ilanguage_code = (gchar*)malloc (3); + if(!(new->language_code)){ + GST_DEBUG_OBJECT (self, " .Error 2"); + goto error; + } + gchar *attr_val=new->language_code ; + strncpy (attr_val, "un", 2); + attr_val[2]='\0'; + + new->language_key = g_strdup(langKey_length[i]); + self->priv->lang_list = g_list_append (self->priv->lang_list, new); + GST_DEBUG_OBJECT (self, " new...Successfull"); + g_free(langkey[i]); + } + } + if (fp) { + g_free(temp_path); + g_free(file_path_type); + fclose(fp); + fp = NULL; + } + return TRUE; +error: + GST_DEBUG_OBJECT (self, " In Error"); + if (fp) { + g_free(temp_path); + g_free(file_path_type); + fclose(fp); + fp = NULL; + } + return FALSE; +} + +gboolean +validate_langlist_body(GList * lang_list, Gstsubmux * self){ + gchar * file_path_type = NULL; + gchar * file_path = NULL; + gchar line[1025]; + FILE * fp = NULL; + guint i = 0, found_count = 0,k = 0; + const guint list_len = g_list_length(lang_list); + gboolean counter[MAX_LANGUAGE]; + GstPad *sinkpad = NULL; + struct LangStruct + { + gchar *language_code; + gchar *language_key; + } * lang; + sinkpad = (GstPad *) g_list_nth_data(self->sinkpad, 0); + GstQuery *cquery; + GstStructure *structure; + const GValue *value; + structure = gst_structure_new ("FileSrcURI", "file-uri", G_TYPE_STRING, NULL, NULL); + + cquery = gst_query_new_application (GST_QUERY_CUSTOM, structure); + + if (!gst_pad_peer_query (sinkpad, cquery)) { + GST_ERROR_OBJECT (self, "Failed to query SMI file path"); + gst_query_unref (cquery); + return FALSE; + } + structure = gst_query_get_structure (cquery); + value = gst_structure_get_value (structure, "file-uri"); + file_path = g_strdup (g_value_get_string (value)); + + if (file_path == NULL){ + GST_ERROR_OBJECT (self, "Could not parse the SMI file path"); + gst_query_unref (cquery); + return FALSE; + } + + if (self->external_filepath == NULL) { + self->external_filepath = file_path; + } + else { + if (!g_strcmp0 (file_path, self->external_filepath)) { + GST_INFO_OBJECT (self, "Same external file URI, no need to parse again"); + gst_query_unref (cquery); + g_free(file_path); + return TRUE; + } + else { + g_free (self->external_filepath); + self->external_filepath = NULL; + self->external_filepath = file_path; + } + } + + gst_query_unref (cquery); + GST_INFO_OBJECT (self, "File path comes as %s", file_path); + + file_path_type = g_strndup ((gchar *) file_path, 4); + GST_INFO_OBJECT (self, "Received file path by query = %s, %s", file_path, file_path_type); + if (!g_strcmp0(file_path_type, "file")){ + file_path += 7; + GST_INFO_OBJECT (self, "File path comes as %s", file_path); + + fp = fopen (file_path, "r"); + if (!fp){ + GST_ERROR_OBJECT (self, "Failed to open file"); + g_free(file_path_type); + return FALSE; + } + + for (i = 0; i < list_len; i++){ + counter[i] = FALSE; + } + + while (!feof (fp) && found_count < list_len){ + GError *err = NULL; + gsize * consumed = NULL; + gint gap = 0; + guint charCount = 0; + gchar* result = NULL; + gchar* temp = NULL; + gchar* temp_lang = NULL; + gchar* con_temp_end = NULL; + gchar* con_temp_start = NULL; + gchar* new_key = NULL; + gint new_key_length = 0; + gboolean new_key_found = FALSE; + gchar * temp1 = NULL; + gchar *con_temp_lang = NULL; + gchar *con_temp = NULL; + gboolean conversion = TRUE; + charCount = fread (line, sizeof(char), 1024, fp); + line[charCount] = '\0'; + if (!charCount) { + GST_WARNING_OBJECT (self, "fread returned zero bytes"); + continue; + } + + GST_DEBUG_OBJECT (self, "value of detected encoding is %s and self encoding is %s", + self->detected_encoding,self->encoding); + if (self->detected_encoding && strcmp (self->detected_encoding, "UTF-8") && conversion){ + result = convert_to_utf8 (line, charCount, self->detected_encoding, consumed, &err, self); + } + if (result == NULL) { + result = line; + conversion = FALSE; + } + con_temp = g_utf8_strdown (result, strlen (result)); + temp = con_temp; + while (con_temp) { + con_temp = g_strstr_len(con_temp, strlen (con_temp), "class="); + if (con_temp) { + temp1 = g_strstr_len(con_temp+1, strlen (con_temp), "class="); + } + if (temp1 && con_temp){ + gap = strlen (con_temp) - strlen (temp1); + } else if (con_temp) { + gap = strlen (con_temp); + } else { + continue; + } + if (con_temp){ + for (i = 0; i < list_len; i++){ + if (counter[i] == TRUE) { + con_temp = con_temp + 1; + continue; + } + lang = (struct LangStruct *) g_list_nth_data (lang_list, i); + if (lang) { + temp_lang = g_strdup(lang->language_key); + con_temp_lang = g_utf8_strdown (temp_lang, strlen (temp_lang)); + if (g_strstr_len (con_temp, gap, con_temp_lang)) { + found_count++; + counter[i] = TRUE; + GST_INFO_OBJECT (self, "Valid Language in list : [%s]", lang->language_key); + con_temp = con_temp + 1; + } +/* Fix Me: Cases where there is no body for a specific language + * inside a single language .smi file */ +#if 0 + else { + con_temp_start = con_temp; + con_temp_end = con_temp; + while(con_temp_end) { + if(*con_temp_end == '=') { + con_temp_start = con_temp_end+1; + con_temp_end++; + }else if(*con_temp_end == '>') { + con_temp_end = con_temp_end; + new_key_found = TRUE; + break; + }else { + con_temp_end++; + new_key_found = FALSE; + } + } + if(new_key_found) { + new_key_length = strlen(con_temp_start)-strlen(con_temp_end); + new_key = g_malloc(new_key_length +1); + for(k=0;klanguage_key); + g_free(new_key); + found_count++; + counter[i] = TRUE; + con_temp = con_temp + 1; + } + } +#endif + g_free (temp_lang); + g_free (con_temp_lang); + } + } + } + } + if (conversion) + g_free (result); + if (temp) + g_free (temp); + } + + if (found_count < list_len) { + for (i = 0; i < list_len; i++) { + if (counter[i] == FALSE) + lang_list = g_list_delete_link (lang_list, g_list_nth (lang_list, i)); + } + } + } else { + GST_ERROR_OBJECT (self, "File is not a local file"); + g_free(file_path_type); + return FALSE; + } + fclose (fp); + g_free(file_path_type); + return TRUE; +} + +/* +** +** Description : Chain function used to push the subtitle buffer to internal pipelines of submux element +** Params : (1) sink pad on which buffer is arriving (2) the buffer itself +** return : GST_FLOW_OK on successfully pushing subtitle buffer to next element +** +*/ +static GstFlowReturn +gst_submux_chain(GstPad *pad, GstBuffer *buffer) +{ + guint length = 0; + guint i=0; + GstPad *checkpad = NULL; + Gstsubmux *submux = GST_SUBMUX(GST_PAD_PARENT(pad)); + gboolean ret = FALSE; + GstFlowReturn fret = GST_FLOW_ERROR; + GstSubmuxStream *stream = NULL; + GstMessage *m = NULL; + + if (GST_BUFFER_IS_DISCONT (buffer)) + GST_DEBUG_OBJECT(submux, "Discont buffer came in chain function"); + GST_DEBUG_OBJECT (submux, "^^^^^entering in chain^^^^^^"); + if (!submux->priv->is_internal) { + if (!submux->priv->first_buffer) { + submux->detected_encoding = detect_encoding ((gchar*)GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); + } + if (!submux->langlist_msg_posted && submux->priv->lang_list) { + if (!validate_langlist_body (submux->priv->lang_list, submux)){ + GST_WARNING_OBJECT(submux, "Error occured while validating language list. Posting without validation"); + } + if (submux->priv->lang_list) { + GList* temp_list_to_post = NULL; + temp_list_to_post = g_list_copy (submux->priv->lang_list); + m = gst_message_new_element (GST_OBJECT_CAST (submux), gst_structure_new("Ext_Sub_Language_List", + "lang_list", G_TYPE_POINTER, temp_list_to_post, NULL)); + + gst_element_post_message (GST_ELEMENT_CAST (submux), m); + submux->langlist_msg_posted = TRUE; + } + GST_DEBUG_OBJECT (submux, "LANGLIST POSTED"); + } + if (submux->need_segment) { + ret = gst_pad_push_event (submux->srcpad, gst_event_new_new_segment (FALSE, submux->segment.rate, + submux->segment.format, submux->segment.start, submux->segment.stop, + submux->segment.time)); + GST_DEBUG_OBJECT (submux, "pushing newsegment event with %" GST_SEGMENT_FORMAT, &submux->segment); + if (!ret) { + GST_ERROR_OBJECT (submux, "Sending newsegment to next element is failed"); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (submux, "Starting the loop again"); + if (!gst_pad_start_task (submux->srcpad, (GstTaskFunction) gst_submux_loop, submux)) { + GST_ERROR_OBJECT (submux, "failed to start srcpad task..."); + GST_ELEMENT_ERROR (submux, RESOURCE, FAILED, ("failed to create push loop"), (NULL)); + return GST_FLOW_ERROR; + } + submux->need_segment = FALSE; + } + GST_DEBUG_OBJECT (submux, "before pushing buffer to each apprsrc"); + if (!submux->priv->lang_list && submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) { + GST_ERROR_OBJECT (submux, "lang list is not there"); + return GST_FLOW_ERROR; + } + if (submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) + length = g_list_length (submux->priv->lang_list); + else + length = 1; + + for (i = 0; i < length; i++) { + stream = g_list_nth_data(submux->streams, i); + if ((submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) && !submux->priv->first_buffer){ + GstLangStruct *lang = g_list_nth_data(submux->priv->lang_list, i); + if(submux->msl_streams){ + GstLangStruct *lang1 = g_list_nth_data(submux->msl_streams, i); + lang->active = lang1->active; + } else { + if (i == 0) + lang->active = TRUE; + else + lang->active = FALSE; + } + } + GST_DEBUG_OBJECT (submux, "making stream need segment false"); + stream->need_segment = FALSE; + } + + for (i = 0; i < length; i++) { + stream = g_list_nth_data(submux->streams, i); + if (!stream){ + GST_ERROR_OBJECT (submux, "stream not found"); + return GST_FLOW_ERROR; + } + + if (!stream->pipe_struc.appsrc) { + GST_ERROR_OBJECT (submux, "appsrc not found"); + return GST_FLOW_ERROR; + } + if (i < (length - 1)) + gst_buffer_ref (buffer); + + fret = gst_app_src_push_buffer ((GstAppSrc*)stream->pipe_struc.appsrc, buffer); + + if (fret != GST_FLOW_OK) { + GST_ERROR_OBJECT (submux, "push buffer failed with fret is %d", fret); + return fret; + } + GST_DEBUG_OBJECT (submux, "pad_push successfull to appsrc %p buffer", buffer); + } + } else { + length = submux->sinkpads_count; + checkpad = (GstPad *) g_list_nth_data (submux->sinkpad, 0); + if (checkpad == pad) { + if (submux->need_segment) { + ret = gst_pad_push_event (submux->srcpad, gst_event_new_new_segment (FALSE, submux->segment.rate, + submux->segment.format, submux->segment.start, submux->segment.stop, + submux->segment.time)); + GST_DEBUG_OBJECT (submux, "pushing newsegment event with %" GST_SEGMENT_FORMAT, &submux->segment); + if (!ret) { + GST_ERROR_OBJECT (submux, "Sending newsegment to next element is failed"); + return GST_FLOW_ERROR; + } + GST_DEBUG_OBJECT (submux, "Starting the loop again"); + if (!gst_pad_start_task (submux->srcpad, (GstTaskFunction) gst_submux_loop, submux)) { + GST_ERROR_OBJECT (submux, "failed to start srcpad task..."); + GST_ELEMENT_ERROR (submux, RESOURCE, FAILED, ("failed to create push loop"), (NULL)); + return GST_FLOW_ERROR; + } + submux->need_segment = FALSE; + } + } + for (i = 0; i < length; i++) { + checkpad = (GstPad *) g_list_nth_data(submux->sinkpad, i); + if (checkpad == pad) { + stream = g_list_nth_data (submux->streams, i); + if (!stream) { + GST_ERROR_OBJECT (submux, "Stream not available..."); + return GST_FLOW_ERROR; + } + if (stream->flushing){ + GST_DEBUG_OBJECT (submux, "flushing going on in appsink"); + return GST_FLOW_OK ; + } + + g_mutex_lock (stream->queue_lock); + g_queue_push_tail (stream->queue, buffer); + g_cond_signal (stream->queue_empty); + g_mutex_unlock (stream->queue_lock); + fret = GST_FLOW_OK; + GST_DEBUG_OBJECT (submux, "push buffer success to appsrc with fret is %d for stream[%d]", fret, i); + break; + } + } + } + + if (!submux->priv->first_buffer) { + GST_DEBUG_OBJECT (submux, "got the first buffer"); + submux->priv->first_buffer = TRUE; + if (submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) { + + } + } + GST_DEBUG_OBJECT (submux, "^^^^^exiting in chain^^^^^^"); + return fret; +} + +/* stream_denit */ +static void +gst_submux_stream_deinit (GstSubmuxStream *stream,Gstsubmux *submux) +{ + GstBuffer *buf = NULL; + + if (stream) { + if (stream->queue) { + while (!g_queue_is_empty (stream->queue)) { + buf = g_queue_pop_head (stream->queue); + gst_buffer_unref (buf); + buf = NULL; + } + g_queue_free (stream->queue); + stream->queue = NULL; + } + + if (stream->pipe_struc.pipe) { + gst_element_set_state (stream->pipe_struc.pipe, GST_STATE_NULL); + gst_element_get_state (stream->pipe_struc.pipe, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref(GST_OBJECT(stream->pipe_struc.appsrc)); + gst_object_unref(GST_OBJECT(stream->pipe_struc.appsink)); + gst_object_unref (stream->pipe_struc.pipe); + } + + if (stream->queue_lock) { + g_cond_broadcast(stream->queue_empty); + g_mutex_free (stream->queue_lock); + stream->queue_lock = NULL; + } + + if (stream->queue_empty) { + g_cond_free (stream->queue_empty); + stream->queue_empty= NULL; + } + + g_free (stream); + GST_DEBUG_OBJECT (submux, "stream deinit completed"); + } +} + +/* releasing the requested pad */ +static void +gst_submux_release_pad (GstElement * element, GstPad * pad) +{ + Gstsubmux *submux = GST_SUBMUX_CAST (element); + GstPad *check_pad; + int i=0; + guint length; + GST_INFO_OBJECT(element,"entering in the release pad"); + length = g_list_length(submux->sinkpad); + GST_DEBUG_OBJECT (element, "Releasing %s:%s", GST_DEBUG_PAD_NAME (pad)); + + for (i=1;i<=length;i++) { + check_pad = (struct GstPad *) g_list_nth_data(submux->sinkpad,i); + if (check_pad == pad) { + /* this is it, remove */ + submux->sinkpad = g_list_remove_link (submux->sinkpad, pad); + gst_element_remove_pad (element, pad); + break; + } + } +} + +/* request new pad */ +static GstPad * +gst_submux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * req_name) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + Gstsubmux *submux = GST_SUBMUX_CAST (element); + GstPad *newpad = NULL; + gchar *name = NULL; + + if (templ->direction != GST_PAD_SINK) { + GST_ERROR_OBJECT (submux, "templ direction is not sinkpad, returning from here"); + goto wrong_direction; + } + + if (templ == gst_element_class_get_pad_template (klass, "sink%d")) { + name = g_strdup_printf ("sink%d", submux->sinkpads_count++); + } + + GST_DEBUG_OBJECT (submux, "Requested pad: %s", name); + newpad = (GstPad*)g_new0 (GstPad*, 1); + /* create pad and add to collections */ + newpad = gst_pad_new_from_template (templ, name); + g_free (name); + if(!submux->priv->is_internal) { + submux->external_sinkpad = TRUE; + } + submux->sinkpad = g_list_append (submux->sinkpad, newpad); + /* set up pad */ + + /* set up pad functions */ + gst_pad_set_setcaps_function (newpad, GST_DEBUG_FUNCPTR (gst_submux_setcaps)); + gst_pad_set_event_function (newpad, GST_DEBUG_FUNCPTR (gst_submux_handle_sink_event)); + gst_pad_set_chain_function(newpad, GST_DEBUG_FUNCPTR (gst_submux_chain)); + gst_pad_set_active (newpad, TRUE); + gst_element_add_pad (element, newpad); + + return newpad; + +/* ERRORS */ +wrong_direction: + GST_WARNING_OBJECT (submux, "Request pad that is not a SINK pad."); + return NULL; +} + +static gboolean +gst_submux_handle_src_event (GstPad * pad, GstEvent * event) +{ + Gstsubmux *submux = GST_SUBMUX(GST_PAD_PARENT(pad)); + gboolean ret = FALSE; + guint length = 0; + gint i = 0; + gboolean update; + GstSubmuxStream *cur_stream = NULL; + + GST_DEBUG_OBJECT (submux, "Handling %s event", GST_EVENT_TYPE_NAME (event)); + length = g_list_length(submux->streams); + + switch (GST_EVENT_TYPE (event)) { + /* this event indicates speed change or seek */ + case GST_EVENT_SEEK: { + GstFormat format; + GstSeekType start_type, stop_type; + gint64 start, stop; + gdouble rate; + GstPad *sinkpad = (GstPad *) g_list_nth_data (submux->sinkpad, 0); + gst_event_parse_seek (event, &rate, &format, &submux->segment_flags, + &start_type, &start, &stop_type, &stop); + gst_segment_set_seek (&submux->segment, rate, format, submux->segment_flags, + start_type, start, stop_type, stop, &update); + if (submux->priv->is_internal || submux->priv->parser_type != GST_SUB_PARSE_FORMAT_SAMI) { + length = g_list_length (submux->sinkpad); + } else { + length = g_list_length(submux->streams); + } + if (!submux->priv->is_internal) { + ret = gst_pad_push_event (sinkpad, gst_event_new_seek (rate, GST_FORMAT_BYTES, submux->segment_flags, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, 0)); + gst_event_unref (event); + } else { + GST_DEBUG_OBJECT (submux, "handling seek in case of internal"); + ret = gst_pad_event_default (pad, event); + } + + if (!ret) { + GST_ERROR_OBJECT (submux, "sending seek event to sink pad failed"); + break; + } + GST_DEBUG_OBJECT (submux, "sending seek event to sink pad passed"); + + break; + } + + default: { + ret = gst_pad_event_default (pad, event); + break; + } + } + + return ret; +} + +static gboolean +gst_submux_handle_sink_event (GstPad * pad, GstEvent * event) +{ + Gstsubmux *submux = GST_SUBMUX (GST_PAD_PARENT (pad)); + gboolean ret = FALSE; + guint length = 0; + GstBuffer *buf = NULL; + GstPad *checkpad = NULL; + gint i = 0; + GstSubmuxStream *cur_stream = NULL; + + GST_DEBUG_OBJECT (submux, "Handling %s event", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS:{ + length = g_list_length (submux->sinkpad); + GST_OBJECT_LOCK (submux); + for (i = 0; i < length; i++) { + GST_DEBUG_OBJECT(submux, "inside the handling of EOS event"); + cur_stream = g_list_nth_data(submux->streams,i); + if (!cur_stream->eos_sent) { + GstBuffer *buf = gst_buffer_new_and_alloc (3 + 1); + GST_DEBUG_OBJECT(submux, "sending EOS buffer to chain\n"); + GST_DEBUG_OBJECT (submux, "EOS. Pushing remaining text (if any)"); + GST_BUFFER_DATA (buf)[0] = 'e'; + GST_BUFFER_DATA (buf)[1] = 'o'; + GST_BUFFER_DATA (buf)[2] = 's'; + GST_BUFFER_DATA (buf)[3] = '\0'; + /* play it safe */ + GST_BUFFER_SIZE (buf) = 3; + GST_BUFFER_FLAG_SET(buf,GST_BUFFER_FLAG_GAP); + gst_submux_chain (g_list_nth_data(submux->sinkpad,i), buf);// + cur_stream->eos_sent = TRUE; + } + } + GST_OBJECT_UNLOCK (submux); + gst_event_unref(event); + ret = TRUE; + break; + } + case GST_EVENT_NEWSEGMENT: { + GstFormat format; + gdouble rate,arate; + gint64 start, stop, time; + gboolean update; + GST_OBJECT_LOCK (submux); + if (!submux->pipeline_made) { + if (!gst_submux_format_autodetect (submux)) { + GST_ERROR_OBJECT (submux, "auto detect function failed"); + return FALSE; + } + if (!gst_calculate_number_languages(submux)) { + GST_ERROR_OBJECT (submux, "failed to calculate number of languages"); + return FALSE; + } + if (!gst_submux_create_pipelines (submux, pad)) { + GST_ERROR_OBJECT (submux, "failed to create pipelines"); + return FALSE; + } + } + + if (!submux->priv->is_internal) { + gst_event_unref(event); + length = g_list_length(submux->streams); + for (i = 0; i < length; i++) { + GST_DEBUG_OBJECT (submux, "inside the handling of new_segment event"); + cur_stream = g_list_nth_data(submux->streams,i); + GST_DEBUG_OBJECT (submux, "pushing newsegment event with %" GST_SEGMENT_FORMAT, &submux->segment); + if (!cur_stream->pipe_struc.pipe) { + GST_ERROR_OBJECT (submux, "pipeline is null"); + return FALSE; + } + cur_stream = g_list_nth_data(submux->streams,i); + cur_stream->seek_ts = submux->segment.start; + ret = gst_element_send_event (cur_stream->pipe_struc.pipe, gst_event_new_new_segment (FALSE, + submux->segment.rate, submux->segment.format, + submux->segment.start, submux->segment.stop, submux->segment.time)); + if (!ret){ + GST_ERROR_OBJECT(submux, "sending newsegment event to stream[%d] failed", i); + break; + } + } + submux->need_segment = TRUE; + } else { + length = g_list_length (submux->sinkpad); + if (length == g_list_length (submux->streams) && submux->need_segment) { + for (i = 0; i < length; i++) { + GST_DEBUG_OBJECT (submux, "inside the handling of new_segment event"); + cur_stream = g_list_nth_data(submux->streams, i); + if (cur_stream->need_segment) { + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, &start, &stop, &time); + gst_segment_set_newsegment_full (&submux->segment, update, rate, arate, format, start, stop, time); + GST_DEBUG_OBJECT (submux, "pushing newsegment event with %" GST_SEGMENT_FORMAT, &submux->segment); + ret = TRUE; + cur_stream->need_segment = FALSE; + } + } + submux->need_segment = TRUE; + gst_event_unref(event); + } + } + GST_OBJECT_UNLOCK (submux); + break; + } + case GST_EVENT_FLUSH_START: { + length = g_list_length(submux->streams); + if (!submux->priv->is_internal) { + gst_event_unref(event); + ret = gst_pad_event_default (pad, gst_event_new_flush_start ()); + for (i = 0;i < length;i++) { + cur_stream = g_list_nth_data(submux->streams,i); + cur_stream->flushing = TRUE; + cur_stream->discont_came = FALSE; + GST_DEBUG_OBJECT (submux, "making discont false"); + GST_DEBUG_OBJECT (submux, "making flushing TRUE"); + cur_stream->eos_came = FALSE; + cur_stream->eos_sent = FALSE; + GST_DEBUG_OBJECT (submux, "making eos_came and eos_sent FALSE"); + } + for (i = 0; i < length; i++) { + cur_stream = g_list_nth_data(submux->streams,i); + ret= gst_element_send_event(cur_stream->pipe_struc.appsrc,gst_event_new_flush_start ()); + ret= gst_element_send_event(cur_stream->pipe_struc.appsrc,gst_event_new_flush_stop ()); + ret = gst_element_send_event(cur_stream->pipe_struc.appsrc,gst_event_new_eos ()); + GST_INFO_OBJECT(cur_stream,"flush stop and start and eos is done with ret %d",ret); + submux->flushing =TRUE; + g_mutex_lock (cur_stream->queue_lock); + g_cond_signal (cur_stream->queue_empty); + g_mutex_unlock(cur_stream->queue_lock); + cur_stream->flush_done = TRUE; + GST_DEBUG_OBJECT (cur_stream, "signaling queue empty signal from flush start"); + ret = TRUE; + GST_DEBUG_OBJECT (submux, "sending flush start event to stream[%d] success", i); + } + + if (!ret){ + GST_ERROR_OBJECT (submux, "sending flush start event to srcpad pad failed"); + break; + } + + if (submux && GST_PAD_TASK(submux->srcpad)) { + GST_INFO_OBJECT (submux, "trying acquire srcpad lock"); + GST_PAD_STREAM_LOCK (submux->srcpad); + GST_INFO_OBJECT (submux, "acquired stream lock"); + GST_PAD_STREAM_UNLOCK (submux->srcpad); + } + /*changes for new design*/ + for (i = 0;i < length;i++) { + cur_stream = g_list_nth_data(submux->streams,i); + gst_submux_stream_deinit(cur_stream,submux); + } + g_list_free(submux->streams); + submux->streams = NULL; + gst_submux_deinit_private_values (submux); + + submux->stop_loop = FALSE; + submux->need_segment = TRUE; + submux->langlist_msg_posted = FALSE; + GST_DEBUG_OBJECT (submux, "flush start successfully send to next element"); + } else { + GST_DEBUG_OBJECT(submux, "flusht start in case of internal subtitle"); + gst_event_unref (event); + submux->flushing = TRUE; + checkpad = (GstPad *) g_list_nth_data (submux->sinkpad, length - 1); + if (checkpad == pad) { + ret = gst_pad_event_default (pad, gst_event_new_flush_start ()); + for (i = 0; i < length; i++) { + cur_stream = g_list_nth_data(submux->streams, i); + cur_stream->flushing = TRUE; + GST_DEBUG_OBJECT (submux, "in case of internal making discont unchanged"); + GST_DEBUG_OBJECT (submux, "making flushing TRUE"); + } + for (i = 0; i < length; i++) { + cur_stream = g_list_nth_data(submux->streams, i); + submux->flushing = TRUE; + g_mutex_lock (cur_stream->queue_lock); + while (!g_queue_is_empty (cur_stream->queue)) { + buf = g_queue_pop_head (cur_stream->queue); + gst_buffer_unref (buf); + } + GST_DEBUG_OBJECT (submux, "cleared stream cur_stream->queue"); + g_queue_clear (cur_stream->queue); + g_cond_signal (cur_stream->queue_empty); + g_mutex_unlock(cur_stream->queue_lock); + GST_DEBUG_OBJECT (cur_stream, "signaling queue empty signal from flush start"); + cur_stream->eos_came = FALSE; + cur_stream->eos_sent = FALSE; + GST_DEBUG_OBJECT (submux, "making eos_came and eos_sent FALSE"); + ret = TRUE; + GST_DEBUG_OBJECT (submux, "sending flush start event to stream[%d] success", i); + } + if (!ret){ + GST_ERROR_OBJECT (submux, "sending flush start event to srcpad pad failed"); + break; + } + + if (submux && GST_PAD_TASK (submux->srcpad)) { + GST_INFO_OBJECT (submux, "trying acquire srcpad lock"); + GST_PAD_STREAM_LOCK (submux->srcpad); + GST_INFO_OBJECT (submux, "acquired srcpad lock"); + GST_PAD_STREAM_UNLOCK (submux->srcpad); + } + GST_DEBUG_OBJECT(submux, "flush start successfully send to next element"); + } + } + break; + } + case GST_EVENT_FLUSH_STOP: { + gst_event_unref(event); + if (!submux->priv->is_internal) { + guint idx = 0; + submux->flushing = FALSE; + ret = gst_pad_event_default (pad, gst_event_new_flush_stop ()); + if (!ret){ + GST_ERROR_OBJECT (submux, "sending flush-stop event to srcpad pad failed"); + break; + } + for (idx = 0; idx < submux->priv->stream_count; idx++) { + submux->cur_buf_array[idx] = NULL; + } + GST_DEBUG_OBJECT (submux, "flush stop successfully send to next element"); + } else { + length = g_list_length(submux->streams); + GST_DEBUG_OBJECT (submux, "flusht stop in case of internal subtitle"); + checkpad = (GstPad *) g_list_nth_data (submux->sinkpad, length - 1); + if (checkpad == pad) { + for (i = 0; i < length; i++) { + cur_stream = g_list_nth_data(submux->streams, i); + cur_stream->need_segment = TRUE; + submux->cur_buf_array[i] = NULL; + submux->need_segment = TRUE; + GST_DEBUG_OBJECT (submux, "making need_segment true"); + submux->flushing = FALSE; + cur_stream->flushing = FALSE; + GST_DEBUG_OBJECT (submux, "making flushing FALSE"); + ret = TRUE; + GST_DEBUG_OBJECT (submux, "sending %s event to stream[%d] success", GST_EVENT_TYPE_NAME (event), i); + } + ret = gst_pad_event_default (pad, gst_event_new_flush_stop ()); + if (!ret){ + GST_ERROR_OBJECT (submux, "sending flush-stop event to srcpad pad failed"); + break; + } + GST_DEBUG_OBJECT (submux, "flush stop successfully send to next element"); + } + } + break; + } + default:{ + if (!submux->priv->is_internal) { + ret = gst_pad_event_default (pad, event); + } else { + checkpad = (GstPad *) g_list_nth_data (submux->sinkpad, length - 1); + if (checkpad == pad) { + ret = gst_pad_event_default (pad, event); + } else { + ret = TRUE; + } + } + if (!ret){ + GST_ERROR_OBJECT (submux, "sending %s event to srcpad pad failed", GST_EVENT_TYPE_NAME (event)); + break; + } + break; + } + } + return ret; +} + +static gint gst_submux_buffer_list_sorting (gconstpointer a, gconstpointer b) +{ + GstBuffer *buf_a = (GstBuffer *) a; + GstBuffer *buf_b = (GstBuffer *) b; + if (GST_BUFFER_TIMESTAMP(buf_a)>GST_BUFFER_TIMESTAMP(buf_b)) + return -1; + else if(GST_BUFFER_TIMESTAMP(buf_a)= ref_stop)) + return FALSE; + + /* if a stop position is given and is before the segment start, + * we're outside of the segment. Special case is were start + * and stop are equal to the segment start. In that case we + * are inside the segment. */ + if (G_UNLIKELY (stop != -1 && (stop < ref_start || (start != stop && stop == ref_start)))) + return FALSE; + + return TRUE; +} + +/* This function do the actual muxing of buffer on the basis of timestamps */ +static GList* +gst_submux_muxing (Gstsubmux *submux) +{ + GstClockTime min_timestamp = 0; + int min_stream = 0; + int overlap = 0; + GstClockTime next_min_time = 0; + int idx = 0; + GList *push_list = NULL; + + /* Finding least timestamp of all streams and their stream ID */ + for (idx = 0; idx < submux->priv->stream_count; idx++) { + if(submux->cur_buf_array[idx] && !min_timestamp) { + min_timestamp = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]); + min_stream = idx; + } + if(submux->cur_buf_array[idx] && (GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]) < min_timestamp)) { + min_timestamp = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]); + min_stream = idx; + } + } + + GST_DEBUG_OBJECT (submux, "Identified least timestamp: %"GST_TIME_FORMAT" for stream: %d", + GST_TIME_ARGS(min_timestamp), min_stream); + + /* Finding overlap buffers and next least timestamp */ + for (idx = 0; idx < submux->priv->stream_count; idx++) { + if(submux->cur_buf_array[idx] && (idx != min_stream) && (!next_min_time)) { + next_min_time = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]); + } + if(submux->cur_buf_array[idx] && (idx != min_stream)) { + if(gst_submux_is_muxing_needed (submux->cur_buf_array[min_stream], submux->cur_buf_array[idx])) { + overlap = overlap | (1<cur_buf_array[idx]) < next_min_time) + next_min_time = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]); + } + } + } + + GST_DEBUG_OBJECT (submux, "Identified overlap: %d next least timestamp: %"GST_TIME_FORMAT" ", overlap, GST_TIME_ARGS(next_min_time)); + + /* If no overlap send buffer as it is */ + if(!overlap) { + GST_DEBUG_OBJECT (submux, "pushing string: %s....", (gchar*)GST_BUFFER_DATA(submux->cur_buf_array[min_stream])); + push_list = g_list_append(push_list, submux->cur_buf_array[min_stream]); + GST_DEBUG_OBJECT (submux, "No overlap found pushing buffer of ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream])), GST_TIME_ARGS(GST_BUFFER_DURATION(submux->cur_buf_array[min_stream]))); + submux->cur_buf_array[min_stream] = NULL; + } else { + GstBuffer *push_buf = NULL; + GstClockTime stop_time = 0; + int stop_idx = 0; + GstBuffer *overlap_buf = NULL; + guint overlap_buf_length = 0; + GString *overlap_text = NULL; + gchar *text = NULL; + + if(next_min_time > GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream])) { + GST_DEBUG_OBJECT (submux, "Before duration change ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream])), GST_TIME_ARGS(GST_BUFFER_DURATION(submux->cur_buf_array[min_stream]))); + push_buf = gst_buffer_copy (submux->cur_buf_array[min_stream]); + push_buf->duration = next_min_time - GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream]); + GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream]) = next_min_time; + GST_BUFFER_DURATION(submux->cur_buf_array[min_stream]) -= push_buf->duration; + GST_DEBUG_OBJECT (submux, "After duration change ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(submux->cur_buf_array[min_stream])), GST_TIME_ARGS(GST_BUFFER_DURATION(submux->cur_buf_array[min_stream]))); + + min_timestamp = next_min_time; + GST_INFO_OBJECT (submux, "pushing string: %s...", (gchar*)GST_BUFFER_DATA(push_buf)); + push_list = g_list_append(push_list, push_buf); + GST_DEBUG_OBJECT (submux, "Overlap found pushing initial partial buffer of ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(push_buf)), GST_TIME_ARGS(GST_BUFFER_DURATION(push_buf))); + } + + for (idx = 0; idx < submux->priv->stream_count; idx++) { + if(submux->cur_buf_array[idx] && !stop_time) { + stop_time = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]) + GST_BUFFER_DURATION(submux->cur_buf_array[idx]); + stop_idx = idx; + } + if(submux->cur_buf_array[idx] && + ((GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx])+ GST_BUFFER_DURATION(submux->cur_buf_array[idx])) < stop_time)) { + stop_time = GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx]) + GST_BUFFER_DURATION(submux->cur_buf_array[idx]); + stop_idx = idx; + } + } + GST_DEBUG_OBJECT (submux, "Identified least stop timestamp: %"GST_TIME_FORMAT" for stream: %d", + GST_TIME_ARGS(stop_time), stop_idx); + + overlap_text = g_string_new (""); + overlap = overlap | (1<priv->stream_count; idx++) { + int finder = 1<cur_buf_array[idx])); + g_string_append (overlap_text, (gchar*)GST_BUFFER_DATA (submux->cur_buf_array[idx])); + GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx])+= (stop_time - min_timestamp); + GST_BUFFER_DURATION(submux->cur_buf_array[idx])-= (stop_time - min_timestamp); + if(overlap > (1<<(idx+1))) g_string_append_c (overlap_text, '\n'); + } + } + text = g_string_free (overlap_text, FALSE); + overlap_buf_length = strlen(text); + overlap_buf = gst_buffer_new_and_alloc (overlap_buf_length + 1); + memcpy (GST_BUFFER_DATA (overlap_buf), text, overlap_buf_length + 1); + overlap_buf->timestamp = min_timestamp; + overlap_buf->duration = stop_time - min_timestamp; + g_free (text); + text = NULL; + submux->cur_buf_array[stop_idx] = NULL; + GST_DEBUG_OBJECT (submux, "pushing string: %s....", (gchar*)GST_BUFFER_DATA(overlap_buf)); + push_list = g_list_append(push_list, overlap_buf); + GST_DEBUG_OBJECT (submux, "Overlap found pushing merged buffer of ts = %"GST_TIME_FORMAT", dur = %"GST_TIME_FORMAT, + GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(overlap_buf)), GST_TIME_ARGS(GST_BUFFER_DURATION(overlap_buf))); + GST_DEBUG_OBJECT (submux, "request for new buffer for stream %d", stop_idx); + for (idx = 0; idx < submux->priv->stream_count; idx++) { + if(submux->cur_buf_array[idx] && + ((GST_BUFFER_TIMESTAMP(submux->cur_buf_array[idx])+ GST_BUFFER_DURATION(submux->cur_buf_array[idx])) <= stop_time)) { + submux->cur_buf_array[idx] = NULL; + GST_DEBUG_OBJECT (submux, "request for new buffer for stream %d", idx); + } + } + } + return push_list; +} + +static void gst_submux_loop (Gstsubmux *submux) +{ + guint length = 0; + GstBuffer *src_buffer = NULL; + GstBuffer *temp_buffer = NULL; + GstBuffer *check_buffer = NULL; + GstSubmuxStream *cur_stream = NULL; + GstSubmuxStream *check_stream = NULL; + gboolean match = FALSE; + GstFlowReturn fret = GST_FLOW_OK; + GstClockTime cur_duration = 0 ; + GstClockTime cur_ts = 0; + gboolean eos = TRUE; + guint i= 0,k = 0; + GList *push_list = NULL; + GstBuffer *push_buf = NULL; + + if (!submux->priv->first_buffer) { + GST_INFO_OBJECT (submux, "exiting from lopp"); + return; + } + + if (submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI) { + length = g_list_length (submux->priv->lang_list); + } else { + length = submux->sinkpads_count; + } + + gboolean made = FALSE; + // length = 2; + + for (i = 0; i < length; i++) { + +re_pop: + cur_stream = g_list_nth_data (submux->streams, i); + GST_DEBUG_OBJECT (submux, "Before lock acquired in loop stream[%d]", i); + g_mutex_lock (cur_stream->queue_lock); + GST_DEBUG_OBJECT (submux, "Lock acquired in loop stream[%d]", i); + + if (g_queue_is_empty (cur_stream->queue) && !submux->flushing) { + GST_DEBUG_OBJECT (submux, "Queue is empty, waiting for the condition signal stream[%d]", i); + g_cond_wait (cur_stream->queue_empty, cur_stream->queue_lock); + } + GST_DEBUG_OBJECT (submux, "Got the queue condition signal stream[%d]", i); + + if (submux->flushing || submux->stop_loop) { + GST_DEBUG_OBJECT (submux, "Flushing going on in loop"); + GST_DEBUG_OBJECT (submux, "Got the condition signal"); + g_mutex_unlock (cur_stream->queue_lock); + goto error; + } + + check_buffer = g_queue_peek_head (cur_stream->queue); + if (!strcmp ((const char*)GST_BUFFER_DATA (check_buffer), "eos")){ + cur_stream->eos_came = TRUE; + GST_DEBUG_OBJECT (submux, "Eos recieved for stream"); + } + for (k = 0; k < length; k++) { + check_stream = g_list_nth_data(submux->streams, k); + if (!check_stream->eos_came) { + eos = FALSE; + break; + } else { + eos = TRUE; + } + } + if (eos) { + GST_DEBUG_OBJECT (submux, "Sending EOS to submux srcpad"); + gst_pad_push_event(submux->srcpad, gst_event_new_eos ()); + g_mutex_unlock (cur_stream->queue_lock); + goto eos_sent; + } + + if (!cur_stream->eos_came && (submux->priv->parser_type == GST_SUB_PARSE_FORMAT_SAMI || + submux->priv->is_internal)) { + GstLangStruct *lang = NULL; + if (submux->priv->lang_list) { + if (submux->cur_buf_array[i] == NULL) { + check_buffer = g_queue_pop_head (cur_stream->queue); + lang = g_list_nth_data(submux->priv->lang_list, i); + if (!lang->active) { + if(check_buffer) { + gst_buffer_unref(check_buffer); + check_buffer = NULL; + GST_DEBUG_OBJECT (submux, "unreffing the non-active stream[%d] buffer", i); + } + submux->cur_buf_array[i] = NULL; + g_mutex_unlock (cur_stream->queue_lock); + GST_DEBUG_OBJECT(submux,"rejecting not selected language"); + continue; + } else { + if (!check_buffer) { + GST_WARNING_OBJECT (submux, "checkbuffer null.. repop"); + g_mutex_unlock (cur_stream->queue_lock); + goto re_pop; + } + if (!GST_BUFFER_DURATION(check_buffer)) { + GST_WARNING_OBJECT (submux, "duration of buffer is zero..re-pop"); + gst_buffer_unref (check_buffer); + g_mutex_unlock (cur_stream->queue_lock); + goto re_pop; + } + submux->cur_buf_array[i] = check_buffer; + GST_DEBUG_OBJECT (submux, "consuming active stream [%d] buffer : ts = %"GST_TIME_FORMAT"and dur = %"GST_TIME_FORMAT, + i, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (check_buffer)), GST_TIME_ARGS(GST_BUFFER_DURATION (check_buffer))); + } + } + } else { + g_mutex_unlock (cur_stream->queue_lock); + GST_DEBUG_OBJECT(submux,"Coming to Else case lang submux->priv->lang_list %x ",submux->priv->lang_list); + continue; + } + } else if (!cur_stream->eos_came) { + /* External subtitle format other than smi */ + if (submux->sinkpad) { + if (submux->cur_buf_array[i] == NULL) { + check_buffer = g_queue_pop_head (cur_stream->queue); + if (!check_buffer) { + GST_WARNING_OBJECT (submux, "checkbuffer null.. repop"); + g_mutex_unlock (cur_stream->queue_lock); + goto re_pop; + } + if (!GST_BUFFER_DURATION (check_buffer)) { + GST_WARNING_OBJECT (submux, "duration of buffer is zero..re-pop"); + gst_buffer_unref (check_buffer); + g_mutex_unlock (cur_stream->queue_lock); + goto re_pop; + } + submux->cur_buf_array[i] = check_buffer; + GST_DEBUG_OBJECT (submux, "consuming active stream [%d] buffer : ts = %"GST_TIME_FORMAT"and dur = %"GST_TIME_FORMAT, + i, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP (check_buffer)), GST_TIME_ARGS(GST_BUFFER_DURATION (check_buffer))); + } + } else { + g_mutex_unlock (cur_stream->queue_lock); + GST_DEBUG_OBJECT(submux,"Coming to Else case submux->sinkpad %x ",submux->sinkpad); + continue; + } + } else { + GST_DEBUG_OBJECT (submux, "already received EOS on this stream[%d] and cur_buf_array[idx] = NULL", i); + submux->cur_buf_array[i] = NULL; + } + + g_mutex_unlock (cur_stream->queue_lock); + GST_DEBUG_OBJECT (submux, "After unlocking in loop and signal queue full"); + } + + push_list = gst_submux_muxing (submux); + if (push_list) { + guint idx = 0; + GST_LOG_OBJECT (submux, "length of push list = %d", g_list_length (push_list)); + + for (idx = 0; idx < g_list_length (push_list); idx++) { + push_buf = g_list_nth_data (push_list, idx); + + if (push_buf) { + GST_DEBUG_OBJECT (submux, "pushing buffer : ts = %"GST_TIME_FORMAT", " + "dur = %"GST_TIME_FORMAT" and data %s ...", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (push_buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (push_buf)), (gchar*)GST_BUFFER_DATA (push_buf)); + + fret = gst_pad_push (submux->srcpad, push_buf); + if (fret != GST_FLOW_OK) { + GST_ERROR_OBJECT (submux, "failed to push buffer. reason : %s", gst_flow_get_name (fret)); + /* clean any left buffers in push_list */ + idx++; + for (; idx < g_list_length (push_list); idx++) { + push_buf = g_list_nth_data (push_list, idx); + gst_buffer_unref (push_buf); + } + g_list_free (push_list); + goto error; + } + } + } + + g_list_free (push_list); + } + + GST_DEBUG_OBJECT (submux, "Exiting from lopp in last"); + + return; + +eos_sent: +error: + GST_WARNING_OBJECT (submux->srcpad, "Pausing the push task..."); + if (fret < GST_FLOW_UNEXPECTED) { + GST_ERROR_OBJECT (submux, "Crtical error in push loop...."); + GST_ELEMENT_ERROR (submux, CORE, PAD, ("failed to push. reason - %s", gst_flow_get_name (fret)), (NULL)); + } + gst_pad_pause_task (submux->srcpad); + GST_DEBUG_OBJECT (submux, "Exiting from lopp in last"); + return; +} + +//////////////////////////////////////////////////////// +// Plugin Utility Functions // +//////////////////////////////////////////////////////// +/* +** +** Description : De-Initializing the submux private structure +** Params : (1) submux instance +** return : TRUE +** Comments : +** +*/ +static gboolean +gst_submux_deinit_private_values(Gstsubmux *submux) +{ + guint idx = 0; + GST_DEBUG_OBJECT (submux, "deinit priv values"); + + submux->priv->first_buffer = FALSE; + submux->priv->parser_type = 0; + if (submux->priv->lang_list && !submux->priv->is_internal) { + g_list_free (submux->priv->lang_list); + submux->priv->lang_list = NULL; + } + for (idx = 0; idx < submux->priv->stream_count; idx++) { + submux->cur_buf_array[idx] = NULL; + } + if (submux->cur_buf_array) { + g_free (submux->cur_buf_array); + submux->cur_buf_array = NULL; + } + + submux->priv->is_internal = FALSE; + submux->priv->stream_count = 0; + + return TRUE; +} + +static gboolean +gst_submux_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "submux", GST_RANK_PRIMARY, GST_TYPE_SUBMUX); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,GST_VERSION_MINOR,"submux","submux",gst_submux_plugin_init,"0.10.36","Proprietary","Samsung Electronics Co","http://www.samsung.com") diff --git a/submux/src/gstsubmux.h b/submux/src/gstsubmux.h new file mode 100755 index 0000000..696b8b6 --- /dev/null +++ b/submux/src/gstsubmux.h @@ -0,0 +1,171 @@ +/* + * N-Project + * Copyright (c) 2008 Samsung Electronics, Inc. + * All rights reserved. + * + * This software is a confidential and proprietary information + * of Samsung Electronics, Inc. ("Confidential Information"). You + * shall not disclose such Confidential Information and shall use + * it only in accordance with the terms of the license agreement + * you entered into with Samsung Electronics. + */ + +/* + * This file defines the functions of Gstreamer plug-in for submux + * + * @file + * @author Deepak Singh (deep.singh@samsung.com) + * @version 1.0 + * @usage + * @brief This plug-in is targeted to be used for ARM MP4ASP decoding. + */ + +/*! Revision History: + *! --------------------------------------------------------------------------- + *! DATE | AUTHOR | COMMENTS + *! --------------------------------------------------------------------------- + *! 17-Jan-2014 deep.singh@samsung.com created + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +G_BEGIN_DECLS + +#define GST_TYPE_SUBMUX (gst_submux_get_type()) +#define GST_SUBMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SUBMUX,Gstsubmux)) +#define GST_SUBMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SUBMUX,GstsubmuxClass)) +#define GST_SUBMUX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_SUBMUX,GstsubmuxClass)) +#define GST_IS_SUBMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SUBMUX)) +#define GST_IS_SUBMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SUBMUX)) +#define GST_SUBMUX_CAST(obj) ((Gstsubmux *)(obj)) +#define MAX_LANG 10 + +typedef struct _Gstsubmux Gstsubmux; +typedef struct _GstsubmuxClass GstsubmuxClass; +typedef struct _GstsubmuxPrivate GstsubmuxPrivate; +typedef struct _LanguageStruct GstLangStruct; +typedef struct _GstSubMuxStream GstSubmuxStream; +typedef struct _GstSubMuxPipe SubMuxPipe; + +typedef enum +{ + GST_SUB_PARSE_FORMAT_UNKNOWN = 0, + GST_SUB_PARSE_FORMAT_MDVDSUB = 1, + GST_SUB_PARSE_FORMAT_SUBRIP = 2, + GST_SUB_PARSE_FORMAT_MPSUB = 3, + GST_SUB_PARSE_FORMAT_SAMI = 4, + GST_SUB_PARSE_FORMAT_TMPLAYER = 5, + GST_SUB_PARSE_FORMAT_MPL2 = 6, + GST_SUB_PARSE_FORMAT_SUBVIEWER = 7, + GST_SUB_PARSE_FORMAT_DKS = 8, + GST_SUB_PARSE_FORMAT_QTTEXT = 9 +} GstSubMuxFormat; +typedef enum +{ + GST_SUB_PARSE_REGEX_UNKNOWN = 0, + GST_SUB_PARSE_REGEX_MDVDSUB = 1, + GST_SUB_PARSE_REGEX_SUBRIP = 2, + GST_SUB_PARSE_REGEX_DKS = 3, +} GstSubMuxRegex; +struct _GstSubMuxPipe +{ + GstElement *pipe; + GstElement *appsrc; + GstElement *appsink; + GstElement *parser; + GstPad *app_sinkpad; +}; +struct _GstSubMuxStream +{ + void *parent; + gboolean need_segment; + gboolean discont_came; + gboolean flushing; + SubMuxPipe pipe_struc; + gboolean eos_sent; + gboolean eos_came; + GstClockTime duration; + GstClockTime last_ts; /* last timestamp of subtitle present in subtitle file*/ + GstClockTime eos_ts; + GstClockTime seek_ts; + GstBuffer *buffer; + GQueue *queue; + GMutex *queue_lock; + GCond *queue_empty; + gboolean flush_done; +}; +struct _Gstsubmux +{ + GstElement element; + + /*< private >*/ + GstsubmuxPrivate *priv; + + /* pads */ + guint sinkpads_count; + gboolean external_sinkpad; + gboolean stop_loop; + GstPad *srcpad; + GList *sinkpad; + gboolean pipeline_made; + //GstPad *sinkpad; + GList *buffer_list; + gboolean init_done; + GList *msl_streams; + GList *streams; + gboolean flushing; + gboolean need_segment; + GstSubmuxStream muxed_stream; + gchar *encoding; + gchar *detected_encoding; + gint fps_n, fps_d; + GstSegment segment; + GstTask *loop_task; + GstSeekFlags segment_flags; + gboolean seek_came ; + gboolean finalize; + gboolean langlist_msg_posted; + GstBuffer **cur_buf_array; + gchar* external_filepath; +}; + +struct _GstsubmuxPrivate +{ + gboolean first_buffer; + GstSubMuxFormat parser_type; + GList *lang_list; + gboolean is_internal; + guint stream_count; +}; + + +struct _LanguageStruct +{ + gchar *language_code; + gchar *language_key; + gboolean active; +}; + +struct _GstsubmuxClass +{ + GstElementClass parent_class; +}; + +GType gst_submux_get_type (void); + +G_END_DECLS + + diff --git a/toggle/src/gsttoggle.c b/toggle/src/gsttoggle.c index 2ae6ac2..1f8d831 100755 --- a/toggle/src/gsttoggle.c +++ b/toggle/src/gsttoggle.c @@ -104,11 +104,9 @@ static void gst_mytoggle_class_init (GstMytoggleClass * klass) { GObjectClass *gobject_class; - GstElementClass *gstelement_class; GstBaseTransformClass *gstbasetrans_class; gobject_class = G_OBJECT_CLASS (klass); - gstelement_class = GST_ELEMENT_CLASS (klass); gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (klass); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_mytoggle_set_property); diff --git a/avsystem/Makefile.am b/xvimagesrc/Makefile.am similarity index 100% rename from avsystem/Makefile.am rename to xvimagesrc/Makefile.am diff --git a/xvimagesrc/src/Makefile.am b/xvimagesrc/src/Makefile.am new file mode 100644 index 0000000..eff56f2 --- /dev/null +++ b/xvimagesrc/src/Makefile.am @@ -0,0 +1,26 @@ +# plugindir is set in configure + +############################################################################## +# change libgstplugin.la to something more suitable, e.g. libmysomething.la # +############################################################################## +plugin_LTLIBRARIES = libgstxvimagesrc.la + +############################################################################## +# for the next set of variables, rename the prefix if you renamed the .la, # +# e.g. libgstplugin_la_SOURCES => libmysomething_la_SOURCES # +# libgstplugin_la_CFLAGS => libmysomething_la_CFLAGS # +# libgstplugin_la_LIBADD => libmysomething_la_LIBADD # +# libgstplugin_la_LDFLAGS => libmysomething_la_LDFLAGS # +############################################################################## + +# sources used to compile this plug-in +libgstxvimagesrc_la_SOURCES = gstxvimagesrc.c + +# flags used to compile this plugin +# add other _CFLAGS and _LIBS as needed +libgstxvimagesrc_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(TBM_CFLAGS) $(DRI2_CFLAGS) $(X11_CFLAGS) $(XEXT_CFLAGS) $(XV_CFLAGS) $(XDAMAGE_CFLAGS) $(DRM_DEVEL_CFLAGS) $(DRM_CFLAGS) +libgstxvimagesrc_la_LIBADD = $(GST_LIBS) $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(TBM_LIBS) $(DRI2_LIBS) $(X11_LIBS) $(XEXT_LIBS) $(XV_LIBS) $(XDAMAGE_LIBS) $(DRM_LIBS) $(DRM_DEVEL_LIBS) $(DRM_SLP_MSM) +libgstxvimagesrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +# headers we need but don't want installed +noinst_HEADERS = gstxvimagesrc.h diff --git a/xvimagesrc/src/gstxvimagesrc.c b/xvimagesrc/src/gstxvimagesrc.c new file mode 100755 index 0000000..1a29d62 --- /dev/null +++ b/xvimagesrc/src/gstxvimagesrc.c @@ -0,0 +1,1832 @@ +/* + * xvimagesrc + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Hyunil Park + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +/** +* SECTION:element-xvimagesrc +* +* xvimagesrc captures frame buffer which includes the application data along with video layer data +* from the XServer and pushes the data to the downstream element. +* +* +* +* Example launch line +* |[ +* gst-launch xvimagesrc ! "video/x-raw-yuv, width=720, height=1280, framerate=(fraction)30/1, format=(fourcc)ST12" ! fakesink +* ]| captures the frame buffer from the XServer and send the buffers to a fakesink. +* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstxvimagesrc.h" + +/* headers for drm */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (xvimagesrc_debug); +#define GST_CAT_DEFAULT xvimagesrc_debug + + +#define GST_XV_IMAGE_SRC_CAPS \ + "video/x-raw-rgb, " \ + "bpp = (int) 32, " \ + "depth = (int) 24, " \ + "endianness = (int) BIG_ENDIAN, " \ + "red_mask = (int) 0xFF000000, " \ + "green_mask = (int) 0x00FF0000, " \ + "blue_mask = (int) 0x0000FF00, " \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ], " \ + "framerate = (fraction) [0/1, 2147483647/1];" \ + "video/x-raw-yuv," \ + "format = (fourcc) { SN12, ST12, NV12 }, " \ + "width = (int) [ 1, 4096 ], " \ + "height = (int) [ 1, 4096 ], " \ + "framerate = (fraction) [0/1, 2147483647/1];" \ + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS(GST_XV_IMAGE_SRC_CAPS) +); + +enum +{ + PROP_0, + PROP_AUDIO_LATENCY, + PROP_SECURE_MODE, + PROP_DISPLAY_ROTATE, +}; + +enum +{ + VIDEO_TYPE_UI, + VIDEO_TYPE_VIDEO_WITH_UI, + VIDEO_TYPE_VIDEO_ONLY, +}; + +enum +{ + SIGNAL_UI_ONLY = 0, + SIGNAL_VIDEO_WITH_UI, + SIGNAL_VIDEO_ONLY, + SIGNAL_SELECTION_NOTIFY, + SIGNAL_LAST +}; + +#define GEM_NAME_MAX 10 + +#define SCMN_CS_YUV420 1 /* Y:U:V 4:2:0 */ +#define SCMN_CS_I420 SCMN_CS_YUV420 /* Y:U:V */ +#define SCMN_CS_NV12 6 +#define SCMN_CS_NV12_T64X32 11 /* 64x32 Tiled NV12 type */ +#define SCMN_CS_UYVY 100 +#define SCMN_CS_YUYV 101 +#define SCMN_CS_YUY2 SCMN_CS_YUYV + +/* max channel count *********************************************************/ +#define SCMN_IMGB_MAX_PLANE (4) + +/* image buffer definition *************************************************** + + +------------------------------------------+ --- + | | ^ + | a[], p[] | | + | +---------------------------+ --- | | + | | | ^ | | + | |<---------- w[] ---------->| | | | + | | | | | | + | | | | + | | | h[] | e[] + | | | | + | | | | | | + | | | | | | + | | | v | | + | +---------------------------+ --- | | + | | v + +------------------------------------------+ --- + + |<----------------- s[] ------------------>| +*/ + +typedef struct +{ + /* width of each image plane */ + int w[SCMN_IMGB_MAX_PLANE]; + /* height of each image plane */ + int h[SCMN_IMGB_MAX_PLANE]; + /* stride of each image plane */ + int s[SCMN_IMGB_MAX_PLANE]; + /* elevation of each image plane */ + int e[SCMN_IMGB_MAX_PLANE]; + /* user space address of each image plane */ + void *a[SCMN_IMGB_MAX_PLANE]; + /* physical address of each image plane, if needs */ + void *p[SCMN_IMGB_MAX_PLANE]; + /* color space type of image */ + int cs; + /* left postion, if needs */ + int x; + /* top position, if needs */ + int y; + /* to align memory */ + int __dummy2; + /* arbitrary data */ + int data[16]; + /* dmabuf fd */ + gint32 fd[SCMN_IMGB_MAX_PLANE]; + /* flag for buffer share */ + int buf_share_method; + /* Y plane size in case of ST12 */ + int y_size; + /* UV plane size in case of ST12 */ + int uv_size; + + /* Tizen buffer object of each image plane */ + void *bo[SCMN_IMGB_MAX_PLANE]; + + /* JPEG data */ + void *jpeg_data; + /* JPEG size */ + int jpeg_size; + + /* tzmem buffer */ + int tz_enable; +} SCMN_IMGB; + +typedef struct +{ + void *address[GEM_NAME_MAX]; + int buffer_size[GEM_NAME_MAX]; + int name[GEM_NAME_MAX]; + gint32 fd[GEM_NAME_MAX]; + gint32 handle[GEM_NAME_MAX]; +} GEM_MMAP; + +typedef enum { + BUF_SHARE_METHOD_PADDR = 0, + BUF_SHARE_METHOD_FD +} buf_share_method_t; + +typedef struct GstXvImageOutBuffer GstXvImageOutBuffer; + +struct GstXvImageOutBuffer { + GstBuffer buffer; + int fd_name; + int YBuf; + GstXVImageSrc * xvimagesrc; +}; + + + +#define DEFAULT_USER_AGENT "GStreamer xvimagesrc " + +//#define _MAKE_DUMP +#ifdef _MAKE_DUMP +#define YUV_FRAME_SIZE 3110400 +#define YUV_720_FRAME_SIZE 1382400 +#define YUV_VGA_FRAME_SIZE 460800 + +static int g_prev_frame[YUV_FRAME_SIZE] = {0,}; +static int g_dump_frame[100][YUV_720_FRAME_SIZE]; +//static int g_dump_frame[200][YUV_VGA_FRAME_SIZE]; +static int f_idx = 0; +static int f_done = 0; +#endif + +static guint gst_xv_image_src_signals[SIGNAL_LAST] = { 0 }; + +//#define COUNT_FRAMES +#ifdef COUNT_FRAMES +gchar old_time[10] = {0, }; +#endif +static gboolean error_caught = FALSE; + +#define HANDLE_OUTBUF_UNREF + +#ifdef HANDLE_OUTBUF_UNREF +#define BUFFER_COND_WAIT_TIMEOUT 1000000 +#define GST_TYPE_GST_XV_IMAGE_OUT_BUFFER (gst_xv_image_out_buffer_get_type()) +#define GST_IS_GST_XV_IMAGE_OUT_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GST_XV_IMAGE_OUT_BUFFER)) +static GstBufferClass *gst_xv_image_out_buffer_parent_class = NULL; +static void gst_xv_image_out_buffer_class_init(gpointer g_class, gpointer class_data); +static void gst_xv_image_out_buffer_finalize(GstXvImageOutBuffer *buffer); +static GstXvImageOutBuffer *gst_xv_image_out_buffer_new(GstXVImageSrc *src); +#endif +static GstStateChangeReturn gst_xv_image_src_change_state (GstElement * element, GstStateChange transition); +static void gst_xv_image_src_finalize (GObject * gobject); + +static void gst_xv_image_src_set_property (GObject *object, guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_xv_image_src_get_property (GObject *object, guint prop_id, GValue * value, GParamSpec * pspec); +static GstFlowReturn gst_xv_image_src_create (GstPushSrc * psrc, GstBuffer ** outbuf); +static gboolean gst_xv_image_src_start (GstBaseSrc * bsrc); +static gboolean gst_xv_image_src_stop (GstBaseSrc * bsrc); +static gboolean gst_xv_image_src_get_size (GstBaseSrc * bsrc, guint64 * size); +static gboolean gst_xv_image_src_is_seekable (GstBaseSrc * bsrc); +static gboolean gst_xv_image_src_query (GstBaseSrc * bsrc, GstQuery * query); +static gboolean gst_xv_image_src_unlock (GstBaseSrc * bsrc); +static gboolean gst_xv_image_src_unlock_stop (GstBaseSrc * bsrc); +static gboolean gst_xv_image_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps); + +static tbm_bufmgr bufmgr_get (Display *dpy, Pixmap pixmap); +static int port_get (GstXVImageSrc * src, unsigned int id); +static void pixmap_update (GstXVImageSrc * src,Display *dpy, tbm_bufmgr bufmgr, Pixmap pixmap, + int x, int y, int width, int height); +static Pixmap pixmap_create (GstXVImageSrc * src, Display *dpy, int width, int height); + +static void* gst_xv_image_src_update_thread (void * asrc); +static gboolean xvimagesrc_thread_start(GstXVImageSrc *src); +static void drm_init(GstXVImageSrc *src); +static void drm_finalize(GstXVImageSrc *src); +static gint32 drm_convert_gem_to_fd(int * gemname_cnt, int drm_fd, unsigned int name, void * data, void **virtual_address); +static void gst_xv_get_image_sleep(void *asrc, long duration); + +static void +_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (xvimagesrc_debug, "xvimagesrc", 0, "Xv image src"); +} + +GST_BOILERPLATE_FULL (GstXVImageSrc, gst_xv_image_src, GstPushSrc, GST_TYPE_PUSH_SRC, _do_init); + +#ifdef HANDLE_OUTBUF_UNREF +static void gst_xv_image_out_buffer_class_init(gpointer g_class, gpointer class_data) +{ + GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS(g_class); + gst_xv_image_out_buffer_parent_class = g_type_class_peek_parent(g_class); + mini_object_class->finalize = (GstMiniObjectFinalizeFunction)gst_xv_image_out_buffer_finalize; +} + +static GType gst_xv_image_out_buffer_get_type(void) +{ + static GType _gst_gst_xv_image_out_buffer_type; + + if (G_UNLIKELY(_gst_gst_xv_image_out_buffer_type == 0)) { + static const GTypeInfo gst_xv_image_out_buffer_info = { + sizeof (GstBufferClass), + NULL, + NULL, + gst_xv_image_out_buffer_class_init, + NULL, + NULL, + sizeof (GstXvImageOutBuffer), + 0, + NULL, + NULL + }; + _gst_gst_xv_image_out_buffer_type = g_type_register_static(GST_TYPE_BUFFER, + "GstOmxOutBuffer", + &gst_xv_image_out_buffer_info, 0); + } + return _gst_gst_xv_image_out_buffer_type; +} +#ifdef DEBUG_BUFFER + int value[5] ={0}; + static int value_count =0; +#endif + +static GstStateChangeReturn +gst_xv_image_src_change_state (GstElement * element, GstStateChange transition) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (element); + + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + src->pause_cond_var = FALSE; + g_cond_signal(src->pause_cond); + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_WARNING("GST_STATE_CHANGE_PLAYING_TO_PAUSED: START"); + src->pause_cond_var = TRUE; + g_cond_wait(src->pause_resp, src->pause_resp_lock); + GST_WARNING("GST_STATE_CHANGE_PLAYING_TO_PAUSED: End"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_WARNING("GST_STATE_CHANGE_PAUSED_TO_READY"); + src->thread_return = TRUE; + g_cond_signal(src->pause_cond); + g_cond_signal(src->queue_cond); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default : + break; + } + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + return result; +} + + +static void gst_xv_image_out_buffer_finalize(GstXvImageOutBuffer *buffer) +{ + Atom atom_retbuf = 0; + if (buffer->xvimagesrc->thread_return) { + GST_INFO("xvimagesrc is being shutdown"); + return; + } + + g_mutex_lock (buffer->xvimagesrc->dpy_lock); + atom_retbuf = XInternAtom (buffer->xvimagesrc->dpy, "_USER_WM_PORT_ATTRIBUTE_RETURN_BUFFER", False); + XvSetPortAttribute (buffer->xvimagesrc->dpy, buffer->xvimagesrc->p, atom_retbuf, buffer->YBuf); //data->YBuf is gemname, refer to drm_convert_gem_to_fd + g_mutex_unlock (buffer->xvimagesrc->dpy_lock); + g_cond_signal(buffer->xvimagesrc->buffer_cond); + GST_INFO(" xvimagesrc = %p, gem_name =%d, fd_name =%d", buffer->xvimagesrc, buffer->YBuf, buffer->fd_name); +#ifdef DEBUG_BUFFER + int i = 0; + for(i=0 ; i<5; i++){ + if (value[i] == buffer->YBuf) { + value[i]=0; + GST_ERROR("value[%d]=%d", i, value[i]); + } + } +#endif + return; +} + + +static GstXvImageOutBuffer *gst_xv_image_out_buffer_new(GstXVImageSrc *src) +{ + GstXvImageOutBuffer *newbuf = NULL; + GST_LOG("gst_omx_out_buffer_new"); + + newbuf = (GstXvImageOutBuffer *)gst_mini_object_new(GST_TYPE_GST_XV_IMAGE_OUT_BUFFER); + if(!newbuf) + { + GST_ERROR("gst_omx_out_buffer_new out of memory"); + return NULL; + } + GST_LOG("creating buffer : %p", newbuf); + newbuf->xvimagesrc = gst_object_ref(GST_OBJECT(src)); + newbuf->fd_name = 0; + newbuf->YBuf =0; + return newbuf; +} +#endif + +static void +gst_xv_image_src_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&srctemplate)); + gst_element_class_set_details_simple (element_class, "XServer Display FB video source", + "Source/video", + "Receive frame buffer data from XServer and passes to next element", + ""); +} + +static void +gst_xv_image_src_class_init (GstXVImageSrcClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSrcClass *gstbasesrc_class; + GstPushSrcClass *gstpushsrc_class; + GstElementClass *gstelement_class; + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = (GstElementClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstpushsrc_class = (GstPushSrcClass *) klass; + gobject_class->set_property = gst_xv_image_src_set_property; + gobject_class->get_property = gst_xv_image_src_get_property; + gobject_class->finalize = gst_xv_image_src_finalize; + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_xv_image_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_xv_image_src_stop); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_xv_image_src_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_xv_image_src_unlock_stop); + gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_xv_image_src_get_size); + gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_xv_image_src_is_seekable); + gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_xv_image_src_query); + gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_xv_image_src_setcaps); + gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_xv_image_src_create); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_xv_image_src_change_state); + g_object_class_install_property (gobject_class, PROP_DISPLAY_ROTATE, + g_param_spec_uint64 ("display-rotate", + "display-rotate", + "Display rotate info", + 0, G_MAXUINT64, 0, G_PARAM_READWRITE)); + + gst_xv_image_src_signals[SIGNAL_UI_ONLY] = + g_signal_new ("ui-only", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstXVImageSrcClass, ui_only), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + + gst_xv_image_src_signals[SIGNAL_VIDEO_WITH_UI] = + g_signal_new ("video-with-ui", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstXVImageSrcClass, video_with_ui), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + + gst_xv_image_src_signals[SIGNAL_VIDEO_ONLY] = + g_signal_new ("video-only", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstXVImageSrcClass, video_only), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + + gst_xv_image_src_signals[SIGNAL_SELECTION_NOTIFY] = + g_signal_new ("selection-notify", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstXVImageSrcClass, selection_notify), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); +} + +static gboolean gst_xv_image_src_get_frame_size(int fourcc, int width, int height, unsigned int *outsize) +{ + switch (fourcc) { +/* case GST_MAKE_FOURCC('I','4','2','0'): // V4L2_PIX_FMT_YUV420 + *outsize = GST_ROUND_UP_4 (width) * GST_ROUND_UP_2 (height); + *outsize += 2 * ((GST_ROUND_UP_8 (width) / 2) * (GST_ROUND_UP_2 (height) / 2)); + break;*/ + case GST_MAKE_FOURCC('N','V','1','2'): // V4L2_PIX_FMT_NV12 non-linear + GST_INFO("NV12"); + *outsize = GST_ROUND_UP_4 (width) * GST_ROUND_UP_2 (height); + *outsize += (GST_ROUND_UP_4 (width) * height) / 2; + break; + case GST_MAKE_FOURCC('S','N','1','2'): // V4L2_PIX_FMT_NV12 non-linear + GST_INFO("SN12"); + *outsize = GST_ROUND_UP_4 (width) * GST_ROUND_UP_2 (height); + *outsize += (GST_ROUND_UP_4 (width) * height) / 2; + break; + case GST_MAKE_FOURCC('S','T','1','2'): // V4L2_PIX_FMT_NV12 tiled non-linear + GST_INFO("ST12"); + *outsize = GST_ROUND_UP_4 (width) * GST_ROUND_UP_2 (height); + *outsize += (GST_ROUND_UP_4 (width) * height) / 2; + break; + case GST_MAKE_FOURCC('R','G','B','4'): + /* jpeg size can't be calculated here. */ + *outsize = width * height * 4; + break; + default: + /* unkown format!! */ + *outsize = 0; + return FALSE; + } + return TRUE; +} + +static gboolean +gst_xv_image_src_parse_caps (const GstCaps * caps, guint32 *fourcc, + gint * width, gint * height, gint * rate_numerator, gint * rate_denominator, unsigned int *framesize) +{ + const GstStructure *structure; + GstPadLinkReturn ret = TRUE; + const GValue *framerate; + const char *media_type = NULL; + GST_DEBUG ("parsing caps"); + if (gst_caps_get_size (caps) < 1) return FALSE; + GST_INFO("xvimagesrc src caps:%"GST_PTR_FORMAT, caps); + structure = gst_caps_get_structure (caps, 0); + ret = gst_structure_get_int (structure, "width", width); + if(!ret) { + GST_ERROR ("xvimagesrc width not specified in caps"); + goto error; + } + ret = gst_structure_get_int (structure, "height", height); + if(!ret) { + GST_ERROR ("xvimagesrc height not specified in caps"); + goto error; + } + media_type = gst_structure_get_name (structure); + if(media_type == NULL) { + GST_ERROR ("xvimagesrc media type not specified in caps"); + goto error; + } + framerate = gst_structure_get_value (structure, "framerate"); + if (framerate) { + *rate_numerator = gst_value_get_fraction_numerator (framerate); + *rate_denominator = gst_value_get_fraction_denominator (framerate); + } else { + GST_ERROR ("xvimagesrc frametype not specified in caps"); + goto error; + } + if (g_strcmp0 (media_type, "video/x-raw-rgb") == 0) { + gst_xv_image_src_get_frame_size(FOURCC_RGB32, *width, *height, framesize); + *fourcc = FOURCC_RGB32; + GST_DEBUG ("Caps set to RGB32"); + } else if(g_strcmp0 (media_type, "video/x-raw-yuv") == 0) { + + GST_INFO ("media_type is video/x-raw-yuv"); + guint32 format = FOURCC_SN12; + ret = gst_structure_get_fourcc (structure, "format", &format); + if (!ret) GST_DEBUG ("xvimagesrc format not specified in caps, SN12 selected as default"); + ret = gst_xv_image_src_get_frame_size(format, *width, *height, framesize); + if(!ret) { + GST_ERROR ("xvimagesrc unsupported format type specified in caps"); + goto error; + } + *fourcc = format; + } + return TRUE; +/* ERRORS */ +error: + return FALSE; +} + +static gboolean gst_xv_image_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps) +{ + gboolean res = TRUE; + gint width, height, rate_denominator, rate_numerator; + GstXVImageSrc *src; + src = GST_XV_IMAGE_SRC (bsrc); + res = gst_xv_image_src_parse_caps (caps, &src->format_id, &width, &height, + &rate_numerator, &rate_denominator, &src->framesize); + if (res) { + /* looks ok here */ + src->width = width; + src->height = height; + src->rate_numerator = rate_numerator; + src->rate_denominator = rate_denominator; + GST_DEBUG_OBJECT (src, "size %dx%d, %d/%d fps", + src->width, src->height, + src->rate_numerator, src->rate_denominator); + if(src->rate_numerator) + { + src->sleep_base_time = (long) (((int)(1000/src->rate_numerator))*1000); + src->sleep_limit_time = (long) (-1*src->sleep_base_time); + } + } + xvimagesrc_thread_start(src); + return res; +} +static void +gst_xv_image_src_reset (GstXVImageSrc * src) +{ +} + +static void +gst_xv_image_src_init (GstXVImageSrc * src, GstXVImageSrcClass * g_class) +{ + src->format_id = 0; + src->running_time = GST_CLOCK_TIME_NONE; + src->frame_duration = GST_CLOCK_TIME_NONE; + src->virtual = NULL; + src->bo = NULL; + src->dri2_buffers = NULL; + src->queue_lock = g_mutex_new (); + src->queue = g_queue_new (); + src->queue_cond = g_cond_new (); + src->cond_lock = g_mutex_new (); + src->buffer_cond = g_cond_new (); + src->buffer_cond_lock = g_mutex_new (); + src->pause_cond = g_cond_new (); + src->pause_cond_lock = g_mutex_new (); + src->pause_resp = g_cond_new (); + src->pause_resp_lock = g_mutex_new (); + src->dpy_lock = g_mutex_new (); + src->pause_cond_var = FALSE; + src->drm_fd = -1; + src->current_data_type = VIDEO_TYPE_VIDEO_WITH_UI; + src->new_data_type = VIDEO_TYPE_VIDEO_WITH_UI; + src->get_image_overtime = 0; + src->get_image_overtime_cnt = 0; + src->gemname_cnt = 0; + src->tz_enable = 0; + src->sleep_base_time = 0; + src->sleep_limit_time = 0; + src->switching_to_udp = FALSE; + drm_init(src); + gst_base_src_set_live (GST_BASE_SRC (src), TRUE); +} + +static void +gst_xv_image_src_finalize (GObject * gobject) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (gobject); + GST_DEBUG_OBJECT (src, "finalize"); + g_mutex_free (src->queue_lock); + drm_finalize(src); + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} +static void drm_init(GstXVImageSrc *src) +{ + Display *dpy; + int eventBase, errorBase; + int dri2Major, dri2Minor; + char *driverName, *deviceName; + struct drm_auth auth_arg = {0}; + + src->drm_fd = -1; + dpy = XOpenDisplay(0); + + /* DRI2 */ + if (!DRI2QueryExtension(dpy, &eventBase, &errorBase)) { + GST_ERROR("DRI2QueryExtension !!"); + return; + } + if (!DRI2QueryVersion(dpy, &dri2Major, &dri2Minor)) { + GST_ERROR("DRI2QueryVersion !!"); + return; + } + if (!DRI2Connect(dpy, RootWindow(dpy, DefaultScreen(dpy)), &driverName, &deviceName)) { + GST_ERROR("DRI2Connect !!"); + return; + } + GST_INFO("Open drm device : %s", deviceName); + + /* get the drm_fd though opening the deviceName */ + src->drm_fd = open(deviceName, O_RDWR); + if (src->drm_fd < 0) { + GST_ERROR("cannot open drm device (%s)", deviceName); + return; + } + + /* get magic from drm to authentication */ + if (ioctl(src->drm_fd, DRM_IOCTL_GET_MAGIC, &auth_arg)) { + GST_ERROR("cannot get drm auth magic"); + close(src->drm_fd); + src->drm_fd = -1; + return; + } + if (!DRI2Authenticate(dpy, RootWindow(dpy, DefaultScreen(dpy)), auth_arg.magic)) { + GST_ERROR("cannot get drm authentication from X"); + close(src->drm_fd); + src->drm_fd = -1; + return; + } +} +static void drm_finalize(GstXVImageSrc *src) +{ + if (src->drm_fd >= 0) { + close(src->drm_fd); + src->drm_fd = -1; + } +} + +static gint32 drm_convert_gem_to_fd(int *gemname_cnt, int drm_fd, unsigned int name, void *data, void **virtual_address) +{ + g_return_val_if_fail((data != NULL),0); + int count=0; + gint32 fd = 0; + count = *gemname_cnt; + GST_DEBUG("gamname_cnt = %d", count); + GST_DEBUG("name = %u", name); + + + GEM_MMAP *xv_gem_mmap = NULL; + xv_gem_mmap = (GEM_MMAP *) data; + if(count >=GEM_NAME_MAX) + goto PASS; + + if (count < GEM_NAME_MAX ) { + int i =0; + for ( i =0 ; i < GEM_NAME_MAX ; i++) { + if (name == xv_gem_mmap->name[i]) + goto PASS; + } + + struct drm_prime_handle prime; + struct drm_gem_open gem_open; + struct drm_exynos_gem_mmap gem_mmap; //for virtual address + + memset (&gem_open, 0, sizeof (struct drm_gem_open)); + gem_open.name = name; + if (ioctl(drm_fd, DRM_IOCTL_GEM_OPEN, &gem_open)) { + GST_ERROR("Gem Open failed"); + return 0; + } + memset (&prime, 0, sizeof (struct drm_prime_handle)); + prime.handle = gem_open.handle; + prime.flags = DRM_CLOEXEC; + /*get gem_open handle*/ + xv_gem_mmap->handle[count] = gem_open.handle; + GST_DEBUG("gem_open.handle =%d, xv_gem_mmap->handle[count]=%d", gem_open.handle, xv_gem_mmap->handle[count]); + /*get virtual address */ + /*set name*/ + xv_gem_mmap->name[count] = name; + memset (&gem_mmap, 0, sizeof (struct drm_exynos_gem_mmap)); + gem_mmap.handle = prime.handle; + gem_mmap.size = gem_open.size; + /*set size*/ + xv_gem_mmap->buffer_size[count] = gem_mmap.size; + if (drmIoctl(drm_fd, DRM_IOCTL_EXYNOS_GEM_MMAP, &gem_mmap) !=0) { + GST_ERROR("Gem mmap failed [handle %d, size %d]", gem_mmap.handle, gem_mmap.size); + return 0; + } + /*set virtual address*/ + xv_gem_mmap->address[count] = (void *)(gem_mmap.mapped); + GST_DEBUG ("%d - Virtual address[%d] = %p size=%d ", name, count, xv_gem_mmap->address[count], xv_gem_mmap->buffer_size[count] ); + + /*get fd*/ + if (ioctl(drm_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime) < 0) { + GST_ERROR("Gem Handle to Fd failed"); + return 0; + } + xv_gem_mmap->fd[count] = prime.fd; + GST_DEBUG("fd = %d", xv_gem_mmap->fd[count]); + } + + if (count < GEM_NAME_MAX) { + count ++; + *gemname_cnt = count; + } + +PASS: + + if (name == xv_gem_mmap->name[0]) { + *virtual_address = xv_gem_mmap->address[0]; + fd = xv_gem_mmap->fd[0]; + } else if (name == xv_gem_mmap->name[1]) { + *virtual_address = xv_gem_mmap->address[1]; + fd = xv_gem_mmap->fd[1]; + } else if (name == xv_gem_mmap->name[2]) { + *virtual_address = xv_gem_mmap->address[2]; + fd = xv_gem_mmap->fd[2]; + } else if ( name == xv_gem_mmap->name[3]) { + *virtual_address = xv_gem_mmap->address[3]; + fd = xv_gem_mmap->fd[3]; + } else if (name == xv_gem_mmap->name[4]) { + *virtual_address = xv_gem_mmap->address[4]; + fd = xv_gem_mmap->fd[4]; + } else if ( name == xv_gem_mmap->name[5]) { + *virtual_address = xv_gem_mmap->address[5]; + fd = xv_gem_mmap->fd[5]; + } else if (name == xv_gem_mmap->name[6]) { + *virtual_address = xv_gem_mmap->address[6]; + fd = xv_gem_mmap->fd[6]; + } else if (name == xv_gem_mmap->name[7]) { + *virtual_address = xv_gem_mmap->address[7]; + fd = xv_gem_mmap->fd[7]; + } else if ( name == xv_gem_mmap->name[8]) { + *virtual_address = xv_gem_mmap->address[8]; + fd = xv_gem_mmap->fd[8]; + } else if (name == xv_gem_mmap->name[9]) { + *virtual_address = xv_gem_mmap->address[9]; + fd = xv_gem_mmap->fd[9]; + } + + GST_DEBUG("virtual_address = %p fd = %d", *virtual_address, fd); + return fd; +} + +static void +gst_xv_image_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (object); + GST_INFO ("set property function %x", src); + switch (prop_id) { + case PROP_DISPLAY_ROTATE: + src->display_rotate = g_value_get_uint64 (value); + GST_DEBUG("display_rotate [%d]", src->display_rotate); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + return; +} + +static void +gst_xv_image_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (object); + GST_INFO ("get property function %x", src); + switch (prop_id) { + case PROP_DISPLAY_ROTATE: + g_value_set_uint64 (value, src->display_rotate); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static inline GstClockTime xvimagesrc_clock (GstXVImageSrc *src) +{ + struct timeval tv; + gettimeofday (&tv, NULL); + if(!src->base_time) { + src->base_time = GST_TIMEVAL_TO_TIME(tv); + } + return (GstClockTime)(GST_TIMEVAL_TO_TIME(tv) - src->base_time); +} + +static gboolean gst_xv_image_src_get_timeinfo2(GstXVImageSrc *src, GstClockTime *time, GstClockTime *dur) +{ + int fps_nu = 0; + int fps_de = 0; + GstClockTime timestamp = GST_CLOCK_TIME_NONE; + GstClockTime duration = GST_CLOCK_TIME_NONE; + static GstClockTime prev_timestamp = 0; + + GstClock *clock; + if (!src) { + GST_WARNING("Invalid pointer [handle:%p]", src); + return FALSE; + } + clock = gst_element_get_clock (GST_ELEMENT (src)); + if (clock) { + timestamp = gst_clock_get_time (clock); + if(!gst_element_get_base_time (GST_ELEMENT(src))) + timestamp = GST_CLOCK_TIME_NONE; + else timestamp -= gst_element_get_base_time (GST_ELEMENT (src)); + gst_object_unref (clock); + } else { + /* not an error not to have a clock */ + timestamp = GST_CLOCK_TIME_NONE; + } + + if ((timestamp - prev_timestamp <= 30000000) || (timestamp-prev_timestamp >=36000000)) { + //GST_ERROR("Gap is below 30ms or over 36ms!!"); + } + + /* if we have a framerate adjust timestamp for frame latency */ + if ((int)((float)src->rate_numerator / (float)src->rate_denominator) <= 0) { + /*if fps is zero, auto fps mode*/ + fps_nu = 0; + fps_de = 1; + } else { + fps_nu = 1; + fps_de = (int)((float)src->rate_numerator / (float)src->rate_denominator); + } + if (fps_nu > 0 && fps_de > 0) { + GstClockTime latency; + latency = gst_util_uint64_scale_int(GST_SECOND, fps_nu, fps_de); + duration = latency; + } + timestamp+=src->initial_audio_latency; + *time = timestamp; + *dur = duration; + prev_timestamp = timestamp; + return TRUE; +} + +static gboolean gst_xv_image_src_get_timeinfo(GstXVImageSrc *src, GstBuffer *buffer) +{ + int fps_nu = 0; + int fps_de = 0; + GstClockTime timestamp = GST_CLOCK_TIME_NONE; + GstClockTime duration = GST_CLOCK_TIME_NONE; + GstClock *clock; + if (!src || !buffer) { + GST_WARNING("Invalid pointer [handle:%p, buffer:%p]", src, buffer); + return FALSE; + } + clock = gst_element_get_clock (GST_ELEMENT (src)); + if (clock) { + timestamp = gst_clock_get_time (clock); + timestamp -= gst_element_get_base_time (GST_ELEMENT (src)); + gst_object_unref (clock); + } else { + /* not an error not to have a clock */ + timestamp = GST_CLOCK_TIME_NONE; + } + + //timestamp = xvimagesrc_clock(src); + /* if we have a framerate adjust timestamp for frame latency */ + if ((int)((float)src->rate_numerator / (float)src->rate_denominator) <= 0) { + /*if fps is zero, auto fps mode*/ + fps_nu = 0; + fps_de = 1; + } else { + fps_nu = 1; + fps_de = (int)((float)src->rate_numerator / (float)src->rate_denominator); + } + if (fps_nu > 0 && fps_de > 0) { + GstClockTime latency; + latency = gst_util_uint64_scale_int(GST_SECOND, fps_nu, fps_de); + duration = latency; + } + GST_BUFFER_TIMESTAMP(buffer) = timestamp; + GST_BUFFER_DURATION(buffer) = duration; + return TRUE; +} +#ifdef COUNT_FRAMES +static int fps = 0; + +static gchar *get_current_system_time() +{ + gchar target[10]={0,}; + time_t t; + struct tm tm; + + t = time(NULL); + tzset(); + /*localtimer_r : available since libc 5.2.5*/ + if(localtime_r(&t, &tm) == NULL) { + return NULL; + } + snprintf(target, sizeof(target), "%02i:%02i:%02i", tm.tm_hour, tm.tm_min, tm.tm_sec); + return g_strdup(target); +} +#endif +static void gst_xv_get_image_sleep(void *asrc, long duration) +{ + GST_INFO("end_time duration=%d", duration); + if (duration < 0) return; + GstXVImageSrc *src = (GstXVImageSrc *)asrc; + g_return_if_fail(src != NULL); + long sleep_time = 0; + sleep_time = src->sleep_base_time - duration; + + if (sleep_time < 0) { + src->get_image_overtime_cnt ++; + src->get_image_overtime += sleep_time; + if (src->get_image_overtime_cnt > 2) + src->get_image_overtime = 0; + if (src->get_image_overtime <= src->sleep_limit_time) + src->get_image_overtime = 0; + //GST_WARNING("Over Time[%d] : %d", src->get_image_overtime_cnt, src->get_image_overtime); + } else if (sleep_time > 0) { + src->get_image_overtime_cnt = 0; + sleep_time = sleep_time + src->get_image_overtime; + + if (src->get_image_overtime < 0) { + //GST_WARNING("Over Time : %d, So sleep time : %d", src->get_image_overtime, sleep_time); + } + + src->get_image_overtime = (sleep_time < 0) ? sleep_time : 0; + + if (sleep_time >0) { + GST_INFO("end_time : sleep_time = %d", sleep_time); + usleep(sleep_time); + } + } +} +static GstFlowReturn +gst_xv_image_src_create (GstPushSrc * psrc, GstBuffer ** buffer) +{ + GST_INFO("gst_xv_image_src_create"); + GstXVImageSrc *src; + GstXvImageOutBuffer *outbuf = NULL; + src = GST_XV_IMAGE_SRC (psrc); + g_mutex_lock (src->queue_lock); + if(g_queue_is_empty (src->queue)) { + GST_INFO("g_queue_is_empty"); + g_mutex_unlock (src->queue_lock); + GST_INFO("g_cond_wait"); + g_cond_wait(src->queue_cond, src->cond_lock); + if(src->pause_cond_var) return GST_FLOW_WRONG_STATE; + g_mutex_lock (src->queue_lock); + + if(src->switching_to_udp == TRUE) { + GstStructure *structure; + structure = gst_structure_new("Switch_udp", "switch_to_udp", G_TYPE_BOOLEAN, NULL, NULL); + GstEvent *event = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, structure); + GstPad *srcpad = gst_element_get_static_pad(src, "src"); + gst_pad_push_event(srcpad, event); + GST_INFO("sending the event"); + src->switching_to_udp = FALSE; + } + + outbuf = (GstXvImageOutBuffer *)g_queue_pop_head(src->queue); + GST_INFO("g_queue_pop_head"); + g_mutex_unlock (src->queue_lock); + } + else { + GstXvImageOutBuffer *tempbuf = NULL; + if((tempbuf = (GstXvImageOutBuffer*)g_queue_pop_head(src->queue)) != NULL) + { + outbuf = tempbuf; // To reduce latency, skipping the old frames and submitting only latest frames + g_mutex_unlock (src->queue_lock); + } + GST_INFO("g_queue_pop_head end"); + } + if(outbuf == NULL) return GST_FLOW_ERROR; + GST_INFO("gem_name=%d, fd_name=%d, Time stamp of the buffer is %"GST_TIME_FORMAT, outbuf->YBuf, outbuf->fd_name, GST_TIME_ARGS(GST_BUFFER_TIMESTAMP(outbuf))); + *buffer = GST_BUFFER_CAST(outbuf); +#ifdef COUNT_FRAMES + gchar *current_time = NULL; + current_time = get_current_system_time(); + if (strncmp(current_time, old_time, 10) == 0) { + fps ++; + strncpy(old_time, current_time, 10); + } else { + g_printf("xvimagesrc %s - fps : %d\n", old_time, fps); + fps = 1; + strncpy(old_time, current_time, 10); + } +#endif + GST_INFO("return ok"); + return GST_FLOW_OK; +} + +static gboolean xvimagesrc_thread_start(GstXVImageSrc *src) +{ + GError *error; + if(!src->updates_thread) src->updates_thread = g_thread_create ((GThreadFunc) gst_xv_image_src_update_thread, src, TRUE, &error); + else GST_LOG_OBJECT (src, "The thread function already running"); + return TRUE; +} + +static gboolean +gst_xv_image_src_start (GstBaseSrc * bsrc) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + if(src->format_id) xvimagesrc_thread_start(src); + return TRUE; +} + +gboolean signal_emit_func(void *asrc) +{ + GstXVImageSrc *src = (GstXVImageSrc *)asrc; + + if (src->current_data_type == VIDEO_TYPE_UI) { + src->switching_to_udp = TRUE; + g_signal_emit (src, gst_xv_image_src_signals[SIGNAL_UI_ONLY] , 0, NULL); + } else if (src->current_data_type == VIDEO_TYPE_VIDEO_ONLY) { + g_signal_emit (src, gst_xv_image_src_signals[SIGNAL_VIDEO_ONLY] , 0, NULL); + } else if (src->current_data_type == VIDEO_TYPE_VIDEO_WITH_UI) { + g_signal_emit (src, gst_xv_image_src_signals[SIGNAL_VIDEO_WITH_UI] , 0, NULL); + } + + return FALSE; +} + +gboolean signal_selection_emit_func(void *asrc) +{ + GstXVImageSrc *src = (GstXVImageSrc *)asrc; + + g_signal_emit (src, gst_xv_image_src_signals[SIGNAL_SELECTION_NOTIFY] , 0, NULL); + + return FALSE; +} + +static int +gst_xvimagesrc_handle_xerror (Display * display, XErrorEvent * xevent) +{ + char error_msg[1024]; + + XGetErrorText (display, xevent->error_code, error_msg, 1024); + GST_DEBUG ("XError. error: %s", error_msg); + error_caught = TRUE; + return 0; +} + +//static gboolean first_frame=TRUE; +static int framecount=0; +static void* gst_xv_image_src_update_thread (void * asrc) +{ + GstXVImageSrc *src = (GstXVImageSrc *)asrc; + Atom atom_data_type = 0; + Atom atom_secure = 0, atom_capture=0, atom_display=0; + Atom atom_fps = 0; + g_return_val_if_fail((src != NULL),NULL); + int i=0; + struct drm_gem_close gem_close; + GEM_MMAP *xv_gem_mmap = NULL; + xv_gem_mmap = (GEM_MMAP *)malloc(sizeof(GEM_MMAP)); + g_return_val_if_fail((xv_gem_mmap != NULL),NULL); + + memset(xv_gem_mmap, 0, sizeof(GEM_MMAP)); + + GST_LOG_OBJECT (src, "The thread function start"); + { + int damage_err_base = 0; + Atom atom_format = 0; + src->dpy = XOpenDisplay (NULL); + src->p = port_get (src, src->format_id); + if (src->p < 0) goto finish; + /*src->width and src->height is set by caps info */ + GST_DEBUG (" width and height of caps : %dx%d ", src->width, src->height); + if (src->width == 0 || src->height == 0) goto finish; + GST_DEBUG ("gst_xv_image_src_update_thread pixmap_create !!"); + src->pixmap = pixmap_create (src, src->dpy, src->width, src->height); + GST_DEBUG ("gst_xv_image_src_update_thread pixmap_create !!"); + src->gc = XCreateGC (src->dpy, src->pixmap, 0, 0); + GST_DEBUG ("gst_xv_image_src_update_thread XCreateGC !!"); + src->bufmgr = bufmgr_get (src->dpy, src->pixmap); + if (!src->bufmgr) goto finish; + GST_DEBUG ("gst_xv_image_src_update_thread bufmgr_get !!"); + if (!XDamageQueryExtension(src->dpy, &src->damage_base, &damage_err_base)) goto finish; + GST_DEBUG ("gst_xv_image_src_update_thread XDamageQueryExtension !!"); + src->damage = XDamageCreate (src->dpy, src->pixmap, XDamageReportNonEmpty); + atom_format = XInternAtom (src->dpy, "_USER_WM_PORT_ATTRIBUTE_FORMAT", False); + atom_capture = XInternAtom (src->dpy, "_USER_WM_PORT_ATTRIBUTE_CAPTURE", False); + atom_display = XInternAtom (src->dpy, "_USER_WM_PORT_ATTRIBUTE_DISPLAY", False); + + /*get data type*/ + atom_data_type = XInternAtom (src->dpy, "_USER_WM_PORT_ATTRIBUTE_DATA_TYPE", False); + /* _USER_WM_PORT_ATTRIBUTE_STREAM_OFF is removed */ + //src->atom_stream_off = XInternAtom (src->dpy, "_USER_WM_PORT_ATTRIBUTE_STREAM_OFF", False); + + XvSetPortAttribute (src->dpy, src->p, atom_format, src->format_id); + XvSetPortAttribute (src->dpy, src->p, atom_capture, 2); + XvSetPortAttribute (src->dpy, src->p, atom_display, 1); + + XvSelectPortNotify (src->dpy, src->p, 1); + XvGetPortAttribute (src->dpy, src->p, atom_data_type, &(src->new_data_type)); + + + /* Set display type */ + Atom atom_select_display=0; + Atom atom_select_display_type=0; + Atom atom_encoding=0; + XTextProperty xtp; + const char *display_type = "WFD"; + + atom_select_display = XInternAtom(src->dpy, "SEL_EXT_DISPLAY", False); + atom_select_display_type = XInternAtom(src->dpy, "SEL_EXT_DISPLAY_TYPE", False); + atom_encoding = XInternAtom(src->dpy, "UTF8_STRING", False); + src->win = DefaultRootWindow(src->dpy); + + XSetSelectionOwner(src->dpy, atom_select_display, src->win, 0); + + xtp.value = (unsigned char *)display_type; + xtp.format = 8; + xtp.encoding = atom_encoding; + xtp.nitems = strlen(display_type); + XSetTextProperty(src->dpy, src->win, &xtp, atom_select_display_type); + } + + struct timeval start_time, end_time; + long duration; + long starttime, endtime; + GTimeVal timeout; + XEvent ev; + int eventcount = 0; + int refresh = 1; + void *virtual_address = NULL; + int (*handler) (Display *, XErrorEvent *); + GstXvImageOutBuffer *outbuf = NULL; + + GstClockTime ts_putstill = 0; + GstClockTime dur_putstill = 0; + + gboolean got_display_select_req = FALSE; + + while(!src->thread_return) { + if(src->pause_cond_var == TRUE) { + GST_WARNING("PAUSED in thread"); + g_cond_signal(src->pause_resp); + g_cond_wait(src->pause_cond,src->pause_cond_lock); + } + + if (src->thread_return) { + GST_WARNING("Thread return"); + break; + } + + if (refresh == 1) { + duration = 0; + starttime =0; + endtime = 0; + start_time.tv_sec = 0; + start_time.tv_usec = 0; + end_time.tv_sec = 0; + end_time.tv_usec = 0; + gettimeofday(&start_time, NULL); + refresh = 0; + } + + eventcount = 0; + virtual_address = NULL; + duration = 0; + outbuf = NULL; + g_mutex_lock (src->dpy_lock); + GST_DEBUG("[XCALL] call XSync"); + XSync(src->dpy, 0); + GST_INFO ("gst_xv_image_src_update_thread XSync@@ !!"); + g_mutex_unlock (src->dpy_lock); + error_caught = FALSE; + //GST_DEBUG ("gst_xv_image_src_update_thread XSetErrorHandler in !!"); + handler = XSetErrorHandler (gst_xvimagesrc_handle_xerror); + //GST_INFO ("gst_xv_image_src_update_thread XSetErrorHandler !!"); + //GST_INFO ("gst_xv_image_src_update_thread XvPutStill in !!"); + g_mutex_lock (src->dpy_lock); + GST_DEBUG("[XCALL] call XvPutStill"); + XvPutStill (src->dpy, src->p, src->pixmap, src->gc, 0, 0, src->width, src->height, 0, 0, src->width, src->height); + + gst_xv_image_src_get_timeinfo2(src, &ts_putstill, &dur_putstill); + + //GST_INFO ("gst_xv_image_src_update_thread XvPutStill !!"); + GST_DEBUG("[XCALL] call 2nd XSync"); + XSync (src->dpy, 0); + GST_DEBUG("[XCALL] call 2nd XSync done"); + g_mutex_unlock (src->dpy_lock); + if (error_caught) { + GST_ERROR("gst_xv_image_src_update_thread error_caught is TRUE, X is out of buffers"); + error_caught = FALSE; + XSetErrorHandler(handler); + g_get_current_time(&timeout); + g_time_val_add(&timeout, BUFFER_COND_WAIT_TIMEOUT); + if(!g_cond_timed_wait(src->buffer_cond, src->buffer_cond_lock, &timeout)) { + GST_ERROR("skip wating"); + } else { + GST_ERROR("Signal received"); + } + continue; + } + /*reset error handler*/ + error_caught = FALSE; + XSetErrorHandler(handler); + + //GST_INFO ("gst_xv_image_src_update_thread XSync !!"); +next_event: + g_mutex_lock (src->dpy_lock); + //GST_INFO("XNextEvent in"); + XNextEvent (src->dpy, &ev); /* wating for x event */ + //GST_INFO("XNextEvent out"); + g_mutex_unlock (src->dpy_lock); + //GST_INFO ("gst_xv_image_src_update_thread XNextEvent !!"); + if (ev.type == (src->damage_base + XDamageNotify)) { + XDamageNotifyEvent *damage_ev = (XDamageNotifyEvent *)&ev; + GST_INFO ("gst_xv_image_src_update_thread XDamageNotifyEvent"); + g_mutex_lock (src->dpy_lock); + if (damage_ev->drawable == src->pixmap) { + pixmap_update (src, src->dpy, src->bufmgr, src->pixmap, + damage_ev->area.x, + damage_ev->area.y, + damage_ev->area.width, + damage_ev->area.height); + GST_INFO("gst_xv_image_src_update_thread pixmap_update"); + } + XDamageSubtract (src->dpy, src->damage, None, None ); + g_mutex_unlock (src->dpy_lock); + GST_INFO ("gst_xv_image_src_update_thread XDamageSubtract"); + } + /* Added to handle display selection notify */ + else if (ev.type == SelectionClear) { + XSelectionEvent *selection_ev = (XSelectionEvent*)&ev; + + GST_ERROR("ev.type : %d SelectionClear", ev.type); + //g_timeout_add(1, signal_selection_emit_func, src); + } + else if (ev.type == SelectionRequest) { + XSelectionRequestEvent *selection_ev = (XSelectionRequestEvent*)&ev; + + GST_ERROR("ev.type : %d SelectionRequest", ev.type); + src->requestor = selection_ev->requestor; + src->selection = selection_ev->selection; + src->target = selection_ev->target; + src->property = selection_ev->property; + + got_display_select_req = TRUE; + g_timeout_add(1, signal_selection_emit_func, src); + } + else if (ev.type == SelectionNotify) { + XSelectionEvent *selection_ev = (XSelectionEvent*)&ev; + + GST_ERROR("ev.type : %d SelectionNotify", ev.type); + //g_timeout_add(1, signal_selection_emit_func, src); + } + else if (ev.type == (src->evt_base + XvPortNotify)) { + XvPortNotifyEvent *notify_ev = (XvPortNotifyEvent*)&ev; + if (notify_ev->attribute == atom_secure) { + GST_WARNING ("secure attr changed : %s \n", ((int)notify_ev->value)?"Secure":"Normal"); + src->tz_enable = (int)notify_ev->value; + GST_ERROR("src->tz_enable = %d", src->tz_enable); + } + else + if (notify_ev->attribute == atom_data_type) { + /* got a port notify, data_type */ + src->new_data_type = (int)notify_ev->value; + if (src->current_data_type != src->new_data_type) { + src->current_data_type = src->new_data_type; + //GST_WARNING("current_data_type : %s \n", (src->current_data_type)?"Video":"UI+Video"); + if (src->current_data_type == VIDEO_TYPE_UI) { + GST_WARNING("current_data_type : UI\n"); + } else if (src->current_data_type == VIDEO_TYPE_VIDEO_WITH_UI) { + GST_WARNING("current_data_type : Video+UI\n"); + } else if (src->current_data_type == VIDEO_TYPE_VIDEO_ONLY) { + GST_WARNING("current_data_type : VideoOnly\n"); + } + + g_timeout_add(1, signal_emit_func, src); + } + } + goto next_event; + } + + if(!src->virtual) continue; + if(src->format_id == FOURCC_RGB32) { + outbuf = (GstXvImageOutBuffer*)gst_buffer_new_and_alloc (src->framesize); + gst_buffer_set_caps (GST_BUFFER_CAST(outbuf), GST_PAD_CAPS (GST_BASE_SRC_PAD (src))); + memcpy(GST_BUFFER_DATA (outbuf), src->virtual, src->framesize); + } else if ((src->format_id == FOURCC_SN12) || (src->format_id == FOURCC_NV12)) { + XV_DATA_PTR data = (XV_DATA_PTR)src->virtual; + int error = XV_VALIDATE_DATA (data); + outbuf = gst_xv_image_out_buffer_new(src); + if(!outbuf) + { + GST_ERROR("Out of memory"); + continue; + } + gst_buffer_set_caps (GST_BUFFER_CAST(outbuf), GST_PAD_CAPS (GST_BASE_SRC_PAD (src))); + if (error == XV_HEADER_ERROR) + GST_ERROR ("XV_HEADER_ERROR\n"); + else if (error == XV_VERSION_MISMATCH) + GST_ERROR ("XV_VERSION_MISMATCH\n"); + else + { + SCMN_IMGB *psimgb = NULL; + psimgb = (SCMN_IMGB *)malloc(sizeof(SCMN_IMGB)); + if (psimgb == NULL) { + GST_ERROR_OBJECT(src, "failed to alloc SCMN_IMGB"); + return NULL; + } + memset(psimgb, 0x00, sizeof(SCMN_IMGB)); + if (data->BufType == XV_BUF_TYPE_LEGACY) { + psimgb->p[0] = (void *)data->YBuf; + psimgb->p[1] = (void *)data->CbBuf; + psimgb->buf_share_method = BUF_SHARE_METHOD_PADDR; + psimgb->a[0] = NULL; + psimgb->a[1] = NULL; + } else if (data->BufType == XV_BUF_TYPE_DMABUF) { + psimgb->fd[0] = drm_convert_gem_to_fd(&src->gemname_cnt, src->drm_fd, (void *)data->YBuf, xv_gem_mmap, &virtual_address); + if(!virtual_address) { + free(psimgb); + psimgb = NULL; + continue; + } + psimgb->a[0] = virtual_address; + GST_DEBUG("YBuf gem to fd[0]=%d virtual_address = %p", psimgb->fd[0], psimgb->a[0]); + psimgb->fd[1] = drm_convert_gem_to_fd(&src->gemname_cnt, src->drm_fd, (void *)data->CbBuf, xv_gem_mmap, &virtual_address); + if(!virtual_address) { + free(psimgb); + psimgb = NULL; + continue; + } + psimgb->a[1] = virtual_address; + GST_DEBUG("CbBuf gem to fd[1]=%d virtual_address = %p", psimgb->fd[1], psimgb->a[1]); + psimgb->buf_share_method = BUF_SHARE_METHOD_FD; + psimgb->w[1] = src->width; + psimgb->h[1] = src->height >> 1; + psimgb->s[1] = GST_ROUND_UP_16(psimgb->w[1]); + psimgb->e[1] = GST_ROUND_UP_16(psimgb->h[1]); + psimgb->cs = SCMN_CS_NV12; + psimgb->tz_enable = 0; + } + psimgb->w[0] = src->width; + psimgb->h[0] = src->height; + psimgb->s[0] = GST_ROUND_UP_16(psimgb->w[0]); + psimgb->e[0] = GST_ROUND_UP_16(psimgb->h[0]); + outbuf->fd_name = psimgb->fd[0]; + GST_BUFFER_MALLOCDATA(outbuf) = (unsigned char*)psimgb; + GST_BUFFER_DATA(outbuf) = src->virtual; + outbuf->YBuf = data->YBuf; + outbuf->fd_name = psimgb->fd[0]; +#ifdef DEBUG_BUFFER + for ( i=0; i<5 ; i++) { + if(value[value_count] == outbuf->YBuf ) { + GST_ERROR("ERROR: value[%d](%d)==YBUf(%d)", value_count, value[value_count], outbuf->YBuf); + } + } + value[value_count] = outbuf->YBuf ; + GST_ERROR("value[%d]=%d", value_count, value[value_count]); + if( value_count < 4){ + value_count ++; + } else { + value_count = 0; + } +#endif + +#ifdef _MAKE_DUMP + if (f_idx < 100) { + if (src->running_time > 14000000000 && src->running_time < 20000000000) { + GST_ERROR("Mem copy"); + if (virtual_address == NULL) { + GST_ERROR("Mem virtual is NULL[%d]", f_idx); + } else { + memcpy(g_dump_frame[f_idx++], virtual_address, YUV_720_FRAME_SIZE); + //memcpy(g_dump_frame[f_idx++], virtual_address, YUV_VGA_FRAME_SIZE); + GST_ERROR("Mem copy done[%d]", f_idx); + } + } + } else { + if (f_done == 0) { + GST_ERROR("File DUMP!!"); + FILE *fp = NULL; + fp = fopen("/opt/usr/media/Videos/frame.yuv", "a"); + int i = 0; + for (i = 0; i < 100; i++) { + fwrite(g_dump_frame[i], YUV_720_FRAME_SIZE, 1, fp); + //fwrite(g_dump_frame[i], YUV_VGA_FRAME_SIZE, 1, fp); + } + fclose(fp); + f_done = 1; + GST_ERROR("File DUMP done!!"); + } + } +#endif + } + } + if(!outbuf) continue; + GST_BUFFER_SIZE (outbuf) = src->framesize; + //gst_xv_image_src_get_timeinfo(src, GST_BUFFER_CAST(outbuf)); + GST_BUFFER_TIMESTAMP(outbuf) = ts_putstill; + GST_BUFFER_DURATION(outbuf) = dur_putstill; + src->running_time = GST_BUFFER_TIMESTAMP(outbuf); + src->frame_duration = GST_BUFFER_DURATION(outbuf); + //first_frame = FALSE; + g_mutex_lock (src->queue_lock); + g_queue_push_tail(src->queue, outbuf); + //GST_INFO("g_queue_push_tail"); + g_mutex_unlock (src->queue_lock); + g_cond_signal(src->queue_cond); + //GST_INFO("g_cond_signal"); + + update_done: + if (src->virtual) tbm_bo_unmap(src->bo); + src->virtual = NULL; + GST_INFO("g_cond_signal"); + if (src->bo) tbm_bo_unref(src->bo); + src->bo = NULL; + if (src->dri2_buffers) free(src->dri2_buffers); + src->dri2_buffers = NULL; + gettimeofday(&end_time, NULL); + starttime = start_time.tv_usec; + endtime = end_time.tv_usec; + //GST_INFO("star_time: %d, end_time:%d", starttime, endtime); + if (endtime > starttime) { + //GST_INFO("end_time > start_time"); + duration = endtime - starttime; + } else { + //GST_INFO("end_time.tv_usec < start_time.tv_usec"); + endtime=endtime+1000000; + //GST_INFO("end_time =%d", endtime); + duration = endtime -starttime; + } + + GST_INFO("end_time duration = %d", duration); + //if (src->sleep_base_time > duration) + gst_xv_get_image_sleep (src, duration); + + refresh = 1; + GST_INFO("gst_xv_image_src_update_thread cleanup !!"); + } + GST_WARNING("gst_xv_image_src_update_thread loop ended"); + + for ( i=0 ; i < GEM_NAME_MAX ; i++) { + /*gem munmap*/ + if (xv_gem_mmap->address[i]) { + if (-1 == munmap(xv_gem_mmap->address[i],xv_gem_mmap->buffer_size[i])) { + GST_ERROR ("munmap failed"); + return NULL; + } + } + if (xv_gem_mmap->handle[i]) { + gem_close.handle = xv_gem_mmap->handle[i]; + if (ioctl(src->drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close)) { + GST_ERROR("Gem Close failed"); + } + } + if (xv_gem_mmap->fd[i]) { + close(xv_gem_mmap->fd[i]); + } + } + if (xv_gem_mmap) { + free(xv_gem_mmap); + xv_gem_mmap = NULL; + } + GST_LOG_OBJECT (src, "The thread function cleanup"); + + XvStopVideo(src->dpy, src->p, src->pixmap); + + if (src->bufmgr) { + tbm_bufmgr_deinit (src->bufmgr); + src->bufmgr = NULL; + } + if (src->p > 0) { + XvUngrabPort (src->dpy, src->p, 0); + src->p = 0; + } + + if (got_display_select_req) { + /* Notify miracast-destroyed to X */ + GST_WARNING_OBJECT (src, "There is display selection request"); + XEvent xev; + XSelectionEvent xnotify; + + xnotify.type = SelectionNotify; + xnotify.display = src->dpy; + xnotify.requestor = src->requestor; + xnotify.selection = src->selection; + xnotify.target = src->target; + xnotify.property = src->property; + xnotify.time = 0; + xnotify.send_event = True; + xnotify.serial = 0; + xev.xselection = xnotify; + if (XSendEvent(src->dpy, src->requestor, False, 0, &xev) < 0) { + GST_ERROR("XSendEvent failed!"); + } else { + GST_INFO("XSendEvent success!"); + } + } + + if (src->gc) { + XFreeGC (src->dpy, src->gc); + src->gc = NULL; + } + if (src->pixmap > 0) { + XFreePixmap (src->dpy, src->pixmap); + src->pixmap = 0; + } + if (src->dpy) { + XCloseDisplay (src->dpy); + src->dpy = NULL; + } + + GST_LOG_OBJECT (src, "The thread function stop"); + return NULL; +finish: + GST_LOG_OBJECT (src, "The thread function Error cleanup"); + + XvStopVideo(src->dpy, src->p, src->pixmap); + + if (src->bufmgr) tbm_bufmgr_deinit (src->bufmgr); + src->bufmgr = NULL; + if (src->p > 0) XvUngrabPort (src->dpy, src->p, 0); + src->p = 0; + if (src->gc) XFreeGC (src->dpy, src->gc); + src->gc = NULL; + if (src->pixmap > 0) XFreePixmap (src->dpy, src->pixmap); + src->pixmap = 0; + if (src->dpy) XCloseDisplay (src->dpy); + src->dpy = NULL; + GST_LOG_OBJECT (src, "The thread function Error stop"); + return NULL; +} + +static tbm_bufmgr bufmgr_get (Display *dpy, Pixmap pixmap) +{ + int screen; + int drm_fd; + tbm_bufmgr bufmgr; + int eventBase, errorBase; + int dri2Major, dri2Minor; + char *driverName = NULL, *deviceName = NULL; + drm_magic_t magic; + + screen = DefaultScreen(dpy); + if (!DRI2QueryExtension (dpy, &eventBase, &errorBase)) { + GST_ERROR ("!!Error : DRI2QueryExtension !!"); + return NULL; + } + if (!DRI2QueryVersion (dpy, &dri2Major, &dri2Minor)) { + GST_ERROR ("!!Error : DRI2QueryVersion !!"); + return NULL; + } + if (!DRI2Connect (dpy, RootWindow(dpy, screen), &driverName, &deviceName)) { + GST_ERROR ( "!!Error : DRI2Connect !!"); + if(driverName) Xfree(driverName); + if(deviceName) Xfree(deviceName); + return NULL; + } + + if(driverName) Xfree(driverName); + if(!deviceName) return NULL; + + GST_DEBUG("Open drm device : %s", deviceName); + // get the drm_fd though opening the deviceName + drm_fd = open (deviceName, O_RDWR); + if (drm_fd < 0) { + GST_ERROR ("!!Error : cannot open drm device (%s)", deviceName); + if(deviceName) Xfree(deviceName); + return NULL; + } + if(deviceName) Xfree(deviceName); + /* get the drm magic */ + drmGetMagic(drm_fd, &magic); + fprintf(stderr, ">>> drm magic=%d \n", magic); + if (!DRI2Authenticate(dpy, RootWindow(dpy, screen), magic)) + { + fprintf(stderr, "!!Error : DRI2Authenticate !!\n"); + close (drm_fd); + return NULL; + } + // drm slp buffer manager init + bufmgr = tbm_bufmgr_init (drm_fd); + if (!bufmgr) { + GST_ERROR ("!!Error : fail to init buffer manager "); + close (drm_fd); + return NULL; + } + DRI2CreateDrawable (dpy, pixmap); + close (drm_fd); + return bufmgr; +} + +static int port_get (GstXVImageSrc * src, unsigned int id) +{ + unsigned int ver, rev, req_base, err_base; + unsigned int adaptors; + XvAdaptorInfo *ai = NULL; + XvAttribute *at = NULL; + XvImageFormatValues *fo = NULL; + int attributes, formats; + int i, j, p; + + if (XvQueryExtension (src->dpy, &ver, &rev, &req_base, &src->evt_base, &err_base) != Success) return -1; + if (XvQueryAdaptors (src->dpy, DefaultRootWindow (src->dpy), &adaptors, &ai) != Success) return -1; + if (!ai) return -1; + for (i = 0; i < adaptors; i++) { + int support_format = False; + if (!(ai[i].type & XvInputMask) || !(ai[i].type & XvStillMask)) continue; + GST_LOG ("==========================================="); + GST_LOG (" name: %s" + " first port: %ld" + " ports: %ld", + ai[i].name, + ai[i].base_id, + ai[i].num_ports); + p = ai[i].base_id; + GST_LOG (" attribute list:"); + at = XvQueryPortAttributes (src->dpy, p, &attributes); + for (j = 0; j < attributes; j++) GST_LOG ("\t- name: %s\n" + "\t\t flags: %s%s\n" + "\t\t min_value: %i\n" + "\t\t max_value: %i\n", + at[j].name, + (at[j].flags & XvGettable) ? " get" : "", + (at[j].flags & XvSettable) ? " set" : "", + at[j].min_value, at[j].max_value); + if (at) XFree (at); + GST_LOG (" image format list:"); + fo = XvListImageFormats (src->dpy, p, &formats); + for (j = 0; j < formats; j++) { + GST_LOG ("\t- 0x%x (%4.4s) %s", fo[j].id, (char *)&fo[j].id, (fo[j].format == XvPacked) ? "packed" : "planar"); + if (fo[j].id == (int)id) support_format = True; + } + if (fo) XFree (fo); + if (!support_format) continue; + for (; p < ai[i].base_id + ai[i].num_ports; p++) { + if (XvGrabPort (src->dpy, p, 0) == Success) { + GST_LOG ("========================================"); + GST_DEBUG ("XvGrabPort success : %d", p); + GST_LOG ("========================================"); + XvFreeAdaptorInfo (ai); + return p; + } + } + } + XvFreeAdaptorInfo (ai); + return -1; +} + +static void pixmap_update (GstXVImageSrc * src, Display *dpy, tbm_bufmgr bufmgr, Pixmap pixmap, + int x, int y, int width, int height) +{ + unsigned int attachments[1]; + int dri2_count, dri2_out_count; + int dri2_width, dri2_height, dri2_stride; + int opt; + tbm_bo_handle temp_virtual; + attachments[0] = DRI2BufferFrontLeft; + dri2_count = 1; + GST_DEBUG ("DRI2GetBuffers"); + src->dri2_buffers = DRI2GetBuffers (dpy, pixmap, &dri2_width, &dri2_height, attachments, dri2_count, &dri2_out_count); + if (!src->dri2_buffers) { + GST_ERROR ("[Error] : fail to get buffers"); + goto update_done; + } + if (!src->dri2_buffers[0].name) { + GST_ERROR ("[Error] : a handle of the dri2 buffer is null "); + goto update_done; + } + GST_DEBUG ("tbm_bo_import"); + src->bo = tbm_bo_import(bufmgr, src->dri2_buffers[0].name); + if (!src->bo) { + GST_ERROR ("[Error] : cannot import bo (key:%d)", src->dri2_buffers[0].name); + goto update_done; + } + dri2_stride = src->dri2_buffers[0].pitch; + opt = TBM_OPTION_READ|TBM_OPTION_WRITE; + GST_DEBUG ("tbm_bo_map"); + temp_virtual = tbm_bo_map (src->bo, TBM_DEVICE_CPU, opt); + src->virtual = temp_virtual.ptr; + if (!src->virtual) { + GST_ERROR ("[Error] : fail to map "); + goto update_done; + } + return; +update_done: + if (src->virtual) tbm_bo_unmap(src->bo); + src->virtual = NULL; + if (src->bo) tbm_bo_unref(src->bo); + src->bo = NULL; + if (src->dri2_buffers) free(src->dri2_buffers); + src->dri2_buffers = NULL; + return; +} + +static Pixmap pixmap_create (GstXVImageSrc * src, Display *dpy, int width, int height) +{ + src->pixmap = XCreatePixmap(dpy, DefaultRootWindow(dpy), width, height, DefaultDepth(dpy, DefaultScreen(dpy))); + src->gc = XCreateGC (dpy, src->pixmap, 0, 0); + XSetForeground (dpy, src->gc, 0xFFFF0000); + XFillRectangle (dpy, src->pixmap, src->gc, 0, 0, width, height); + XSync(dpy, 0); + XFreeGC (dpy, src->gc); + return src->pixmap; +} + +static gboolean +gst_xv_image_src_stop (GstBaseSrc * bsrc) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + GST_WARNING ("stop()"); + + if(src->updates_thread) { + src->thread_return = TRUE; + g_cond_signal(src->pause_cond); + g_thread_join ( src->updates_thread); + src->updates_thread = NULL; + } + GST_WARNING ("stop end "); + return TRUE; +} + +/* Interrupt a blocking request. */ +static gboolean +gst_xv_image_src_unlock (GstBaseSrc * bsrc) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + GST_DEBUG_OBJECT (src, "unlock()"); + g_cond_signal(src->queue_cond); + return TRUE; +} + +/* Interrupt interrupt. */ +static gboolean +gst_xv_image_src_unlock_stop (GstBaseSrc * bsrc) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + GST_DEBUG_OBJECT (src, "unlock_stop()"); + return TRUE; +} + +static gboolean +gst_xv_image_src_get_size (GstBaseSrc * bsrc, guint64 * size) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + GST_INFO ("Get size %x", src); + return FALSE; +} + +static gboolean +gst_xv_image_src_is_seekable (GstBaseSrc * bsrc) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + GST_INFO ("Is seekable %x", src); + return FALSE; +} + +static gboolean +gst_xv_image_src_query (GstBaseSrc * bsrc, GstQuery * query) +{ + GstXVImageSrc *src = GST_XV_IMAGE_SRC (bsrc); + gboolean ret; + GST_INFO ("src query %x", src); + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_URI: + ret = TRUE; + break; + default: + ret = FALSE; + break; + } + + if (!ret) + ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); + + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (xvimagesrc_debug, "xvimagesrc", 0, "XServer display FB video capture Source"); + return gst_element_register (plugin, "xvimagesrc", GST_RANK_PRIMARY, GST_TYPE_XV_IMAGE_SRC); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "xvimage", + "XServer display video src", + plugin_init, VERSION, "LGPL", "Samsung Electronics Co", "http://www.samsung.com") diff --git a/xvimagesrc/src/gstxvimagesrc.h b/xvimagesrc/src/gstxvimagesrc.h new file mode 100755 index 0000000..25783c4 --- /dev/null +++ b/xvimagesrc/src/gstxvimagesrc.h @@ -0,0 +1,153 @@ +/* + * xvimagesrc + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Hyunil Park + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef __GST_XV_IMAGE_SRC_H__ +#define __GST_XV_IMAGE_SRC_H__ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include //libdri2-dev, libdrm-dev +#include + +#include "xv_types.h" + +#define C(b,m) (((b) >> (m)) & 0xFF) +#define B(c,s) ((((unsigned int)(c)) & 0xff) << (s)) +#define FOURCC(a,b,c,d) (B(d,24) | B(c,16) | B(b,8) | B(a,0)) +#define FOURCC_RGB32 FOURCC('R','G','B','4') +#define FOURCC_I420 FOURCC('I','4','2','0') +#define FOURCC_SN12 FOURCC('S','N','1','2') +#define FOURCC_NV12 FOURCC('N','V','1','2') +#define FOURCC_ST12 FOURCC('S','T','1','2') + +G_BEGIN_DECLS + +#define GST_TYPE_XV_IMAGE_SRC \ + (gst_xv_image_src_get_type()) +#define GST_XV_IMAGE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_XV_IMAGE_SRC,GstXVImageSrc)) +#define GST_XV_IMAGE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + GST_TYPE_XV_IMAGE_SRC,GstXVImageSrcClass)) +#define GST_IS_XV_IMAGE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_XV_IMAGE_SRC)) +#define GST_IS_XV_IMAGE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_XV_IMAGE_SRC)) + +typedef struct _GstXVImageSrc GstXVImageSrc; +typedef struct _GstXVImageSrcClass GstXVImageSrcClass; + +struct _GstXVImageSrc { + GstPushSrc element; + Display *dpy; + int p; + Pixmap pixmap; + GC gc; + unsigned int width; + unsigned int height; + unsigned int framesize; + guint32 format_id; + Damage damage; + int damage_base; + unsigned int evt_base; + tbm_bufmgr bufmgr; + void *virtual; + tbm_bo bo; + DRI2Buffer* dri2_buffers; + guint64 running_time; + guint64 base_time; + guint64 frame_duration; + Atom atom_stream_off; + gint rate_numerator; + gint rate_denominator; + GThread *updates_thread; + gboolean thread_return; + GQueue *queue; + GMutex *queue_lock; + GCond *queue_cond; + GMutex *cond_lock; + GCond *buffer_cond; + gboolean pause_cond_var; + GCond *pause_cond; + GMutex *pause_cond_lock; + GCond *pause_resp; + GMutex *pause_resp_lock; + GMutex *buffer_cond_lock; + GMutex *dpy_lock; + gint drm_fd; + gboolean is_secured_mode; + int current_data_type; + int new_data_type; + long get_image_overtime; + int get_image_overtime_cnt; + int gemname_cnt; + int tz_enable; + long sleep_base_time; + long sleep_limit_time; + gboolean switching_to_udp; + guint64 initial_audio_latency; + guint64 display_rotate; + + /* For display selection */ + Window win; + Atom requestor; + Atom selection; + Atom target; + Atom property; +}; + +struct _GstXVImageSrcClass { + GstPushSrcClass parent_class; + + /* signals */ + void (*ui_only) (void *data); + void (*video_with_ui) (void *data); + void (*video_only) (void *data); + void (*selection_notify) (void *data); +}; + +GType gst_xv_image_src_get_type (void); + +G_END_DECLS + +#endif /* __GST_XV_IMAGE_SRC_H__ */ + diff --git a/xvimagesrc/src/xv_types.h b/xvimagesrc/src/xv_types.h new file mode 100644 index 0000000..99fda51 --- /dev/null +++ b/xvimagesrc/src/xv_types.h @@ -0,0 +1,89 @@ +/************************************************************************** + +xserver-xorg-video-exynos + +Copyright 2010 - 2011 Samsung Electronics co., Ltd. All Rights Reserved. + +Contact: Boram Park + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +/* */ +/* File name : xv_types.h */ +/* Author : Boram Park (boram1288.park@samsung.com) */ +/* Protocol Version : 1.0.1 (Dec 16th 2009) */ +/* This file is for describing Xv APIs' buffer encoding method. */ +/* */ + +#ifndef __XV_TYPE_H__ +#define __XV_TYPE_H__ + +#define XV_DATA_HEADER 0xDEADCD01 +#define XV_DATA_VERSION 0x00010001 + +/* Return Values */ +#define XV_OK 0 +#define XV_HEADER_ERROR -1 +#define XV_VERSION_MISMATCH -2 + +#define XV_BUF_TYPE_DMABUF 0 +#define XV_BUF_TYPE_LEGACY 1 + +/* Data structure for XvPutImage / XvShmPutImage */ +typedef struct +{ + unsigned int _header; /* for internal use only */ + unsigned int _version; /* for internal use only */ + + unsigned int YBuf; + unsigned int CbBuf; + unsigned int CrBuf; + + unsigned int BufType; +} XV_DATA, * XV_DATA_PTR; + +static void +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif +XV_INIT_DATA (XV_DATA_PTR data) +{ + data->_header = XV_DATA_HEADER; + data->_version = XV_DATA_VERSION; +} + +static int +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif +XV_VALIDATE_DATA (XV_DATA_PTR data) +{ + if (data->_header != XV_DATA_HEADER) + return XV_HEADER_ERROR; + if (data->_version != XV_DATA_VERSION) + return XV_VERSION_MISMATCH; + return XV_OK; +} + +#endif +