diff --git a/ext/dash/AUTHORS b/ext/dash/AUTHORS new file mode 100644 index 0000000000..34578219c5 --- /dev/null +++ b/ext/dash/AUTHORS @@ -0,0 +1 @@ +Orange diff --git a/ext/dash/LICENSE b/ext/dash/LICENSE new file mode 100644 index 0000000000..79d009a2fb --- /dev/null +++ b/ext/dash/LICENSE @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/ext/dash/Makefile.am b/ext/dash/Makefile.am new file mode 100644 index 0000000000..b0f72e4d0f --- /dev/null +++ b/ext/dash/Makefile.am @@ -0,0 +1,24 @@ + +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) +libgstdashdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdashdemux_la_LIBTOOLFLAGS = --tag=disable-static + diff --git a/ext/dash/README b/ext/dash/README new file mode 100644 index 0000000000..3c782fc02c --- /dev/null +++ b/ext/dash/README @@ -0,0 +1,35 @@ +dashdemux + +GStreamer plugin allowing the playback of MPEG DASH streams. + +The plugin is based on some basic objects defined in the GStreamer HLS +Demux plugin from the gst-plugins-bad module. + +Minimum requirements: +- glib 2.32.1 +- gstreamer 0.10.36 +- gst-plugins-base 0.10.36 +- gst-plugins-good 0.10.31 +- gst-plugins-bad 0.10.23 + +The following patches need in addition to be applied: + +isomp4 (from gst-plugins-good): +https://bugzilla.gnome.org/show_bug.cgi?id=677535 +https://bugzilla.gnome.org/show_bug.cgi?id=678767 +https://bugzilla.gnome.org/show_bug.cgi?id=678772 + +decodebin2 (from gst-plugins-base): +To be completed + +To build gst-dashdemux: +./autogen.sh +./configure +./make +sudo make install + +To launch gst-dashdemux: + +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 + + diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c new file mode 100644 index 0000000000..3c179d0e1c --- /dev/null +++ b/ext/dash/gstdashdemux.c @@ -0,0 +1,1347 @@ +/* + * 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 (LICENSE); 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" + * ]| + */ + +#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 "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/xml")); + +GST_DEBUG_CATEGORY_STATIC (gst_dash_demux_debug); +#define GST_CAT_DEFAULT gst_dash_demux_debug + +enum +{ + PROP_0, + + PROP_MIN_BUFFERING_TIME, + PROP_MAX_BUFFERING_TIME, + PROP_BANDWIDTH_USAGE, + PROP_MAX_BITRATE, + PROP_LAST +}; + +/* Default values for properties */ +#define DEFAULT_MIN_BUFFERING_TIME 5 /* in seconds */ +#define DEFAULT_MAX_BUFFERING_TIME 30 /* in seconds */ +#define DEFAULT_BANDWIDTH_USAGE 0.8 /* 0 to 1 */ +#define DEFAULT_MAX_BITRATE 24000000 /* in Mbit/s */ + +#define DEFAULT_FAILED_COUNT 3 + + +/* 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_pause_stream_task (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_schedule (GstDashDemux * demux); +static gboolean gst_dash_demux_switch_playlist (GstDashDemux * demux, + guint64 current_bitrate); +static gboolean gst_dash_demux_get_next_fragment (GstDashDemux * demux, + gboolean caching); + +static void gst_dash_demux_reset (GstDashDemux * demux, gboolean dispose); +static GstClockTime gst_dash_demux_get_buffering_time (GstDashDemux * demux); +static float gst_dash_demux_get_buffering_ratio (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); + 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; + } + + if (demux->downloader != NULL) { + g_object_unref (demux->downloader); + demux->downloader = NULL; + } + + gst_dash_demux_reset (demux, TRUE); + + g_queue_free (demux->queue); + + 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_MIN_BUFFERING_TIME, + g_param_spec_uint ("min-buffering-time", "Minimum buffering time", + "Minimum number of seconds of buffer accumulated before playback", + 1, G_MAXUINT, DEFAULT_MIN_BUFFERING_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + 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 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->min_buffering_time = DEFAULT_MIN_BUFFERING_TIME * GST_SECOND; + demux->max_buffering_time = DEFAULT_MAX_BUFFERING_TIME * GST_SECOND; + demux->bandwidth_usage = DEFAULT_BANDWIDTH_USAGE; + demux->max_bitrate = DEFAULT_MAX_BITRATE; + + demux->queue = g_queue_new (); + /* 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); + demux->download_timed_lock = g_mutex_new (); + + /* 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_MIN_BUFFERING_TIME: + demux->min_buffering_time = g_value_get_uint (value) * GST_SECOND; + break; + 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_MIN_BUFFERING_TIME: + g_value_set_uint (value, demux->min_buffering_time); + demux->min_buffering_time *= GST_SECOND; + break; + case PROP_MAX_BUFFERING_TIME: + g_value_set_uint (value, demux->max_buffering_time); + 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); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_dash_demux_reset (demux, FALSE); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* Start the streaming loop in paused only if we already received + the main playlist. It might have been stopped if we were in PAUSED + state and we filled our queue with enough cached fragments + */ + if (gst_mpdparser_get_baseURL (demux->client) != NULL) + gst_dash_demux_resume_stream_task (demux); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + gst_dash_demux_pause_stream_task (demux); + gst_task_pause (demux->stream_task); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + demux->cancelled = TRUE; + gst_dash_demux_stop (demux); + gst_task_join (demux->stream_task); + gst_dash_demux_reset (demux, FALSE); + break; + default: + break; + } + return ret; +} + +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 *walk; + GstClockTime current_pos, target_pos; + gint current_sequence; + GstActiveStream *stream; + GstMediaSegment *chunk; + guint nb_active_stream; + guint stream_idx; + + GST_INFO_OBJECT (demux, "Received GST_EVENT_SEEK"); + + 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; + + nb_active_stream = gst_mpdparser_get_nb_active_stream (demux->client); + gst_task_stop (demux->download_task); + GST_DEBUG_OBJECT (demux, "seek event, rate: %f start: %" GST_TIME_FORMAT + " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop)); + + GST_MPD_CLIENT_LOCK (demux->client); + /* select stream TODO: support multiple streams */ + stream = + g_list_nth_data (demux->client->active_streams, + demux->client->stream_idx); + + current_pos = 0; + target_pos = (GstClockTime) start; + for (walk = stream->segments; walk; walk = walk->next) { + chunk = walk->data; + current_sequence = chunk->number; + if (current_pos <= target_pos + && target_pos < current_pos + chunk->duration) { + break; + } + current_pos += chunk->duration; + } + GST_MPD_CLIENT_UNLOCK (demux->client); + + if (walk == NULL) { + gst_dash_demux_resume_stream_task (demux); + gst_dash_demux_resume_download_task (demux); + GST_WARNING_OBJECT (demux, "Could not find seeked fragment"); + return FALSE; + } + + + if (flags & GST_SEEK_FLAG_FLUSH) { + GST_DEBUG_OBJECT (demux, "sending flush start"); + stream_idx = 0; + while (stream_idx < nb_active_stream) { + gst_pad_push_event (demux->srcpad[stream_idx], + gst_event_new_flush_start ()); + stream_idx++; + } + } + + demux->cancelled = TRUE; + gst_dash_demux_pause_stream_task (demux); + gst_uri_downloader_cancel (demux->downloader); + + /* wait for streaming to finish */ + g_static_rec_mutex_lock (&demux->stream_lock); + + while (!g_queue_is_empty (demux->queue)) { + GList *listfragment = g_queue_pop_head (demux->queue); + guint j = 0; + while (j < g_list_length (listfragment)) { + GstFragment *fragment = g_list_nth_data (listfragment, j); + g_object_unref (fragment); + j++; + } + } + g_queue_clear (demux->queue); + + GST_MPD_CLIENT_LOCK (demux->client); + GST_DEBUG_OBJECT (demux, "seeking to sequence %d", current_sequence); + stream_idx = 0; + while (stream_idx < nb_active_stream) { + stream = + gst_mpdparser_get_active_stream_by_index (demux->client, + stream_idx); + stream->segment_idx = current_sequence; + stream_idx++; + } + gst_mpd_client_get_current_position (demux->client, &demux->position); + demux->position_shift = start - demux->position; + demux->need_segment = TRUE; + GST_MPD_CLIENT_UNLOCK (demux->client); + + + if (flags & GST_SEEK_FLAG_FLUSH) { + GST_DEBUG_OBJECT (demux, "sending flush stop on all pad"); + stream_idx = 0; + while (stream_idx < nb_active_stream) { + gst_pad_push_event (demux->srcpad[stream_idx], + gst_event_new_flush_stop ()); + stream_idx++; + } + } + + 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; + } + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +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 *playlist; + GstQuery *query; + gboolean res; + + if (demux->playlist == NULL) { + GST_WARNING_OBJECT (demux, "Received EOS without a playlist."); + break; + } + + GST_DEBUG_OBJECT (demux, + "Got EOS on the sink pad: main playlist 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); + + playlist = (gchar *) GST_BUFFER_DATA (demux->playlist); + if (playlist == NULL) { + GST_WARNING_OBJECT (demux, "Error validating first playlist."); + } else if (!gst_mpd_parse (demux->client, playlist, + GST_BUFFER_SIZE (demux->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_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), + (NULL)); + return FALSE; + } + gst_buffer_unref (demux->playlist); + demux->playlist = NULL; + + /* Select first adaptation set, first representation and first fragment + * TODO: support for multiple adaptation sets */ + if (!gst_mpd_client_setup_streaming (demux->client, GST_STREAM_VIDEO)) { + GST_ELEMENT_ERROR (demux, STREAM, DECODE, + ("Incompatible manifest file."), (NULL)); + return FALSE; + } + + if (gst_mpdparser_get_nb_adaptationSet (demux->client) > 1) + if (!gst_mpd_client_setup_streaming (demux->client, GST_STREAM_AUDIO)) + GST_INFO_OBJECT (demux, "No audio adaptation set found"); + + if (gst_mpdparser_get_nb_adaptationSet (demux->client) > 2) + if (!gst_mpd_client_setup_streaming (demux->client, + GST_STREAM_APPLICATION)) { + GST_INFO_OBJECT (demux, "No application adaptation set found"); + } + + demux->client->stream_idx = 0; + + /* Send duration message */ + if (!gst_mpd_client_is_live (demux->client)) { + GstClockTime duration = gst_mpd_client_get_duration (demux->client); + + GST_DEBUG_OBJECT (demux, "Sending duration message : %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + if (duration != GST_CLOCK_TIME_NONE) + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_duration (GST_OBJECT (demux), + GST_FORMAT_TIME, duration)); + } + 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_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_URI: + if (dashdemux->client) { + const gchar *initializationURL; + gchar *header_uri; + /* GG: I would answer with the URI of the initialization segment, or, + * if there is no initialization segment, with the URI of the first segment + * as this are usually mp4 files */ + if (!gst_mpd_client_get_next_header (dashdemux->client, + &initializationURL, 0)) { + if (strncmp (initializationURL, "http://", 7) != 0) { + header_uri = + g_strconcat (gst_mpdparser_get_baseURL (dashdemux->client), + initializationURL, NULL); + } else { + header_uri = g_strdup (initializationURL); + } + gst_query_set_uri (query, header_uri); + g_free (header_uri); + ret = TRUE; + } else { + ret = FALSE; + } + } + 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_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; + } + 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 GstFlowReturn +gst_dash_demux_pad (GstPad * pad, GstBuffer * buf) +{ + GstDashDemux *demux = GST_DASH_DEMUX (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 void +gst_dash_demux_stop (GstDashDemux * demux) +{ + gst_uri_downloader_cancel (demux->downloader); + + if (GST_TASK_STATE (demux->download_task) != GST_TASK_STOPPED) { + demux->stop_stream_task = TRUE; + gst_task_stop (demux->download_task); + GST_TASK_SIGNAL (demux->download_task); + } + + if (GST_TASK_STATE (demux->stream_task) != GST_TASK_STOPPED) + gst_task_stop (demux->stream_task); +} + +static void +switch_pads (GstDashDemux * demux, guint nb_adaptation_set) +{ + GstPad *oldpad[3]; + guint i = 0; + while (i < nb_adaptation_set) { + oldpad[i] = demux->srcpad[i]; + if (oldpad[i]) { + GST_DEBUG_OBJECT (demux, + "Switching pads (oldpad:%p)" GST_PTR_FORMAT, oldpad[i]); + } + i++; + } + /* FIXME: This is a workaround for a bug in playsink. + * If we're switching from an audio-only or video-only fragment + * to an audio-video segment, the new sink doesn't know about + * the current running time and audio/video will go out of sync. + * + * This should be fixed in playsink by distributing the + * current running time to newly created sinks and is + * fixed in 0.11 with the new segments. + */ + /*if (*srcpad ) + gst_pad_push_event (*srcpad, gst_event_new_flush_stop ()); + */ + /* First create and activate new pad */ + i = 0; + while (i < nb_adaptation_set) { + demux->srcpad[i] = gst_pad_new_from_static_template (&srctemplate, NULL); + gst_pad_set_event_function (demux->srcpad[i], + GST_DEBUG_FUNCPTR (gst_dash_demux_src_event)); + gst_pad_set_query_function (demux->srcpad[i], + GST_DEBUG_FUNCPTR (gst_dash_demux_src_query)); + gst_pad_set_element_private (demux->srcpad[i], demux); + gst_pad_set_active (demux->srcpad[i], TRUE); + gst_pad_set_caps (demux->srcpad[i], demux->output_caps[i]); + gst_element_add_pad (GST_ELEMENT (demux), demux->srcpad[i]); + i++; + } + gst_element_no_more_pads (GST_ELEMENT (demux)); + /* Push out EOS and remove the last chain/group */ + i = 0; + while (i < nb_adaptation_set) { + if (oldpad[i]) { + gst_pad_push_event (oldpad[i], gst_event_new_eos ()); + gst_pad_set_active (oldpad[i], FALSE); + gst_element_remove_pad (GST_ELEMENT (demux), oldpad[i]); + } + i++; + } +} + +static gboolean +needs_pad_switch (GstDashDemux * demux, GList * fragment) +{ + + gboolean switch_pad = FALSE; + guint i = 0; + while (i < g_list_length (fragment)) { + GstFragment *newFragment = g_list_nth_data (fragment, i); + if (newFragment == NULL) { + continue; + } + GstCaps *srccaps = NULL; + demux->output_caps[i] = gst_fragment_get_caps (newFragment); + if (G_LIKELY (demux->srcpad[i])) + srccaps = gst_pad_get_negotiated_caps (demux->srcpad[i]); + if (G_UNLIKELY (!srccaps + || (!gst_caps_is_equal_fixed (demux->output_caps[i], srccaps)) + || demux->need_segment)) { + switch_pad = TRUE; + GST_INFO_OBJECT (demux, "Switch pad i =%d", i); + } + if (G_LIKELY (srccaps)) + gst_caps_unref (srccaps); + i++; + } + return switch_pad; +} + + +static void +gst_dash_demux_stream_loop (GstDashDemux * demux) +{ + GList *listfragment; + GstFlowReturn ret; + GstBufferList *buffer_list; + guint nb_adaptation_set = 0; + /* Loop for the source pad task. + * + * 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 reach the end of the playlist */ + + /* Wait until the next scheduled push downstream */ + if (g_cond_timed_wait (GST_TASK_GET_COND (demux->stream_task), + demux->stream_timed_lock, &demux->next_stream)) { + goto pause_task; + } + + if (g_queue_is_empty (demux->queue)) { + if (demux->end_of_playlist) + goto end_of_playlist; + + return; + } + + if (GST_STATE (demux) == GST_STATE_PLAYING) { + if (!demux->end_of_playlist + && gst_dash_demux_get_buffering_time (demux) < + demux->min_buffering_time) { + /* Warn we are below our threshold: this will eventually pause + * the pipeline */ + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_buffering (GST_OBJECT (demux), + 100 * gst_dash_demux_get_buffering_ratio (demux))); + } + } + listfragment = g_queue_pop_head (demux->queue); + nb_adaptation_set = g_list_length (listfragment); + /* Figure out if we need to create/switch pads */ + gboolean switch_pad = needs_pad_switch (demux, listfragment); + if (switch_pad) { + switch_pads (demux, nb_adaptation_set); + demux->need_segment = TRUE; + } + guint i = 0; + for (i = 0; i < nb_adaptation_set; i++) { + GstFragment *fragment = g_list_nth_data (listfragment, i); + if (demux->need_segment) { + GstClockTime start = fragment->start_time + demux->position_shift; + /* And send a newsegment */ + GST_DEBUG_OBJECT (demux, "Sending new-segment. segment start:%" + GST_TIME_FORMAT, GST_TIME_ARGS (start)); + gst_pad_push_event (demux->srcpad[i], + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + start, GST_CLOCK_TIME_NONE, start)); + demux->need_segment = FALSE; + demux->position_shift = 0; + } + + GST_DEBUG_OBJECT (demux, "Pushing fragment #%d", fragment->index); + buffer_list = gst_fragment_get_buffer_list (fragment); + g_object_unref (fragment); + ret = gst_pad_push_list (demux->srcpad[i], buffer_list); + if (ret != GST_FLOW_OK) + goto error_pushing; + } + if (GST_STATE (demux) == GST_STATE_PLAYING) { + /* Schedule the next push */ + gst_dash_demux_schedule (demux); + } else { + /* The pipeline is now set up, wait until playback begins */ + goto pause_task; + } + + return; + +end_of_playlist: + { + GST_DEBUG_OBJECT (demux, "Reached end of playlist, sending EOS"); + guint i = 0; + for (i = 0; i < nb_adaptation_set; i++) { + gst_pad_push_event (demux->srcpad[i], gst_event_new_eos ()); + } + gst_dash_demux_stop (demux); + return; + } + +error_pushing: + { + /* FIXME: handle error */ + GST_DEBUG_OBJECT (demux, "Error pushing buffer: %s... stopping task", + gst_flow_get_name (ret)); + gst_dash_demux_stop (demux); + return; + } + +pause_task: + { + gst_task_pause (demux->stream_task); + return; + } +} + +static void +gst_dash_demux_reset (GstDashDemux * demux, gboolean dispose) +{ + demux->end_of_playlist = FALSE; + demux->cancelled = FALSE; + + guint i = 0; + for (i = 0; i < 3; i++) + if (demux->input_caps[i]) { + gst_caps_unref (demux->input_caps[i]); + demux->input_caps[i] = NULL; + } + + if (demux->playlist) { + gst_buffer_unref (demux->playlist); + demux->playlist = NULL; + } + if (demux->client) { + gst_mpd_client_free (demux->client); + demux->client = NULL; + } + if (!dispose) { + demux->client = gst_mpd_client_new (); + } + + while (!g_queue_is_empty (demux->queue)) { + GList *listfragment = g_queue_pop_head (demux->queue); + guint j = 0; + while (j < g_list_length (listfragment)) { + GstFragment *fragment = g_list_nth_data (listfragment, j); + g_object_unref (fragment); + j++; + } + } + g_queue_clear (demux->queue); + + demux->position = 0; + demux->position_shift = 0; + demux->need_segment = TRUE; +} + +static GstClockTime +gst_dash_demux_get_buffering_time (GstDashDemux * demux) +{ + return (g_queue_get_length (demux->queue)) * + gst_mpd_client_get_target_duration (demux->client); +} + +static float +gst_dash_demux_get_buffering_ratio (GstDashDemux * demux) +{ + float buffering_time = gst_dash_demux_get_buffering_time (demux); + if (buffering_time >= demux->min_buffering_time) { + return 1.0; + } else + return buffering_time / demux->min_buffering_time; +} + +void +gst_dash_demux_download_loop (GstDashDemux * demux) +{ + /* Loop for downloading the fragments. It's started from the stream + * loop, and fetches new fragments to maintain the number of queued + * items within a predefined range. When a new fragment is downloaded, + * it evaluates the download time to check if we can or should + * switch to a different bitrate */ + + /* Wait until the next scheduled download */ + if (g_cond_timed_wait (GST_TASK_GET_COND (demux->download_task), + demux->download_timed_lock, &demux->next_download)) { + goto quit; + } + + /* Target buffering time MUST at least exceeds mimimum buffering time + * by the duration of a fragment, but SHOULD NOT exceed maximum + * buffering time */ + GstClockTime target_buffering_time = + demux->min_buffering_time + + gst_mpd_client_get_target_duration (demux->client); + if (demux->max_buffering_time > target_buffering_time) + target_buffering_time = demux->max_buffering_time; + if (!demux->end_of_playlist + && gst_dash_demux_get_buffering_time (demux) < target_buffering_time) { + if (GST_STATE (demux) != GST_STATE_PLAYING) { + /* Signal our buffering status (this will eventually restart the + * pipeline when we have reached 100 %) */ + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_buffering (GST_OBJECT (demux), + 100 * gst_dash_demux_get_buffering_ratio (demux))); + } + + /* fetch the next fragment */ + /* try to switch to another bitrate if needed */ + gst_dash_demux_switch_playlist (demux, + demux->bandwidth_usage * demux->dnl_rate * + gst_dash_demux_get_buffering_ratio (demux)); + + if (!gst_dash_demux_get_next_fragment (demux, FALSE)) { + if (!demux->end_of_playlist && !demux->cancelled) { + demux->client->update_failed_count++; + if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { + GST_WARNING_OBJECT (demux, "Could not fetch the next fragment"); + return; + } else { + GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, + ("Could not fetch the next fragment"), (NULL)); + goto quit; + } + } + } else { + GST_INFO_OBJECT (demux, "Internal buffering : %d s", + gst_dash_demux_get_buffering_time (demux) / GST_SECOND); + demux->client->update_failed_count = 0; + } + } else { + /* schedule the next download in 100 ms */ + g_get_current_time (&demux->next_download); + g_time_val_add (&demux->next_download, 100000); + } + return; +quit: + { + GST_DEBUG_OBJECT (demux, "Stopped download task"); + gst_dash_demux_stop (demux); + } +} + +static void +gst_dash_demux_pause_stream_task (GstDashDemux * demux) +{ + /* Send a signal to the stream task so that it pauses itself */ + GST_TASK_SIGNAL (demux->stream_task); + /* Pause it explicitly (if it was not in the COND) */ + gst_task_pause (demux->stream_task); +} + +static void +gst_dash_demux_resume_stream_task (GstDashDemux * demux) +{ + g_get_current_time (&demux->next_stream); + gst_task_start (demux->stream_task); +} + +static void +gst_dash_demux_resume_download_task (GstDashDemux * demux) +{ + g_get_current_time (&demux->next_download); + gst_task_start (demux->download_task); +} + +static gboolean +gst_dash_demux_schedule (GstDashDemux * demux) +{ + /* schedule the next push */ + g_get_current_time (&demux->next_stream); + g_time_val_add (&demux->next_stream, + gst_mpd_client_get_target_duration (demux->client) + / GST_SECOND * G_USEC_PER_SEC); + GST_INFO_OBJECT (demux, "Next push scheduled at %s", + g_time_val_to_iso8601 (&demux->next_stream)); + + return TRUE; +} + +static gboolean +gst_dash_demux_switch_playlist (GstDashDemux * demux, guint64 bitrate) +{ + /* TODO: support multiple streams */ + GstActiveStream *stream = NULL; + GList *rep_list; + gint new_index; + gboolean ret = FALSE; + + /* select stream TODO: support multiple streams */ + guint i = 0; + while (i < gst_mpdparser_get_nb_active_stream (demux->client)) { + if (demux->client->active_streams) + stream = g_list_nth_data (demux->client->active_streams, i); + if (!stream) + return FALSE; + + /* retrieve representation list */ + if (stream->cur_adapt_set) + rep_list = stream->cur_adapt_set->Representations; + else + rep_list = demux->client->cur_period->Representations; + + /* get representation index with current max_bandwidth */ + new_index = + gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate); + + if (new_index == -1) + new_index = 0; /* if no representation has the required bandwidth, take the lowest one */ + + if (new_index != stream->representation_idx) { + GST_MPD_CLIENT_LOCK (demux->client); + ret = + gst_mpd_client_setup_representation (demux->client, stream, + g_list_nth_data (rep_list, new_index)); + GST_MPD_CLIENT_UNLOCK (demux->client); + if (ret) { + GST_INFO_OBJECT (demux, "Switching bitrate to %d", + stream->cur_representation->bandwidth); + } else { + GST_WARNING_OBJECT (demux, + "Can not switch representation, aborting..."); + } + } + i++; + } + return ret; +} + +static GstFragment * +gst_dash_demux_get_next_header (GstDashDemux * demux, guint stream_idx) +{ + const gchar *next_header_uri, *initializationURL; + + if (!gst_mpd_client_get_next_header (demux->client, &initializationURL, + stream_idx)) + return FALSE; + + 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); + + return gst_uri_downloader_fetch_uri (demux->downloader, next_header_uri); +} + +static GstBufferListItem +gst_dash_demux_add_buffer_cb (GstBuffer ** buffer, + guint group, guint idx, gpointer user_data) +{ + GstFragment *frag = GST_FRAGMENT (user_data); + /* This buffer still belongs to the original fragment */ + /* so we need to increase refcount */ + gst_fragment_add_buffer (frag, gst_buffer_ref (*buffer)); + return GST_BUFFER_LIST_CONTINUE; +} + +/* Since we cannot add headers after the chunk has been downloaded, we have to recreate a new fragment */ +static GstFragment * +gst_dash_demux_prepend_header (GstDashDemux * demux, + GstFragment * frag, GstFragment * header) +{ + GstFragment *res = gst_fragment_new (); + res->name = g_strdup (frag->name); + res->download_start_time = frag->download_start_time; + res->download_stop_time = frag->download_stop_time; + res->start_time = frag->start_time; + res->stop_time = frag->stop_time; + res->index = frag->index; + res->discontinuous = frag->discontinuous; + + GstBufferList *list; + list = gst_fragment_get_buffer_list (header); + gst_buffer_list_foreach (list, gst_dash_demux_add_buffer_cb, res); + gst_buffer_list_unref (list); + list = gst_fragment_get_buffer_list (frag); + gst_buffer_list_foreach (list, gst_dash_demux_add_buffer_cb, res); + gst_buffer_list_unref (list); + + res->completed = TRUE; + + return res; +} + +const gchar * +gst_mpd_mimetype_to_caps (const gchar * mimeType) +{ + 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; +} + +static GstCaps * +gst_dash_demux_get_video_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + guint width, height; + const gchar *mimeType; + GstCaps *caps = NULL; + if (stream == NULL) + return NULL; + width = + gst_mpd_client_get_width_of_video_current_stream (demux->client, stream); + height = + gst_mpd_client_get_height_of_video_current_stream (demux->client, stream); + if (!stream->cur_representation->RepresentationBase->mimeType) + return NULL; + mimeType = + gst_mpd_mimetype_to_caps (stream->cur_representation-> + RepresentationBase->mimeType); + caps = + gst_caps_new_simple (mimeType, "width", G_TYPE_INT, width, "height", + G_TYPE_INT, height, NULL); + return caps; +} + +static GstCaps * +gst_dash_demux_get_audio_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + guint rate, channels; + const gchar *mimeType; + GstCaps *caps = NULL; + channels = + gst_mpd_client_get_num_channels_of_audio_current_stream (demux->client, + stream); + rate = + gst_mpd_client_get_rate_of_audio_current_stream (demux->client, stream); + mimeType = + gst_mpd_mimetype_to_caps (stream->cur_representation-> + RepresentationBase->mimeType); + caps = + gst_caps_new_simple (mimeType, "channels", G_TYPE_INT, channels, "rate", + G_TYPE_INT, rate, NULL); + return caps; +} + +static GstCaps * +gst_dash_demux_get_application_input_caps (GstDashDemux * demux, + GstActiveStream * stream) +{ + const gchar *mimeType; + GstCaps *caps = NULL; + mimeType = + gst_mpd_mimetype_to_caps (stream->cur_representation-> + RepresentationBase->mimeType); + caps = gst_caps_new_simple (mimeType, NULL); + return caps; +} + +static GstCaps * +gst_dash_demux_get_input_caps (GstDashDemux * demux, GstActiveStream * stream) +{ + switch (stream->mimeType) { + case GST_STREAM_VIDEO: + return gst_dash_demux_get_video_input_caps (demux, stream); + case GST_STREAM_AUDIO: + return gst_dash_demux_get_audio_input_caps (demux, stream); + case GST_STREAM_APPLICATION: + return gst_dash_demux_get_application_input_caps (demux, stream); + default: + return GST_CAPS_NONE; + } +} + +static gboolean +gst_dash_demux_get_next_fragment (GstDashDemux * demux, gboolean caching) +{ + GstActiveStream *stream; + GstFragment *download, *header; + GList *list_fragment; + const gchar *next_fragment_uri; + GstClockTime duration; + GstClockTime timestamp; + gboolean discont; + GTimeVal now; + GTimeVal start; + GstClockTime diff; + guint64 size_buffer = 0; + gboolean switch_pad = FALSE; + + g_get_current_time (&start); + /* support multiple streams */ + int stream_idx = 0; + list_fragment = NULL; + while (stream_idx < gst_mpdparser_get_nb_active_stream (demux->client)) { + if (!gst_mpd_client_get_next_fragment (demux->client, + stream_idx, &discont, &next_fragment_uri, &duration, ×tamp)) { + GST_INFO_OBJECT (demux, "This playlist doesn't contain more fragments"); + demux->end_of_playlist = TRUE; + gst_task_start (demux->stream_task); + return FALSE; + } + + GST_INFO_OBJECT (demux, "Fetching next fragment %s", next_fragment_uri); + + download = gst_uri_downloader_fetch_uri (demux->downloader, + next_fragment_uri); + + if (download == NULL) + goto error; + + download->start_time = timestamp; + download->stop_time = timestamp + duration; + + stream = + gst_mpdparser_get_active_stream_by_index (demux->client, stream_idx); + if (stream == NULL) + goto error; + download->index = stream->segment_idx; + + GstCaps *caps = gst_dash_demux_get_input_caps (demux, stream); + + if (!demux->input_caps[stream_idx] + || !gst_caps_is_equal (caps, demux->input_caps[stream_idx]) + || switch_pad) { + /* We changed spatial representation */ + gst_caps_replace (&demux->input_caps[stream_idx], caps); + GST_INFO_OBJECT (demux, "Input source caps: %" GST_PTR_FORMAT, + demux->input_caps[stream_idx]); + switch_pad = TRUE; + /* We need to fetch a new header */ + if ((header = gst_dash_demux_get_next_header (demux, stream_idx)) == NULL) { + GST_INFO_OBJECT (demux, "Unable to fetch header"); + return FALSE; + } + + /* Replace fragment with a new one including the header */ + GstFragment *new_fragment = + gst_dash_demux_prepend_header (demux, download, header); + g_object_unref (header); + g_object_unref (download); + download = new_fragment; + } else + gst_caps_unref (caps); + + gst_fragment_set_caps (download, demux->input_caps[stream_idx]); + list_fragment = g_list_append (list_fragment, download); + size_buffer += gst_fragment_get_buffer_size (download); + stream_idx++; + } + g_queue_push_tail (demux->queue, list_fragment); + if (!caching) { + GST_TASK_SIGNAL (demux->download_task); + } + g_get_current_time (&now); + diff = (GST_TIMEVAL_TO_TIME (now) - GST_TIMEVAL_TO_TIME (start)); + demux->dnl_rate = (size_buffer * 8) / ((double) diff / GST_SECOND); + GST_INFO_OBJECT (demux, "Download rate = %d Kbits/s (%d Ko in %.2f s)", + demux->dnl_rate / 1000, size_buffer / 1024, ((double) diff / GST_SECOND)); + return TRUE; + +error: + { + gst_dash_demux_stop (demux); + return FALSE; + } +} + diff --git a/ext/dash/gstdashdemux.h b/ext/dash/gstdashdemux.h new file mode 100644 index 0000000000..4c7c379e35 --- /dev/null +++ b/ext/dash/gstdashdemux.h @@ -0,0 +1,109 @@ +/* + * 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 (LICENSE); 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__ + +#include +#include +#include +#include +#include "gstmpdparser.h" +#include "gstfragmented.h" +#include "gsturidownloader.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; +/** + * GstDashDemux: + * + * Opaque #GstDashDemux data structure. + */ +struct _GstDashDemux +{ + GstElement parent; + GstPad *sinkpad; + GstPad *srcpad[3]; /*Video/Audio/Application src pad*/ + GstCaps *output_caps[3]; /*Video/Audio/Application output buf caps*/ + GstCaps *input_caps[3]; /*Video/Audio/Application input caps*/ + + GstBuffer *playlist; + GstUriDownloader *downloader; + GstMpdClient *client; /* MPD client */ + GQueue *queue; /*Video/Audio/Application List of fragment storing the fetched fragments */ + gboolean end_of_playlist; + + /* Properties */ + GstClockTime min_buffering_time; /* Minimum buffering time accumulated before playback */ + 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; + gboolean stop_stream_task; + GMutex *stream_timed_lock; + GTimeVal next_stream; /* Time of the next push */ + + /* Download task */ + GstTask *download_task; + GStaticRecMutex download_lock; + gboolean cancelled; + GMutex *download_timed_lock; + GTimeVal next_download; /* Time of the next download */ + + /* Position in the stream */ + GstClockTime position; + GstClockTime position_shift; + gboolean need_segment; + /* Download rate */ + guint64 dnl_rate; +}; + +struct _GstDashDemuxClass +{ + GstElementClass parent_class; +}; + +GType gst_dash_demux_get_type (void); + +G_END_DECLS +#endif /* __GST_DASH_DEMUX_H__ */ + diff --git a/ext/dash/gstfragment.c b/ext/dash/gstfragment.c new file mode 100644 index 0000000000..f76f7d1094 --- /dev/null +++ b/ext/dash/gstfragment.c @@ -0,0 +1,281 @@ +/* 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 "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 +{ + GstBufferList *buffer_list; + guint64 size; + GstBufferListIterator *buffer_iterator; + GstCaps *caps; + GMutex lock; +}; + +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_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GstFragment *fragment = GST_FRAGMENT (object); + + switch (property_id) { + case PROP_CAPS: + gst_fragment_set_caps (fragment, g_value_get_boxed (value)); + break; + + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +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; + + case PROP_BUFFER_LIST: + g_value_set_object (value, gst_fragment_get_buffer_list (fragment)); + break; + + case PROP_CAPS: + g_value_set_boxed (value, gst_fragment_get_caps (fragment)); + 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->set_property = gst_fragment_set_property; + 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)); + + g_object_class_install_property (gobject_class, PROP_BUFFER_LIST, + g_param_spec_object ("buffer-list", "Buffer List", + "A list with the fragment's buffers", GST_TYPE_FRAGMENT, + G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, PROP_CAPS, + g_param_spec_boxed ("caps", "Fragment caps", + "The caps of the fragment's buffer. (NULL = detect)", GST_TYPE_CAPS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_fragment_init (GstFragment * fragment) +{ + GstFragmentPrivate *priv; + + fragment->priv = priv = GST_FRAGMENT_GET_PRIVATE (fragment); + + g_mutex_init (&fragment->priv->lock); + priv->buffer_list = gst_buffer_list_new (); + priv->size = 0; + priv->buffer_iterator = gst_buffer_list_iterate (priv->buffer_list); + gst_buffer_list_iterator_add_group (priv->buffer_iterator); + 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_mutex_clear (&fragment->priv->lock); + + G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject); +} + +void +gst_fragment_dispose (GObject * object) +{ + GstFragmentPrivate *priv = GST_FRAGMENT (object)->priv; + + if (priv->buffer_list != NULL) { + gst_buffer_list_iterator_free (priv->buffer_iterator); + gst_buffer_list_unref (priv->buffer_list); + priv->buffer_list = NULL; + priv->size = 0; + } + + if (priv->caps != NULL) { + gst_caps_unref (priv->caps); + priv->caps = NULL; + } + + G_OBJECT_CLASS (gst_fragment_parent_class)->dispose (object); +} + +GstBufferList * +gst_fragment_get_buffer_list (GstFragment * fragment) +{ + g_return_val_if_fail (fragment != NULL, NULL); + + if (!fragment->completed) + return NULL; + + gst_buffer_list_ref (fragment->priv->buffer_list); + return fragment->priv->buffer_list; +} + +void +gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps) +{ + g_return_if_fail (fragment != NULL); + + g_mutex_lock (&fragment->priv->lock); + gst_caps_replace (&fragment->priv->caps, caps); + g_mutex_unlock (&fragment->priv->lock); +} + +GstCaps * +gst_fragment_get_caps (GstFragment * fragment) +{ + g_return_val_if_fail (fragment != NULL, NULL); + + if (!fragment->completed) + return NULL; + + g_mutex_lock (&fragment->priv->lock); + if (fragment->priv->caps == NULL) { + GstBuffer *buf = gst_buffer_list_get (fragment->priv->buffer_list, 0, 0); + fragment->priv->caps = gst_type_find_helper_for_buffer (NULL, buf, NULL); + } + gst_caps_ref (fragment->priv->caps); + g_mutex_unlock (&fragment->priv->lock); + + return fragment->priv->caps; +} + +guint64 +gst_fragment_get_buffer_size (GstFragment * fragment) +{ + g_return_val_if_fail (fragment != NULL, 0); + + if (!fragment->completed) + return 0; + return fragment->priv->size; +} + + + +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_buffer_list_iterator_add (fragment->priv->buffer_iterator, buffer); + fragment->priv->size = fragment->priv->size + GST_BUFFER_SIZE (buffer); + return TRUE; +} + diff --git a/ext/dash/gstfragment.h b/ext/dash/gstfragment.h new file mode 100644 index 0000000000..9078c25cb1 --- /dev/null +++ b/ext/dash/gstfragment.h @@ -0,0 +1,72 @@ +/* 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_buffer_size (GstFragment * fragment); +GstBufferList * gst_fragment_get_buffer_list (GstFragment *fragment); +void gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps); +GstCaps * gst_fragment_get_caps (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/ext/dash/gstfragmented.h b/ext/dash/gstfragmented.h new file mode 100644 index 0000000000..df94c2456a --- /dev/null +++ b/ext/dash/gstfragmented.h @@ -0,0 +1,16 @@ +#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/ext/dash/gstmpdparser.c b/ext/dash/gstmpdparser.c new file mode 100644 index 0000000000..4383de1375 --- /dev/null +++ b/ext/dash/gstmpdparser.c @@ -0,0 +1,2897 @@ +/* + * gstmpdparser.c - DASH MPD helper library + * Copyright (C) 2012 STMicroelectronics + * Authors: + * Gianluca Gennari + * + * 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. + */ + +#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 (GList ** list, 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 (GstProgramInformationNode ** pointer, 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 (GstMpdClient * client, 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 gchar *gst_mpdparser_parse_baseURL (GstMpdClient * client); +static gchar *gst_mpdparser_get_mediaURL (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); + +/* Period */ +static GstPeriodNode *gst_mpdparser_get_next_period (GList *Periods, GstPeriodNode *prev_period); + +/* Adaptation Set */ +static GstAdaptationSetNode *gst_mpdparser_get_first_adapt_set (GList *AdaptationSets); +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, gint max_bandwidth); +#endif +static GstSegmentBaseType *gst_mpdparser_get_segment_base (GstPeriodNode *Period, GstAdaptationSetNode * AdaptationSet, GstRepresentationNode * Representation); +static GstSegmentListNode *gst_mpdparser_get_first_segment_list (GList * SegmentList); + +/* 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_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"); +} + +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 (GList ** list, xmlNode * a_node) +{ + xmlNode *cur_node; + GstSegmentListNode *new_segment_list; + + new_segment_list = g_slice_new0 (GstSegmentListNode); + if (new_segment_list == NULL) { + GST_WARNING ("Allocation of SegmentList node failed!"); + return; + } + *list = g_list_append (*list, new_segment_list); + + 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->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 *) "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 *) "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 *) "Representation") == 0) { + gst_mpdparser_parse_representation_node (&new_period->Representations, cur_node); + } else 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 *) "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 (GstProgramInformationNode ** pointer, xmlNode * a_node) +{ + xmlNode *cur_node; + GstProgramInformationNode *new_prog_info; + + gst_mpdparser_free_prog_info_node (*pointer); + *pointer = new_prog_info = g_slice_new0 (GstProgramInformationNode); + if (new_prog_info == NULL) { + GST_WARNING ("Allocation of ProgramInfo node failed!"); + return; + } + + GST_LOG ("attributes of ProgramInformation node:"); + 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 (GstMpdClient * client, xmlNode *a_node) +{ + xmlNode *cur_node; + GstMPDNode *new_mpd; + + gst_mpdparser_free_mpd_node (client->mpd_node); + client->mpd_node = 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 GstPeriodNode * +gst_mpdparser_get_next_period (GList * Periods, GstPeriodNode * prev_period) +{ + GList *list = NULL; + + if (Periods == NULL) + return NULL; + + if (prev_period == NULL) { + /* return the first period in the list */ + list = g_list_first (Periods); + } else { + /* found prev_period in the list */ + list = g_list_find (Periods, prev_period); + /* next period */ + list = g_list_next (list); + } + + return list ? (GstPeriodNode *) list->data : NULL; +} + +static GstAdaptationSetNode * +gst_mpdparser_get_first_adapt_set (GList * AdaptationSets) +{ + GList *list = NULL; + + if (AdaptationSets == NULL) + return NULL; + + list = g_list_first (AdaptationSets); + + return list ? (GstAdaptationSetNode *) list->data : NULL; +} + +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->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->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->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) { + SegmentBase = Representation->SegmentBase; + } else if (AdaptationSet) { + SegmentBase = AdaptationSet->SegmentBase; + } else { + SegmentBase = Period->SegmentBase; + } + + return SegmentBase; +} + +gint +gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, + gint max_bandwidth) +{ + GList *list = NULL, *best = NULL; + GstRepresentationNode *representation; + + 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) { + best = list; + } + } + + return best ? g_list_position (Representations, best) : -1; +} + +static GstSegmentListNode * +gst_mpdparser_get_first_segment_list (GList * SegmentList) +{ + GList *list = NULL; + + if (SegmentList == NULL) + return NULL; + + list = g_list_first (SegmentList); + + return list ? (GstSegmentListNode *) list->data : NULL; +} + +/* 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); + gst_mpdparser_free_prog_info_node (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->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_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->Representations, + (GFunc) gst_mpdparser_free_representation_node, NULL); + g_list_free (period_node->Representations); + 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); + 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_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); + g_list_foreach (representation_node->SegmentList, + (GFunc) gst_mpdparser_free_segment_list_node, NULL); + g_list_free (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_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); + g_list_free (active_stream->segments); + g_slice_free (GstActiveStream, active_stream); + } +} + +static gchar * +gst_mpdparser_get_mediaURL (GstSegmentURLNode *segmentURL) +{ + gchar *mediaURL; + + g_return_val_if_fail (segmentURL != NULL, NULL); + g_return_val_if_fail (segmentURL->media != NULL, NULL); + + if (segmentURL->mediaRange) { + gchar *range; + range = g_strdup_printf ("?range=%llu-%llu", segmentURL->mediaRange->first_byte_pos, segmentURL->mediaRange->last_byte_pos); + mediaURL = g_strconcat (segmentURL->media, range, NULL); + g_free (range); + } else { + mediaURL = g_strdup (segmentURL->media); + } + + return mediaURL; +} + +static gchar * +gst_mpdparser_get_initializationURL (GstURLType *InitializationURL) +{ + gchar *mediaURL; + + g_return_val_if_fail (InitializationURL != NULL, NULL); + g_return_val_if_fail (InitializationURL->sourceURL != NULL, NULL); + + if (InitializationURL->range) { + gchar *range; + range = g_strdup_printf ("?range=%llu-%llu", InitializationURL->range->first_byte_pos, InitializationURL->range->last_byte_pos); + mediaURL = g_strconcat (InitializationURL->sourceURL, range, NULL); + g_free (range); + } else { + mediaURL = g_strdup (InitializationURL->sourceURL); + } + + return mediaURL; +} + +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; + + 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); + } 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); + } 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); + } 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); + } else if (!g_strcmp0 (token, "")) { + tokens[i] = g_strdup_printf ("%s", "$"); + g_free (token); + } + } + + ret = g_strjoinv (NULL, tokens); + g_strfreev (tokens); + + return ret; +} + +/* select a stream and extract the baseURL (if present) */ +static gchar * +gst_mpdparser_parse_baseURL (GstMpdClient * client) +{ + GstActiveStream *stream; + GstBaseURL *baseURL; + GList *list; + static gchar *baseURL_array[5]; + static gchar empty[] = ""; + gchar *ret = NULL; + + /* select stream TODO: support multiple streams */ + g_return_val_if_fail (client != NULL, empty); + g_return_val_if_fail (client->active_streams != NULL, empty); + stream = g_list_nth_data (client->active_streams, client->stream_idx); + g_return_val_if_fail (stream != 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->baseURL_idx); + if (!baseURL) { + baseURL = list->data; + } + baseURL_array[0] = baseURL->baseURL; + } + if ((list = client->cur_period->BaseURLs) != NULL) { + baseURL = g_list_nth_data (list, stream->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->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->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); + } + } + + GST_DEBUG ("selected baseURL with index %d: %s", stream->baseURL_idx, ret); + + return ret; +} + +/*****************************/ +/******* API functions *******/ +/*****************************/ + +GstMpdClient *gst_mpd_client_new () +{ + GstMpdClient * client; + + client = g_new0 (GstMpdClient, 1); + client->lock = g_mutex_new (); + + return client; +} + +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->active_streams) { + g_list_foreach (client->active_streams, + (GFunc) gst_mpdparser_free_active_stream, NULL); + g_list_free (client->active_streams); + } + + 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..."); + + /* 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"); + 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, root_element); + } + /* free the document */ + xmlFreeDoc (doc); + /* cleanup function for the XML library */ + xmlCleanupParser (); + /* dump XML library memory for debugging */ + xmlMemoryDump (); + } + + 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; +} + +GstMediaSegment * +gst_mpdparser_get_chunk_by_index (GstMpdClient * client, guint indexStream, guint indexChunk) +{ + GstActiveStream *stream; + + /* select 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, indexStream); + g_return_val_if_fail (stream != NULL, NULL); + + return (GstMediaSegment *) g_list_nth_data (stream->segments, indexChunk); +} + +gboolean +gst_mpd_client_setup_representation (GstMpdClient * client, GstActiveStream *stream, GstRepresentationNode *representation) +{ + GList *rep_list; + GstMediaSegment *media_segment = NULL; + GstClockTime PeriodStart = 0, PeriodEnd, start_time, duration; + guint i; + + rep_list = stream->cur_adapt_set ? stream->cur_adapt_set->Representations : client->cur_period->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; + } + + if (client->cur_period->start != -1) { + PeriodStart = client->cur_period->start * GST_MSECOND; + } + if (client->cur_period->duration != -1) { + PeriodEnd = client->cur_period->duration * GST_MSECOND; + } else { + GstPeriodNode *next_period_node = gst_mpdparser_get_next_period (client->mpd_node->Periods, client->cur_period); + if (next_period_node) { + PeriodEnd = next_period_node->start * GST_MSECOND; + } else { + PeriodEnd = client->mpd_node->mediaPresentationDuration * GST_MSECOND; + } + } + + 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) { + GList *SegmentURL; + + /* get the first segment_base of the selected representation */ + if ((stream->cur_segment_base = + gst_mpdparser_get_segment_base (client->cur_period, stream->cur_adapt_set, representation)) == NULL) { + GST_WARNING ("No valid SegmentBase node in the MPD file, aborting..."); + return FALSE; + } + + /* get the first segment_list of the selected representation */ + if ((stream->cur_segment_list = + gst_mpdparser_get_first_segment_list (representation->SegmentList)) == NULL) { + GST_WARNING ("No valid SegmentList node in the MPD file, aborting..."); + return FALSE; + } + + /* 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_time = 0; + duration = gst_mpd_client_get_target_duration (client); + + while (SegmentURL) { + 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); + /* TODO: support SegmentTimeline */ + media_segment->SegmentURL = (GstSegmentURLNode *) SegmentURL->data; + media_segment->number = i; + media_segment->start_time = start_time; + media_segment->duration = duration; + 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 (client->cur_period->SegmentTemplate != NULL) { + stream->cur_seg_template = client->cur_period->SegmentTemplate; + } + + if (stream->cur_seg_template == NULL || stream->cur_seg_template->MultSegBaseType == NULL) { + GST_WARNING ("Can not parse segment informations from MPD file due to incompatible syntax, aborting..."); + return FALSE; + } + + /* build segment list */ + i = stream->cur_seg_template->MultSegBaseType->startNumber; + start_time = 0; + duration = gst_mpd_client_get_target_duration (client); + + GST_LOG ("using template %s", stream->cur_seg_template->media); + while (PeriodStart + start_time < PeriodEnd) { + 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); + /* TODO: support SegmentTimeline */ + media_segment->number = i; + media_segment->start_time = start_time; + media_segment->duration = duration; + i++; + start_time += duration; + } + } + + if (media_segment) { + /* fix duration of last segment */ + if (media_segment->start_time + media_segment->duration > PeriodEnd) { + media_segment->duration = PeriodEnd - media_segment->start_time; + GST_LOG ("Fixed duration of last segment: %" GST_TIME_FORMAT, GST_TIME_ARGS (media_segment->duration)); + } + GST_LOG ("Built a list of %d segments", media_segment->number); + } + + g_free (stream->baseURL); + stream->baseURL = gst_mpdparser_parse_baseURL (client); + + return TRUE; +} + +gboolean +gst_mpd_client_setup_streaming (GstMpdClient * client, + GstStreamMimeType mimeType) +{ + GstActiveStream *stream; + GstAdaptationSetNode *adapt_set; + GstRepresentationNode *representation; + GList *rep_list; + + /* select a new period */ + if (!client->cur_period) + if ((client->cur_period = + gst_mpdparser_get_next_period (client->mpd_node->Periods, client->cur_period)) == NULL) { + GST_WARNING ("No valid Period node in the MPD file, aborting..."); + 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 (client-> + cur_period->AdaptationSets, "video", 0); + /* if we found no 'video' adaptation set, just get the first one */ + if (!adapt_set) + adapt_set = + gst_mpdparser_get_first_adapt_set (client->cur_period-> + AdaptationSets); + /* retrive the list of representations */ + if (adapt_set != NULL) + rep_list = adapt_set->Representations; + else + rep_list = client->cur_period->Representations; + if (!rep_list) { + GST_WARNING ("Can not retrieve any representation, aborting..."); + return FALSE; + } + break; + case GST_STREAM_AUDIO: +#if 0 + if (g_strcmp0 (client->audio_lang, "none") == 0) { + GST_INFO ("Audio stream disabled"); + return FALSE; + } +#endif + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang (client-> + cur_period->AdaptationSets, "audio", "en"); + /* 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 (client-> + cur_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: +#if 0 + if (g_strcmp0 (client->subtitle_lang, "none") == 0) { + GST_INFO ("Subtitles pipeline disabled"); + return FALSE; + } +#endif + adapt_set = + gst_mpdparser_get_first_adapt_set_with_mimeType_and_lang (client-> + cur_period->AdaptationSets, "application", "en"); + /* 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 (client-> + cur_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->baseURL_idx = 0; + stream->mimeType = mimeType; + stream->representation_idx = 0; + stream->segment_idx = 0; + stream->cur_adapt_set = adapt_set; + + /* retrive representation list */ + if (stream->cur_adapt_set != NULL) + rep_list = stream->cur_adapt_set->Representations; + else + rep_list = client->cur_period->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 (GstMpdClient * client, + guint indexStream, gboolean *discontinuity, const gchar **uri, + GstClockTime *duration, GstClockTime *timestamp) +{ + GstActiveStream *stream = NULL; + GstMediaSegment *currentChunk; + gchar *mediaURL = NULL; + + /* 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); + GST_DEBUG ("Looking for fragment sequence chunk %d", stream->segment_idx); + + currentChunk = gst_mpdparser_get_chunk_by_index (client, indexStream, stream->segment_idx); + if (currentChunk == NULL) { + GST_MPD_CLIENT_UNLOCK (client); + return FALSE; + } + + if (currentChunk->SegmentURL != NULL) { + mediaURL = gst_mpdparser_get_mediaURL (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, 0); + } + gst_mpd_client_get_current_position (client, timestamp); + *discontinuity = stream->segment_idx != currentChunk->number; + stream->segment_idx += 1; + if (strncmp (mediaURL, "http://", 7) != 0) { + *uri = g_strconcat (gst_mpdparser_get_baseURL (client), mediaURL, NULL); + g_free (mediaURL); + } else { + *uri = mediaURL; + } + *duration = gst_mpd_client_get_target_duration (client); + 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 = NULL; + + /* select stream */ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + /* TODO: support multiple streams */ + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != 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) { + *uri = gst_mpdparser_get_initializationURL(stream->cur_segment_base->Initialization); + } else if (stream->cur_seg_template) { + *uri = gst_mpdparser_build_URL_from_template (stream->cur_seg_template->initialization, + stream->cur_representation->id, 0, stream->cur_representation->bandwidth, 0); + } + GST_MPD_CLIENT_UNLOCK (client); + + return *uri == NULL ? FALSE : TRUE; +} + +void +gst_mpd_client_get_current_position (GstMpdClient * client, + GstClockTime * timestamp) +{ + GstActiveStream *stream; + GstClockTime duration; + + *timestamp = GST_CLOCK_TIME_NONE; + + /* select stream TODO: support multiple streams */ + stream = g_list_nth_data (client->active_streams, client->stream_idx); + g_return_if_fail (stream != NULL); + + duration = gst_mpd_client_get_target_duration (client); + *timestamp = duration == GST_CLOCK_TIME_NONE ? GST_CLOCK_TIME_NONE : stream->segment_idx * duration; +} + +GstClockTime +gst_mpd_client_get_duration (GstMpdClient * client) +{ + GstClockTime duration; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + GST_MPD_CLIENT_LOCK (client); + duration = client->mpd_node->mediaPresentationDuration * GST_MSECOND; + /* We can only get the duration for on-demand streams */ + if (!duration) { + duration = GST_CLOCK_TIME_NONE; + } + GST_MPD_CLIENT_UNLOCK (client); + + return duration; +} + +GstClockTime +gst_mpd_client_get_target_duration (GstMpdClient * client) +{ + GstActiveStream *stream; + GstMultSegmentBaseType *base = NULL; + GstClockTime duration; + guint timescale; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + /* select stream TODO: support multiple streams */ + stream = g_list_nth_data (client->active_streams, client->stream_idx); + g_return_val_if_fail (stream != 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; + } + + g_return_val_if_fail (base != NULL, GST_CLOCK_TIME_NONE); + g_return_val_if_fail (base->SegBaseType != NULL, GST_CLOCK_TIME_NONE); + duration = base->duration * GST_SECOND; + timescale = base->SegBaseType->timescale; + + if (timescale > 1) + duration /= timescale; + + return duration; +} + +gboolean +gst_mpd_client_is_live (GstMpdClient * client) +{ + 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, FALSE); + return g_list_length (client->active_streams); +} + +guint gst_mpdparser_get_nb_adaptationSet(GstMpdClient *client){ + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->cur_period != NULL, FALSE); + g_return_val_if_fail (client->cur_period->AdaptationSets != NULL, FALSE); + return g_list_length (client->cur_period->AdaptationSets); +} + +GstActiveStream *gst_mpdparser_get_active_stream_by_index (GstMpdClient *client, gint stream_idx){ + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + return g_list_nth_data (client->active_streams, + stream_idx); +} + +guint gst_mpd_client_get_width_of_video_current_stream (GstMpdClient *client, GstActiveStream *stream){ + + g_return_val_if_fail (stream != NULL, FALSE); + return stream->cur_representation->RepresentationBase->width; +} +guint gst_mpd_client_get_height_of_video_current_stream (GstMpdClient *client, GstActiveStream *stream){ + + g_return_val_if_fail (stream != NULL, FALSE); + return stream->cur_representation->RepresentationBase->height; +} + +guint gst_mpd_client_get_rate_of_audio_current_stream (GstMpdClient *client, GstActiveStream *stream){ + + g_return_val_if_fail (stream != NULL, FALSE); + return stream->cur_representation->RepresentationBase->audioSamplingRate; +} +guint gst_mpd_client_get_num_channels_of_audio_current_stream (GstMpdClient *client, GstActiveStream *stream){ + + g_return_val_if_fail (stream != NULL, FALSE); + /* TODO*/ + return 1; +} + diff --git a/ext/dash/gstmpdparser.h b/ext/dash/gstmpdparser.h new file mode 100644 index 0000000000..6693494e65 --- /dev/null +++ b/ext/dash/gstmpdparser.h @@ -0,0 +1,489 @@ +/* + * gstmpdparser.h - DASH MPD helper library + * Copyright (C) 2012 STMicroelectronics + * Authors: + * Gianluca Gennari + * + * 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_MPDPARSER_H__ +#define __GST_MPDPARSER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstMpdClient GstMpdClient; +typedef struct _GstActiveStream GstActiveStream; +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 (1..N) */ + 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; + 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 */ + guint 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; + /* list of SegmentList nodes */ + GList *SegmentList; +}; + +struct _GstDescriptorType +{ + gchar *schemeIdUri; + gchar *value; +}; + +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; + /* RepresentationBase extension */ + GstRepresentationBaseType *RepresentationBase; + /* SegmentBase node */ + GstSegmentBaseType *SegmentBase; + /* 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; + /* SegmentTemplate node */ + GstSegmentTemplateNode *SegmentTemplate; + /* list of Adaptation Set nodes */ + GList *AdaptationSets; + /* list of Representation nodes */ + GList *Representations; + /* list of Subset nodes */ + GList *Subsets; + /* list of BaseURL nodes */ + GList *BaseURLs; +}; + +struct _GstProgramInformationNode +{ + 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; + /* ProgramInformation node */ + GstProgramInformationNode *ProgramInfo; + /* list of Periods nodes */ + GList *Periods; + /* list of Metrics nodes */ + GList *Metrics; +}; + +/** + * GstMediaSegment: + * + * Media segment data structure + */ +struct _GstMediaSegment +{ + GstSegmentURLNode *SegmentURL; /* this is NULL when using a SegmentTemplate */ + guint number; /* segment number */ + GstClockTime start_time; /* segment start time */ + GstClockTime duration; /* segment duration */ +}; + +/** + * GstActiveStream: + * + * Active stream data structure + */ +struct _GstActiveStream +{ + GstStreamMimeType mimeType; /* video/audio/application */ + + guint baseURL_idx; /* index of the baseURL used for last request */ + 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 */ + gint segment_idx; /* index of next sequence chunk */ + GList *segments; /* list of GstMediaSegment nodes */ +}; + +struct _GstMpdClient +{ + GstMPDNode *mpd_node; /* active MPD manifest file */ + GstPeriodNode *cur_period; /* active period */ + + GList *active_streams; /* list of GstActiveStream (only one supported on the first implementation) */ + guint stream_idx; /* currently active stream */ + + guint update_failed_count; + gchar *mpd_uri; /* manifest file URI */ + GMutex *lock; +}; + +/* Basic initialization/deinitialization functions */ +GstMpdClient *gst_mpd_client_new (); +void gst_mpd_client_free (GstMpdClient * client); + +/* Basic parsing */ +gboolean gst_mpd_parse (GstMpdClient *client, const gchar *data, gint size); +gboolean gst_mpd_client_setup_streaming (GstMpdClient *client, GstStreamMimeType mimeType); +gboolean gst_mpd_client_setup_representation (GstMpdClient *client, GstActiveStream *stream, GstRepresentationNode *representation); +void gst_mpd_client_get_current_position (GstMpdClient *client, GstClockTime * timestamp); +GstClockTime gst_mpd_client_get_duration (GstMpdClient *client); +GstClockTime gst_mpd_client_get_target_duration (GstMpdClient *client); +gboolean gst_mpd_client_get_next_fragment (GstMpdClient *client, guint indexStream, gboolean *discontinuity, const gchar **uri, GstClockTime *duration, GstClockTime *timestamp); +gboolean gst_mpd_client_get_next_header (GstMpdClient *client, const gchar **uri, guint stream_idx); +gboolean gst_mpd_client_is_live (GstMpdClient * client); + +/* Representation selection */ +gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint max_bandwidth); + +/* URL management */ +const gchar *gst_mpdparser_get_baseURL (GstMpdClient *client); +GstMediaSegment *gst_mpdparser_get_chunk_by_index (GstMpdClient *client, guint indexStream, guint indexChunk); + +/* Active stream */ +guint gst_mpdparser_get_nb_active_stream (GstMpdClient *client); +GstActiveStream *gst_mpdparser_get_active_stream_by_index (GstMpdClient *client, gint stream_idx); + +/* AdaptationSet */ +guint gst_mpdparser_get_nb_adaptationSet(GstMpdClient *client); + +/* Get With and high of video parameter by stream */ +guint gst_mpd_client_get_width_of_video_current_stream (GstMpdClient *client, GstActiveStream *stream); +guint gst_mpd_client_get_height_of_video_current_stream (GstMpdClient *client, GstActiveStream *stream); + +/* Get channel and rate of audio parameter by stream */ +guint gst_mpd_client_get_rate_of_audio_current_stream (GstMpdClient *client, GstActiveStream *stream); +guint gst_mpd_client_get_num_channels_of_audio_current_stream (GstMpdClient *client, GstActiveStream *stream); + +G_END_DECLS + +#endif /* __GST_MPDPARSER_H__ */ + diff --git a/ext/dash/gstplugin.c b/ext/dash/gstplugin.c new file mode 100644 index 0000000000..f6712c6baf --- /dev/null +++ b/ext/dash/gstplugin.c @@ -0,0 +1,29 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "gstfragmented.h" +#include "gstdashdemux.h" + +GST_DEBUG_CATEGORY (fragmented_debug); + +static gboolean +fragmented_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (fragmented_debug, "dashdemux", 0, "dashdemux"); + + if (!gst_element_register (plugin, "dashdemux", GST_RANK_PRIMARY, + GST_TYPE_DASH_DEMUX) || FALSE) + return FALSE; + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "fragmented", + "Fragmented streaming plugins", + fragmented_init, VERSION, "LGPL", PACKAGE_NAME, "http://www.gstreamer.org/") + + diff --git a/ext/dash/gsturidownloader.c b/ext/dash/gsturidownloader.c new file mode 100644 index 0000000000..387eb8e663 --- /dev/null +++ b/ext/dash/gsturidownloader.c @@ -0,0 +1,341 @@ +/* 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 "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; +}; + +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 (); +} + +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; + } + + 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_OBJECT_LOCK (downloader); + GST_DEBUG_OBJECT (downloader, "Got EOS on the fetcher pad"); + 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_OBJECT_UNLOCK (downloader); + GST_DEBUG_OBJECT (downloader, "Signaling chain funtion"); + g_cond_signal (downloader->priv->cond); + + } else { + GST_OBJECT_UNLOCK (downloader); + } + 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. + */ + GST_OBJECT_LOCK (downloader); + if (downloader->priv->download == NULL) { + /* Download cancelled, quit */ + GST_OBJECT_UNLOCK (downloader); + 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"); + GST_OBJECT_UNLOCK (downloader); + +done: + { + 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 */ + 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) +{ + GST_OBJECT_LOCK (downloader); + if (downloader->priv->download != NULL) { + GST_DEBUG_OBJECT (downloader, "Cancelling download"); + g_object_unref (downloader->priv->download); + downloader->priv->download = NULL; + GST_OBJECT_UNLOCK (downloader); + GST_DEBUG_OBJECT (downloader, "Signaling chain funtion"); + g_cond_signal (downloader->priv->cond); + } else { + GST_OBJECT_UNLOCK (downloader); + GST_DEBUG_OBJECT (downloader, + "Trying to cancell a download that was alredy cancelled"); + } +} + +static gboolean +gst_uri_downloader_set_uri (GstUriDownloader * downloader, const gchar * uri) +{ + GstPad *pad; + + if (!gst_uri_is_valid (uri)) + return FALSE; + + 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; + + /* 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; + + g_mutex_lock (downloader->priv->lock); + + if (!gst_uri_downloader_set_uri (downloader, uri)) { + goto quit; + } + + 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); + + GST_OBJECT_LOCK (downloader); + download = downloader->priv->download; + downloader->priv->download = NULL; + GST_OBJECT_UNLOCK (downloader); + + if (download != NULL) + GST_INFO_OBJECT (downloader, "URI fetched successfully"); + else + GST_INFO_OBJECT (downloader, "Error fetching URI"); + +quit: + { + gst_uri_downloader_stop (downloader); + g_mutex_unlock (downloader->priv->lock); + return download; + } +} + diff --git a/ext/dash/gsturidownloader.h b/ext/dash/gsturidownloader.h new file mode 100644 index 0000000000..ac16846da8 --- /dev/null +++ b/ext/dash/gsturidownloader.h @@ -0,0 +1,65 @@ +/* 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); +void gst_uri_downloader_free (GstUriDownloader *downloader); + +G_END_DECLS +#endif /* __GSTURIDOWNLOADER_H__ */ +