07070100000000000081A400000000000000000000000169D554BF00000016000000000000000000000000000000000000002200000000gst-rtsp-server-1.28.2/.gitignore *~
/build
/_build
/b/
  07070100000001000081A400000000000000000000000169D554BF0000002A000000000000000000000000000000000000001F00000000gst-rtsp-server-1.28.2/AUTHORS    Wim Taymans <wim.taymans@collabora.co.uk>
  07070100000002000081A400000000000000000000000169D554BF0000673F000000000000000000000000000000000000001F00000000gst-rtsp-server-1.28.2/COPYING    		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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 Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

			    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 Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
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 and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
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 other code 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.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  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, whereas the latter must
be combined with the library in order to run.

		  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) 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.

    d) 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.

    e) 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 materials to be 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 with
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 Lesser 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

           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.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!

 07070100000003000081A400000000000000000000000169D554BF0000673F000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/COPYING.LIB    		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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 Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

			    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 Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
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 and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
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 other code 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.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  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, whereas the latter must
be combined with the library in order to run.

		  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) 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.

    d) 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.

    e) 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 materials to be 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 with
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 Lesser 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

           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.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!

 07070100000004000081A400000000000000000000000169D554BF000000B6000000000000000000000000000000000000001E00000000gst-rtsp-server-1.28.2/README gst-rtsp-server is a library on top of GStreamer for building an RTSP server

There are some examples in the examples/ directory and more comprehensive
documentation in docs/README.
  07070100000005000081A400000000000000000000000169D554BF00000040000000000000000000000000000000000000001C00000000gst-rtsp-server-1.28.2/TODO   
 - use a config file to configure the server
 - error recovery
07070100000006000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000001C00000000gst-rtsp-server-1.28.2/docs   07070100000007000081A400000000000000000000000169D554BF00004EEA000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/docs/README    README 
------

(Last updated on Mon 15 jul 2013, version 0.11.90.1)

This HOWTO describes the basic usage of the GStreamer RTSP libraries and how you
can build simple server applications with it.

* General

 The server relies heavily on the RTSP infrastructure of GStreamer. This includes
 all of the media acquisition, decoding, encoding, payloading and UDP/TCP
 streaming. We use the rtpbin element for all the session management. Most of
 the RTSP message parsing and construction in the server is done using the RTSP
 library that comes with gst-plugins-base. 

 The result is that the server is rather small (a few 11000 lines of code) and easy
 to understand and extend. In its current state of development, things change
 fast, API and ABI are unstable. We encourage people to use it for their various
 use cases and participate by suggesting changes/features.

 Most of the server is built as a library containing a bunch of GObject objects
 that provide reasonable default functionality but has a fair amount of hooks
 to override the default behaviour.

 The server currently integrates with the glib mainloop nicely.  It's currently
 not meant to be used in high-load scenarios and because no security audit has
 been done, you should probably not put it on a public IP address.

* Initialisation

 You need to initialize GStreamer before using any of the RTSP server functions.

   #include <gst/gst.h>

   int
   main (int argc, char *argv[])
   {
     gst_init (&argc, &argv);

     ...
   }
 
 The server itself currently does not have any specific initialisation function
 but that might change in the future.


* Creating the server

 The first thing you want to do is create a new GstRTSPServer object. This object
 will handle all the new client connections to your server once it is added to a
 GMainLoop. You can create a new server object like this:

   #include <gst/rtsp-server/rtsp-server.h>

   GstRTSPServer *server;

   server = gst_rtsp_server_new ();

 The server will by default listen on port 8554 for new connections. This can be
 changed by calling gst_rtsp_server_set_service() or with the 'service' GObject
 property. This makes it possible to run multiple server instances listening on
 multiple ports on one machine.

 We can make the server start listening on its default port by attaching it to a
 mainloop. The following example shows how this is done and will start a server
 on the default 8554 port. For any request we make, we will get a NOT_FOUND
 error code because we need to configure more things before the server becomes
 useful.

   #include <gst/gst.h>
   #include <gst/rtsp-server/rtsp-server.h>

   int
   main (int argc, char *argv[])
   {
     GstRTSPServer *server;
     GMainLoop *loop;

     gst_init (&argc, &argv);

     server = gst_rtsp_server_new ();

     /* make a mainloop for the default context */
     loop = g_main_loop_new (NULL, FALSE);

     /* attach the server to the default maincontext */
     gst_rtsp_server_attach (server, NULL);

     /* start serving */
     g_main_loop_run (loop);
   }

 The server manages four other objects: GstRTSPSessionPool,
 GstRTSPMountPoints, GstRTSPAuth and GstRTSPThreadPool.

 The GstRTSPSessionPool is an object that keeps track of all the active sessions
 in the server. A session will usually be kept for each client that performed a
 SETUP request for a certain media stream. It contains the configuration that
 the client negotiated with the server to receive the particular stream, ie. the
 transport used and port pairs for UDP along with the state of the streaming.
 The default implementation of the session pool is usually sufficient but
 alternative implementation can be used by the server.

 The GstRTSPMountPoints object is more interesting and needs more configuration
 before the server object is useful. This object manages the mapping from a
 request URL to a specific stream and its configuration. We explain in the next
 topic how to configure this object.

 GstRTSPAuth is an object that authenticates users and authorizes actions
 performed by users. By default, a server does not have a GstRTSPAuth object and
 thus does not try to perform any authentication or authorization.

 GstRTSPThreadPool manages the threads used for client connections and media
 pipelines. The server has a default implementation of a threadpool that should
 be sufficient in most cases.


* Making url mount points

 Next we need to define what media is attached to a particular URL. What we want
 to achieve is that when the user asks our server for a specific URL, say /test,
 that we create (or reuse) a GStreamer pipeline that produces one or more RTP
 streams. 
 
 The object that can create such pipeline is called a GstRTSPMediaFactory object. 
 The default implementation of GstRTSPMediaFactory allows you to easily create
 GStreamer pipelines using the gst-launch syntax. It is possible to create a
 GstRTSPMediaFactory subclass that uses different methods for constructing
 pipelines.

 The default GstRTSPMediaFactory can be configured with a gst-launch line that
 produces a toplevel bin (use '(' and ')' around the pipeline description to
 force a toplevel GstBin instead of the default GstPipeline toplevel element).
 The pipeline description should contain elements named payN, one for each
 stream (ex. pay0, pay1, ...). Also, for increased compatibility each stream
 should have a different payload type which can be configured on the payloader.

 The following code snippet illustrates how to create a media factory that
 creates an RTP feed of an H264 encoded test video signal.

   GstRTSPMediaFactory *factory;

   factory = gst_rtsp_media_factory_new ();

   gst_rtsp_media_factory_set_launch (factory, 
                "( videotestsrc ! x264enc ! rtph264pay pt=96 name=pay0 )");

 Now that we have the media factory, we can attach it to a specific url. To do
 this we get the default GstRTSPMountPoints from our server and add the url to
 factory mount points to it like this:

   GstRTSPMountPoints *mounts;

   ...create server..create factory..

   /* get the default mount points from the server */
   mounts = gst_rtsp_server_get_mount_points (server);

   /* attach the video test signal to the "/test" URL */
   gst_rtsp_mount_points_add_factory (mounts, "/test", factory);
   g_object_unref (mounts);

 When starting the server now and directing an RTP client to the URL (like with 
 vlc, mplayer or gstreamer):

   rtsp://localhost:8554/test 

 a test signal will be streamed to the client. The full example code can be
 found in the examples/test-readme.c file.

 Note that by default the factory will create a new pipeline for each client. If
 you want to share a pipeline between clients, use
 gst_rtsp_media_factory_set_shared().


* more on GstRTSPMediaFactory

 The GstRTSPMediaFactory is responsible for creating and caching GstRTSPMedia
 objects. 

 A freshly created GstRTSPMedia object from the factory initially only contains a
 GstElement containing the elements to produce the RTP streams for the media and
 a GPtrArray of GstRTSPStream objects describing the payloader and its source
 pad. The media is unprepared in this state.

 Usually the url will determine what kind of pipeline should be created. You can
 for example use query parameters to configure certain parts of the pipeline or
 select encoders and payloaders based on some url pattern.

 When dealing with a live stream from, for example, a webcam, it can be
 interesting to share the pipeline with multiple clients. This must be done when
 only one instance of the video capture element can be used at a time. In this
 case, the shared property of GstRTSPMedia must be used to instruct the default
 GstRTSPMediaFactory implementation to cache the media.

 When all objects created from a factory can be shared, you can set the shared
 property directly on the factory.

* more on GstRTSPMedia

 After creating the GstRTSPMedia object from the factory, it can be prepared
 with gst_rtsp_media_prepare(). This method will put those objects in a
 GstPipeline and will construct and link the streaming elements and the
 rtpbin session manager object.

 The _prepare() method will then preroll the pipeline in order to figure out the
 caps on the payloaders. After the GstRTSPMedia prerolled it will be in the
 prepared state and can be used for creating SDP files or for streaming to
 clients.

 The prepare method will also create 2 UDP ports for each stream that can be
 used for sending and receiving RTP/RTCP from clients. These port numbers will
 have to be negotiated with the client in the SETUP requests.

 When preparing a GstRTSPMedia, an appsink and asppsrc is also constructed
 for streaming the stream over TCP when requested.

 Media is prepared by the server when DESCRIBE or SETUP requests are received
 from the client.


* the GstRTSPClient object

 When a server detects a new client connection on its port, it will accept the
 connection, check if the connection is allowed and then call the vmethod
 create_client. The default implementation of this function will create
 a new GstRTCPClient object, will configure the session pool, mount points,
 auth and thread pool objects in it.

 The server will then attach the new client to a server mainloop to let it
 handle further communication with the client. In RTSP it is usual to keep
 the connection open between multiple RTSP requests. The client watch will
 be dispatched by the server mainloop when a new GstRTSPMessage is received,
 which will then be handled and a response will be sent.

 The GstRTSPClient object remains alive for as long as a client has a TCP
 connection open with the server. Since is possible for a client to open and close
 the TCP connection between requests, we cannot store the state related
 to the configured RTSP session in the GstRTSPClient object. This server state
 is instead stored in the GstRTSPSession object, identified with the session
 id.


* GstRTSPSession

 This object contains state about a specific RTSP session identified with a
 session id. This state contains the configured streams and their associated
 transports.
 
 When a GstRTSPClient performs a SETUP request, the server will allocate a new
 GstRTSPSession with a unique session id from the GstRTSPSessionPool. The pool
 maintains a list of all existing sessions and makes sure that no session id is
 used multiple times. The session id is sent to the client so that the client
 can refer to its previously configured state by sending the session id in
 further requests.

 A client will then use the session id to configure one or more
 GstRTSPSessionMedia objects, identified by their url. This SessionMedia object
 contains the configuration of a GstRTSPMedia and its configured
 GstRTSPStreamTransport.


* GstRTSPSessionMedia and GstRTSPStreamTransport

 A GstRTSPSessionMedia is identified by a URL and is referenced by a
 GstRTSPSession. It is created as soon as a client performs a SETUP operation on
 a particular URL. It will contain a link to the GstRTSPMedia object associated
 with the URL along with the state of the media and the configured transports
 for each of the streams in the media.

 Each SETUP request performed by the client will configure a
 GstRTSPStreamTransport object linked to by the GstRTSPSessionMedia structure.
 It will contain the transport information needed to send this stream to the
 client. The GstRTSPStreamTransport also contains a link to the GstRTSPStream
 object that generates the actual data to be streamed to the client.

 Note how GstRTSPMedia and GstRTSPStream (the providers of the data to
 stream) are decoupled from GstRTSPSessionMedia and GstRTSPStreamTransport (the
 configuration of how to send this stream to a client) in order to be able to
 send the data of one GstRTSPMedia to multiple clients.


* media control

 After a client has configured the transports for a GstRTSPMedia and its
 GstRTSPStreams, the client can play/pause/stop the stream.

 The GstRTSPMedia object was prepared in the DESCRIBE call (or during SETUP when
 the client skipped the DESCRIBE request). As seen earlier, this configures a
 couple of udpsink and udpsrc elements to respectively send and receive the
 media to clients.

 When a client performs a PLAY request, its configured destination UDP ports are
 added to the GstRTSPStream target destinations, at which point data will
 be sent to the client. The corresponding GstRTSPMedia object will be set to the
 PLAYING state if it was not already in order to send the data to the
 destination.

 The server needs to prepare an RTP-Info header field in the PLAY response,
 which consists of the sequence number and the RTP timestamp of the next RTP
 packet. In order to achive this, the server queries the payloaders for this
 information when it prerolled the pipeline.

 When a client performs a PAUSE request, the destination UDP ports are removed
 from the GstRTSPStream object and the GstRTSPMedia object is set to PAUSED
 if no other destinations are configured anymore.


* seeking

 A seek is performed when a client sends a Range header in the PLAY request.
 This only works when not dealing with shared (live) streams.

 The server performs a GStreamer flushing seek on the media, waits for the
 pipeline to preroll again and then responds to the client after collecting the
 new RTP sequence number and timestamp from the payloaders.


* session management

 The server has to react to clients that suddenly disappear because of network
 problems or otherwise. It needs to make sure that it can reasonably free the
 resources that are used by the various objects in use for streaming when the
 client appears to be gone.

 Each of the GstRTSPSession objects managed by a GstRTSPSessionPool has
 therefore a last_access field that contains the timestamp of when activity from
 a client was last recorded.
 
 Various ways exist to detect activity from a client:

  - RTSP keepalive requests. When a client is receiving RTP data, the RTSP TCP
    connection is largely unused. It is the client's responsibility to
    periodically send keep-alive requests over the TCP channel.

    Whenever a keep-alive request is received by the server (any request that
    contains a session id, usually an OPTION or GET_PARAMETER request) the
    last_access of the session is updated.

  - Since it is not required for a client to keep the RTSP TCP connection open
    while streaming, gst-rtsp-server also detects activity from clients by
    looking at the RTCP messages it receives.

    When an RTCP message is received from a client, the server looks in its list
    of active ports if this message originates from a known host/port pair that
    is currently active in a GstRTSPSession. If this is the case, the session is
    kept alive.

    Since the server does not know anything about the port number that will be
    used by the client to send RTCP, this method does not always work. Later
    RTSP RFCs will include support for negotiating this port number with the
    server. Most clients however use the same port number for sending and
    receiving RTCP exactly for this reason.

 If there was no activity in a particular session for a long time (by default 60
 seconds), the application should remove the session from the pool. For this,
 the application should periodically (say every 2 seconds) check if no sessions
 expired and call gst_rtsp_session_pool_cleanup() to remove them.
 
 When a session is removed from the sessionpool and its last reference is
 unreffed, all related objects and media are destroyed as if a TEARDOWN happened
 from the client.


* TEARDOWN

 A TEARDOWN request will first locate the GstRTSPSessionMedia of the URL. It
 will then remove all transports from the streams, making sure that streaming
 stops to the clients. It will then remove the GstRTSPSessionMedia and
 GstRTSPStreamTransport objects. Finally the GstRTSPSession is released back
 into the pool.

 When there are no more references to the GstRTSPMedia, the media pipeline is
 shut down (with _unprepare) and destroyed. This will then also destroy the
 GstRTSPStream objects.


* Security

 The security of the server and the policy is implemented in a GstRTSPAuth
 object. The object is reponsible for:

  - authenticate the user of the server.

  - check if the current user is authorized to perform an operation.

 For critical operations, the server will call gst_rtsp_auth_check() with
 a string describing the operation which should be validated. The installed
 GstRTSPAuth object is then responsible for checking if the operation is
 allowed.

 Implementations of GstRTSPAuth objects can use the following infrastructure
 bits of the rtsp server to implement these checks:

  - GstRTSPToken: a generic structure describing roles and permissions granted
    to a user.

  - GstRTSPPermissions: a generic list of roles and matching permissions. These
    can be attached to media and factories currently.

 An Auth implementation will usually authenticate a user, using a method such as
 Basic authentication or client certificates or perhaps simply use the IP address.
 The result of the authentication of the user will be a GstRTSPToken that is made
 current in the context of the ongoing request.

 The auth module can then implement the various checks in the server by looking
 at the current token and, if needed, compare it to the required GstRTSPPermissions
 of the current object.

 The security is deliberately kept generic with a default implementation of the
 GstRTSPAuth object providing a usable and simple implementaion. To make more
 complicated security modules, the auth object should be subclassed and new
 implementations for the checks needs to be made.


Objects
-------

GstRTSPServer
 - Toplevel object listening for connections and creating new
   GstRTSPClient objects

GstRTSPClient
 - Handle RTSP Requests from connected clients. All other objects
   are called by this object.

GstRTSPContext
 - Helper structure containing the current state of the request
   handled by the client.


GstRTSPMountPoints
 - Maps a url to a GstRTSPMediaFactory implementation. The default
   implementation uses a simple hashtable to map a url to a factory.

GstRTSPMediaFactory
 - Creates and caches GstRTSPMedia objects. The default implementation
   can create GstRTSPMedia objects based on gst-launch syntax.

GstRTSPMediaFactoryURI
 - Specialized GstRTSPMediaFactory that can stream the content of any
   URI.

GstRTSPMedia
 - The object that contains the media pipeline and various GstRTSPStream
   objects that produce RTP packets

GstRTSPStream
 - Manages the elements to stream a stream of a GstRTSPMedia to one or
   more GstRTSPStreamTransports.


GstRTSPSessionPool
 - Creates and manages GstRTSPSession objects identified by an id.

GstRTSPSession
 - An object containing the various GstRTSPSessionMedia objects managed
   by this session.

GstRTSPSessionMedia
 - The state of a GstRTSPMedia and the configuration of a GstRTSPStream
   objects. The configuration for the GstRTSPStream is stored in
   GstRTSPStreamTransport objects.

GstRTSPStreamTransport
 - Configuration of how a GstRTSPStream is send to a particular client. It
   contains the transport that was negotiated with the client in the SETUP
   request.


GstRTSPSDP
 - helper functions for creating SDP messages from gstRTSPMedia

GstRTSPAddressPool
 - a pool of multicast and unicast addresses used in streaming

GstRTSPThreadPool
 - a pool of threads used for various server tasks such as handling clients and
   managing media pipelines.


GstRTSPAuth
 - Hooks for checking authorizations, all client activity will call this
   object with the GstRTSPContext structure. By default it supports
   basic authentication.

GstRTSPToken
 - Credentials of a user. This contrains the roles that the user is allowed
   to assume and other permissions or capabilities of the user.

GstRTSPPermissions
 - A list of permissions for each role. The permissions are usually attached
   to objects to describe what roles have what permissions.

GstRTSPParams
 - object to handle get and set parameter requests.

  07070100000008000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/docs/design    07070100000009000081A400000000000000000000000169D554BF000005E5000000000000000000000000000000000000003900000000gst-rtsp-server-1.28.2/docs/design/gst-rtp-server-design  RTSP server
-----------

This directory contains an example RTSP server built with various GStreamer
components and libraries. It also uses GStreamer for all of the multimedia
procesing and RTP bits. The following features are implemented:

 - 

Server Design
-------------

The toplevel component of the server is a GstRTSPServer object. This object
creates and binds on the server socket and attaches into the mainloop.

For each request a new GstRTSPClient object is created that will accept the
request and a thread is started to handle further communication with the
client until the connection is closed.

When a client issues a SETUP request we create a GstRTSPSession object,
identified with a sessionid, that will keep track of the state of a client.
The object is destroyed when a TEARDOWN request is made for that sessionid.

We also maintain a pool of URL to media pipeline mappings. Each url is mapped to
an object that is able to provide a pipeline for that media. We provide
pipelines to stream live captured data, on-demand file streaming or on-demand
transcoding of a file or stream.

A pool of currently active pipelines is also maintained. Usually the active
pipelines are in use by one or more GstRTSPSession objects. An active pipeline
becomes inactive when no more sessions refer to it.

A client can choose to start a new pipeline or join a currently active pipeline.
Some active pipeline cannot be joined (such as on-demand streams) but a new
instance of that pipeline can be created.
   0707010000000A000081A400000000000000000000000169D554BF00000018000000000000000000000000000000000000002500000000gst-rtsp-server-1.28.2/docs/index.md  # GStreamer RTSP Server
0707010000000B000081A400000000000000000000000169D554BF00000F7F000000000000000000000000000000000000002800000000gst-rtsp-server-1.28.2/docs/meson.build   build_hotdoc = false

if meson.is_cross_build()
    if get_option('doc').enabled()
        error('Documentation enabled but building the doc while cross building is not supported yet.')
    endif

    message('Documentation not built as building it while cross building is not supported yet.')
    subdir_done()
endif

if static_build
    if get_option('doc').enabled()
        error('Documentation enabled but not supported when building statically.')
    endif

    message('Building statically, can\'t build the documentation')
    subdir_done()
endif

if not build_gir
    if get_option('doc').enabled()
        error('Documentation enabled but introspection not built.')
    endif

    message('Introspection not built, can\'t build the documentation')
    subdir_done()
endif

if gst_dep.type_name() == 'internal'
    gst_proj = subproject('gstreamer')
    plugins_cache_generator = gst_proj.get_variable('plugins_cache_generator')
else
    plugins_cache_generator = find_program('gst-plugins-doc-cache-generator',
        dirs: [join_paths(gst_dep.get_variable('libexecdir', default_value: ''), 'gstreamer-' + api_version)],
        required: false)
endif

plugins_cache = join_paths(meson.current_source_dir(), 'plugins', 'gst_plugins_cache.json')
if plugins.length() == 0
    message('All rtsp-server plugins have been disabled')
elif plugins_cache_generator.found()
    gst_plugins_doc_dep = custom_target('rtsp-server-plugins-doc-cache',
        command: [plugins_cache_generator, plugins_cache, '@OUTPUT@', '@INPUT@'],
        input: plugins,
        output: 'gst_plugins_cache.json',
    )
else
    warning('GStreamer plugin inspector for documentation not found, can\'t update the cache')
endif

if get_option('doc').disabled()
  subdir_done()
endif

build_hotdoc = true

cdir = meson.current_source_dir()
doc_sources = []
foreach s: rtsp_server_sources + rtsp_server_headers
  doc_sources += s.full_path()
endforeach

lib_sources = {
  'rtspserver': pathsep.join(doc_sources)
}

lib_doc_source_file = configure_file(
  output: 'lib_doc_sources.json',
  configuration: lib_sources,
  output_format: 'json')

lib_doc_gi_source_file = configure_file(
  output: 'lib_doc_gi_sources.json',
  configuration: {'rtspserver': rtsp_server_gir[0].full_path()},
  output_format: 'json')

lib_hotdoc_config = custom_target(
  'build-gst-hotdoc-configs',
  command: [
    plugins_cache_generator,
    'hotdoc-lib-config',
    '--srcdir', cdir,
    '--builddir', meson.current_build_dir(),
    '--buildroot', meson.global_build_root(),
    '--project_version', api_version,
    '--gi_source_file', lib_doc_gi_source_file,
    '--gi_c_source_file', lib_doc_source_file,
    '--source_root', cdir,
    '--gi_source_root', cdir / '..' / 'gst' / 'rtsp-server',
    '--output', '@OUTPUT@',
  ],
  output: 'hotdoc-lib-configs.json',
  depends: [rtsp_server_gir[0]],
)

doc_source_file = configure_file(output: 'doc_sources.json', configuration: plugin_sources, output_format: 'json')

plugin_libraries = {}

foreach plugin: plugins
  if plugin.name().startswith('gst')
    plugin_name = plugin.name().substring(3)
  else
    plugin_name = plugin.name()
  endif

  plugin_libraries += {
    plugin_name: plugin.full_path()
  }
endforeach

doc_plugin_libs_file = configure_file(output: 'doc_plugin_libs.json', configuration: plugin_libraries, output_format: 'json')

cdir = meson.current_source_dir()
plugin_hotdoc_configs = custom_target(
  'build-hotdoc-configs',
  command: [
    plugins_cache_generator,
    'hotdoc-config',
    '--builddir', meson.current_build_dir(),
    '--project_version', api_version,
    '--sitemap', cdir / 'plugins/sitemap.txt',
    '--index', cdir / 'plugins/index.md',
    '--gst_index', cdir / 'plugins/index.md',
    '--gst_cache_file', '@INPUT@',
    '--output', '@OUTPUT@',
    '--gst_c_source_file', doc_source_file,
    '--gst_plugin_libraries_file', doc_plugin_libs_file,
  ],
  input: plugins_cache,
  output: 'hotdoc-configs.json',
)
 0707010000000C000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002400000000gst-rtsp-server-1.28.2/docs/plugins   0707010000000D000081A400000000000000000000000169D554BF0000573C000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/docs/plugins/gst_plugins_cache.json    {
    "rtspclientsink": {
        "description": "RTSP client sink element",
        "elements": {
            "rtspclientsink": {
                "author": "Jan Schmidt <jan@centricular.com>",
                "description": "Send data over the network via RTSP RECORD(RFC 2326)",
                "hierarchy": [
                    "GstRTSPClientSink",
                    "GstBin",
                    "GstElement",
                    "GstObject",
                    "GInitiallyUnowned",
                    "GObject"
                ],
                "interfaces": [
                    "GstChildProxy",
                    "GstURIHandler"
                ],
                "klass": "Sink/Network",
                "long-name": "RTSP RECORD client",
                "pad-templates": {
                    "sink_%%u": {
                        "caps": "ANY",
                        "direction": "sink",
                        "presence": "request",
                        "type": "GstRtspClientSinkPad"
                    }
                },
                "properties": {
                    "debug": {
                        "blurb": "Dump request and response messages to stdout",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "false",
                        "mutable": "null",
                        "readable": true,
                        "type": "gboolean",
                        "writable": true
                    },
                    "do-rtsp-keep-alive": {
                        "blurb": "Send RTSP keep alive packets, disable for old incompatible server.",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "true",
                        "mutable": "null",
                        "readable": true,
                        "type": "gboolean",
                        "writable": true
                    },
                    "latency": {
                        "blurb": "Amount of ms to buffer",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "2000",
                        "max": "-1",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint",
                        "writable": true
                    },
                    "location": {
                        "blurb": "Location of the RTSP url to read",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "multicast-iface": {
                        "blurb": "The network interface on which to join the multicast group",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "ntp-time-source": {
                        "blurb": "NTP time source for RTCP packets",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "ntp (0)",
                        "mutable": "null",
                        "readable": true,
                        "type": "GstRTSPClientSinkNtpTimeSource",
                        "writable": true
                    },
                    "port-range": {
                        "blurb": "Client port range that can be used to send RTP data and receive RTCP data, eg. 3000-3005 (NULL = no restrictions)",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "profiles": {
                        "blurb": "Allowed RTSP profiles",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "avp",
                        "mutable": "null",
                        "readable": true,
                        "type": "GstRTSPProfile",
                        "writable": true
                    },
                    "protocols": {
                        "blurb": "Allowed lower transport protocols",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "tcp+udp-mcast+udp",
                        "mutable": "null",
                        "readable": true,
                        "type": "GstRTSPLowerTrans",
                        "writable": true
                    },
                    "proxy": {
                        "blurb": "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "proxy-id": {
                        "blurb": "HTTP proxy URI user id for authentication",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "proxy-pw": {
                        "blurb": "HTTP proxy URI user password for authentication",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "publish-clock-mode": {
                        "blurb": "Clock publishing mode according to RFC7273",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "clock (1)",
                        "mutable": "null",
                        "readable": true,
                        "type": "GstRTSPPublishClockMode",
                        "writable": true
                    },
                    "retry": {
                        "blurb": "Max number of retries when allocating RTP ports.",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "20",
                        "max": "65535",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint",
                        "writable": true
                    },
                    "rtp-blocksize": {
                        "blurb": "RTP package size to suggest to server (0 = disabled)",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "0",
                        "max": "65536",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint",
                        "writable": true
                    },
                    "rtx-time": {
                        "blurb": "Amount of ms to buffer for retransmission. 0 disables retransmission",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "500",
                        "max": "-1",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint",
                        "writable": true
                    },
                    "sdes": {
                        "blurb": "The SDES items of this session",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "mutable": "null",
                        "readable": true,
                        "type": "GstStructure",
                        "writable": true
                    },
                    "tcp-timeout": {
                        "blurb": "Fail after timeout microseconds on TCP connections (0 = disabled)",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "20000000",
                        "max": "18446744073709551615",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint64",
                        "writable": true
                    },
                    "timeout": {
                        "blurb": "Retry TCP transport after UDP timeout microseconds (0 = disabled)",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "5000000",
                        "max": "18446744073709551615",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint64",
                        "writable": true
                    },
                    "tls-database": {
                        "blurb": "TLS database with anchor certificate authorities used to validate the server certificate",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "mutable": "null",
                        "readable": true,
                        "type": "GTlsDatabase",
                        "writable": true
                    },
                    "tls-interaction": {
                        "blurb": "A GTlsInteraction object to prompt the user for password or certificate",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "mutable": "null",
                        "readable": true,
                        "type": "GTlsInteraction",
                        "writable": true
                    },
                    "tls-validation-flags": {
                        "blurb": "TLS certificate validation flags used to validate the server certificate",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "validate-all",
                        "mutable": "null",
                        "readable": true,
                        "type": "GTlsCertificateFlags",
                        "writable": true
                    },
                    "udp-buffer-size": {
                        "blurb": "Size of the kernel UDP receive buffer in bytes, 0=default",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "524288",
                        "max": "2147483647",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "gint",
                        "writable": true
                    },
                    "udp-reconnect": {
                        "blurb": "Reconnect to the server if RTSP connection is closed when doing UDP",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "true",
                        "mutable": "null",
                        "readable": true,
                        "type": "gboolean",
                        "writable": true
                    },
                    "user-agent": {
                        "blurb": "The User-Agent string to send to the server",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "GStreamer/{VERSION}",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "user-id": {
                        "blurb": "RTSP location URI user id for authentication",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    },
                    "user-pw": {
                        "blurb": "RTSP location URI user password for authentication",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "NULL",
                        "mutable": "null",
                        "readable": true,
                        "type": "gchararray",
                        "writable": true
                    }
                },
                "rank": "none",
                "signals": {
                    "accept-certificate": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "GTlsConnection"
                            },
                            {
                                "name": "arg1",
                                "type": "GTlsCertificate"
                            },
                            {
                                "name": "arg2",
                                "type": "GTlsCertificateFlags"
                            }
                        ],
                        "return-type": "gboolean",
                        "when": "last"
                    },
                    "handle-request": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "GstRTSPMessage"
                            },
                            {
                                "name": "arg1",
                                "type": "GstRTSPMessage"
                            }
                        ],
                        "return-type": "void"
                    },
                    "new-manager": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "GstElement"
                            }
                        ],
                        "return-type": "void",
                        "when": "first"
                    },
                    "new-payloader": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "GstElement"
                            }
                        ],
                        "return-type": "void",
                        "when": "first"
                    },
                    "request-rtcp-key": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "guint"
                            }
                        ],
                        "return-type": "GstCaps",
                        "when": "last"
                    },
                    "update-sdp": {
                        "args": [
                            {
                                "name": "arg0",
                                "type": "GstSDPMessage"
                            }
                        ],
                        "return-type": "void"
                    }
                }
            }
        },
        "filename": "gstrtspclientsink",
        "license": "LGPL",
        "other-types": {
            "GstRTSPClientSinkNtpTimeSource": {
                "kind": "enum",
                "values": [
                    {
                        "desc": "NTP time based on realtime clock",
                        "name": "ntp",
                        "value": "0"
                    },
                    {
                        "desc": "UNIX time based on realtime clock",
                        "name": "unix",
                        "value": "1"
                    },
                    {
                        "desc": "Running time based on pipeline clock",
                        "name": "running-time",
                        "value": "2"
                    },
                    {
                        "desc": "Pipeline clock time",
                        "name": "clock-time",
                        "value": "3"
                    }
                ]
            },
            "GstRtspClientSinkPad": {
                "hierarchy": [
                    "GstRtspClientSinkPad",
                    "GstGhostPad",
                    "GstProxyPad",
                    "GstPad",
                    "GstObject",
                    "GInitiallyUnowned",
                    "GObject"
                ],
                "kind": "object",
                "properties": {
                    "payloader": {
                        "blurb": "The payloader element to use (NULL = default automatically selected)",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "mutable": "null",
                        "readable": true,
                        "type": "GstElement",
                        "writable": true
                    },
                    "ulpfec-percentage": {
                        "blurb": "The percentage of ULP redundancy to apply",
                        "conditionally-available": false,
                        "construct": false,
                        "construct-only": false,
                        "controllable": false,
                        "default": "0",
                        "max": "100",
                        "min": "0",
                        "mutable": "null",
                        "readable": true,
                        "type": "guint",
                        "writable": true
                    }
                }
            }
        },
        "package": "GStreamer RTSP Server Library",
        "source": "gst-rtsp-server",
        "tracers": {},
        "url": "Unknown package origin"
    }
}0707010000000E000081A400000000000000000000000169D554BF00000011000000000000000000000000000000000000002D00000000gst-rtsp-server-1.28.2/docs/plugins/index.md  # rtspclientsink
   0707010000000F000081A400000000000000000000000169D554BF0000000A000000000000000000000000000000000000003000000000gst-rtsp-server-1.28.2/docs/plugins/sitemap.txt   gst-index
  07070100000010000081A400000000000000000000000169D554BF00000017000000000000000000000000000000000000002700000000gst-rtsp-server-1.28.2/docs/sitemap.md    gi-index
    gst-index
 07070100000011000081A400000000000000000000000169D554BF00000009000000000000000000000000000000000000002800000000gst-rtsp-server-1.28.2/docs/sitemap.txt   gi-index
   07070100000012000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002000000000gst-rtsp-server-1.28.2/examples   07070100000013000081A400000000000000000000000169D554BF0000043E000000000000000000000000000000000000002C00000000gst-rtsp-server-1.28.2/examples/meson.build   if get_option('examples').disabled() or static_build
  subdir_done()
endif

examples = [
  'test-appsrc',
  'test-appsrc2',
  'test-auth',
  'test-auth-digest',
  'test-launch',
  'test-mp4',
  'test-multicast2',
  'test-multicast',
  'test-netclock',
  'test-netclock-client',
  'test-ogg',
  'test-onvif-backchannel',
  'test-onvif-client',
  'test-onvif-server',
  'test-readme',
  'test-record-auth',
  'test-record',
  'test-replay-server',
  'test-sdp',
  'test-uri',
  'test-video',
  'test-video-rtx',
]

foreach example : examples
  executable(example, '@0@.c'.format(example),
    c_args : rtspserver_args,
    include_directories : rtspserver_incs,
    dependencies : [gst_dep, gstapp_dep, gstnet_dep, gst_rtsp_server_dep],
    install: false)
endforeach

cgroup_dep = dependency('libcgroup', version : '>= 0.26', required : false)
if cgroup_dep.found()
  executable('test-cgroups', 'test-cgroups.c',
    c_args : rtspserver_args,
    include_directories : rtspserver_incs,
    dependencies : [gst_dep, gstnet_dep, gst_rtsp_server_dep, cgroup_dep],
    install: false)
endif
  07070100000014000081A400000000000000000000000169D554BF00001215000000000000000000000000000000000000002E00000000gst-rtsp-server-1.28.2/examples/test-appsrc.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

typedef struct
{
  gboolean white;
  GstClockTime timestamp;
} MyContext;

/* called when we need to give data to appsrc */
static void
need_data (GstElement * appsrc, guint unused, MyContext * ctx)
{
  GstBuffer *buffer;
  guint size;
  GstFlowReturn ret;

  size = 385 * 288 * 2;

  buffer = gst_buffer_new_allocate (NULL, size, NULL);

  /* this makes the image black/white */
  gst_buffer_memset (buffer, 0, ctx->white ? 0xff : 0x0, size);

  ctx->white = !ctx->white;

  /* increment the timestamp every 1/2 second */
  GST_BUFFER_PTS (buffer) = ctx->timestamp;
  GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale_int (1, GST_SECOND, 2);
  ctx->timestamp += GST_BUFFER_DURATION (buffer);

  g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
  gst_buffer_unref (buffer);
}

/* called when a new media pipeline is constructed. We can query the
 * pipeline and configure our appsrc */
static void
media_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media,
    gpointer user_data)
{
  GstElement *element, *appsrc;
  MyContext *ctx;

  /* get the element used for providing the streams of the media */
  element = gst_rtsp_media_get_element (media);

  /* get our appsrc, we named it 'mysrc' with the name property */
  appsrc = gst_bin_get_by_name_recurse_up (GST_BIN (element), "mysrc");

  /* this instructs appsrc that we will be dealing with timed buffer */
  gst_util_set_object_arg (G_OBJECT (appsrc), "format", "time");
  /* configure the caps of the video */
  g_object_set (G_OBJECT (appsrc), "caps",
      gst_caps_new_simple ("video/x-raw",
          "format", G_TYPE_STRING, "RGB16",
          "width", G_TYPE_INT, 384,
          "height", G_TYPE_INT, 288,
          "framerate", GST_TYPE_FRACTION, 0, 1, NULL), NULL);

  ctx = g_new0 (MyContext, 1);
  ctx->white = FALSE;
  ctx->timestamp = 0;
  /* make sure ther datais freed when the media is gone */
  g_object_set_data_full (G_OBJECT (media), "my-extra-data", ctx,
      (GDestroyNotify) g_free);

  /* install the callback that will be called when a buffer is needed */
  g_signal_connect (appsrc, "need-data", (GCallback) need_data, ctx);
  gst_object_unref (appsrc);
  gst_object_unref (element);
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( appsrc name=mysrc ! videoconvert ! x264enc ! rtph264pay name=pay0 pt=96 )");

  /* notify when our media is ready, This is called whenever someone asks for
   * the media and a new pipeline with our appsrc is created */
  g_signal_connect (factory, "media-configure", (GCallback) media_configure,
      NULL);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mounts anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;
}
   07070100000015000081A400000000000000000000000169D554BF00001BA4000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/examples/test-appsrc2.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/app/app.h>

#include <gst/rtsp-server/rtsp-server.h>

typedef struct
{
  GstElement *generator_pipe;
  GstElement *vid_appsink;
  GstElement *vid_appsrc;
  GstElement *aud_appsink;
  GstElement *aud_appsrc;
} MyContext;

/* called when we need to give data to an appsrc */
static void
need_data (GstElement * appsrc, guint unused, MyContext * ctx)
{
  GstSample *sample;
  GstFlowReturn ret;

  if (appsrc == ctx->vid_appsrc)
    sample = gst_app_sink_pull_sample (GST_APP_SINK (ctx->vid_appsink));
  else
    sample = gst_app_sink_pull_sample (GST_APP_SINK (ctx->aud_appsink));

  if (sample) {
    GstBuffer *buffer = gst_sample_get_buffer (sample);
    GstSegment *seg = gst_sample_get_segment (sample);
    GstClockTime pts, dts;

    /* Convert the PTS/DTS to running time so they start from 0 */
    pts = GST_BUFFER_PTS (buffer);
    if (GST_CLOCK_TIME_IS_VALID (pts))
      pts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, pts);

    dts = GST_BUFFER_DTS (buffer);
    if (GST_CLOCK_TIME_IS_VALID (dts))
      dts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, dts);

    if (buffer) {
      /* Make writable so we can adjust the timestamps */
      buffer = gst_buffer_copy (buffer);
      GST_BUFFER_PTS (buffer) = pts;
      GST_BUFFER_DTS (buffer) = dts;
      g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
      gst_buffer_unref (buffer);
    }

    /* we don't need the appsink sample anymore */
    gst_sample_unref (sample);
  }
}

static void
ctx_free (MyContext * ctx)
{
  gst_element_set_state (ctx->generator_pipe, GST_STATE_NULL);

  gst_object_unref (ctx->generator_pipe);
  gst_object_unref (ctx->vid_appsrc);
  gst_object_unref (ctx->vid_appsink);
  gst_object_unref (ctx->aud_appsrc);
  gst_object_unref (ctx->aud_appsink);

  g_free (ctx);
}

/* called when a new media pipeline is constructed. We can query the
 * pipeline and configure our appsrc */
static void
media_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media,
    gpointer user_data)
{
  GstElement *element, *appsrc, *appsink;
  GstCaps *caps;
  MyContext *ctx;

  ctx = g_new0 (MyContext, 1);
  /* This pipeline generates H264 video and PCM audio. The appsinks are kept small so that if delivery is slow,
   * encoded buffers are dropped as needed. There's slightly more buffers (32) allowed for audio */
  ctx->generator_pipe =
      gst_parse_launch
      ("videotestsrc is-live=true ! x264enc speed-preset=superfast tune=zerolatency ! h264parse ! appsink name=vid max-buffers=1 drop=true "
      "audiotestsrc is-live=true ! appsink name=aud max-buffers=32 drop=true",
      NULL);

  /* make sure the data is freed when the media is gone */
  g_object_set_data_full (G_OBJECT (media), "rtsp-extra-data", ctx,
      (GDestroyNotify) ctx_free);

  /* get the element (bin) used for providing the streams of the media */
  element = gst_rtsp_media_get_element (media);

  /* Find the 2 app sources (video / audio), and configure them, connect to the
   * signals to request data */
  /* configure the caps of the video */
  caps = gst_caps_new_simple ("video/x-h264",
      "stream-format", G_TYPE_STRING, "byte-stream",
      "alignment", G_TYPE_STRING, "au",
      "width", G_TYPE_INT, 384, "height", G_TYPE_INT, 288,
      "framerate", GST_TYPE_FRACTION, 15, 1, NULL);
  ctx->vid_appsrc = appsrc =
      gst_bin_get_by_name_recurse_up (GST_BIN (element), "videosrc");
  ctx->vid_appsink = appsink =
      gst_bin_get_by_name (GST_BIN (ctx->generator_pipe), "vid");
  gst_util_set_object_arg (G_OBJECT (appsrc), "format", "time");
  g_object_set (G_OBJECT (appsrc), "caps", caps, NULL);
  g_object_set (G_OBJECT (appsink), "caps", caps, NULL);
  /* install the callback that will be called when a buffer is needed */
  g_signal_connect (appsrc, "need-data", (GCallback) need_data, ctx);
  gst_caps_unref (caps);

  caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S24BE",
      "layout", G_TYPE_STRING, "interleaved", "rate", G_TYPE_INT, 48000,
      "channels", G_TYPE_INT, 2, NULL);
  ctx->aud_appsrc = appsrc =
      gst_bin_get_by_name_recurse_up (GST_BIN (element), "audiosrc");
  ctx->aud_appsink = appsink =
      gst_bin_get_by_name (GST_BIN (ctx->generator_pipe), "aud");
  gst_util_set_object_arg (G_OBJECT (appsrc), "format", "time");
  g_object_set (G_OBJECT (appsrc), "caps", caps, NULL);
  g_object_set (G_OBJECT (appsink), "caps", caps, NULL);
  g_signal_connect (appsrc, "need-data", (GCallback) need_data, ctx);
  gst_caps_unref (caps);

  gst_element_set_state (ctx->generator_pipe, GST_STATE_PLAYING);
  gst_object_unref (element);
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( appsrc name=videosrc ! h264parse ! rtph264pay name=pay0 pt=96 "
      "  appsrc name=audiosrc ! audioconvert ! rtpL24pay name=pay1 pt=97 )");

  /* notify when our media is ready, This is called whenever someone asks for
   * the media and a new pipeline with our appsrc is created */
  g_signal_connect (factory, "media-configure", (GCallback) media_configure,
      NULL);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mounts anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;
}
07070100000016000081A400000000000000000000000169D554BF00001E49000000000000000000000000000000000000003300000000gst-rtsp-server-1.28.2/examples/test-auth-digest.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

static gchar *htdigest_path = NULL;
static gchar *realm = NULL;

static GOptionEntry entries[] = {
  {"htdigest-path", 'h', 0, G_OPTION_ARG_STRING, &htdigest_path,
      "Path to an htdigest file to parse (default: None)", "PATH"},
  {"realm", 'r', 0, G_OPTION_ARG_STRING, &realm,
      "Authentication realm (default: None)", "REALM"},
  {NULL}
};


static gboolean
remove_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
    GstRTSPServer * server)
{
  return GST_RTSP_FILTER_REMOVE;
}

static gboolean
remove_sessions (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;
  GList *sessions G_GNUC_UNUSED;

  g_print ("removing all sessions\n");
  pool = gst_rtsp_server_get_session_pool (server);
  sessions = gst_rtsp_session_pool_filter (pool,
      (GstRTSPSessionPoolFilterFunc) remove_func, server);
  g_object_unref (pool);

  return FALSE;
}

static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  GOptionContext *optctx;
  GError *error = NULL;

  optctx = g_option_context_new (NULL);
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mounts for this server, every server has a default mapper object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);


  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");
  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* allow user and admin to access this resource */
  gst_rtsp_media_factory_add_role (factory, "user",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_media_factory_add_role (factory, "admin",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  /* admin2 can look at the media but not construct so he gets a
   * 401 Unauthorized */
  gst_rtsp_media_factory_add_role (factory, "admin2",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, FALSE, NULL);
  /* Anonymous user can do the same things as admin2 on this resource */
  gst_rtsp_media_factory_add_role (factory, "anonymous",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, FALSE, NULL);

  /* make another factory */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=30/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 )");
  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test2", factory);

  /* allow admin2 to access this resource */
  /* user and admin have no permissions so they can't even see the
   * media and get a 404 Not Found */
  gst_rtsp_media_factory_add_role (factory, "admin2",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* make a new authentication manager */
  auth = gst_rtsp_auth_new ();
  gst_rtsp_auth_set_supported_methods (auth, GST_RTSP_AUTH_DIGEST);

  /* make default token, it has no permissions */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "anonymous", NULL);
  gst_rtsp_auth_set_default_token (auth, token);
  gst_rtsp_token_unref (token);

  /* make user token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_auth_add_digest (auth, "user", "password", token);
  gst_rtsp_token_unref (token);

  if (htdigest_path) {
    token =
        gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
        "user", NULL);

    if (!gst_rtsp_auth_parse_htdigest (auth, htdigest_path, token)) {
      g_printerr ("Could not parse htdigest at %s\n", htdigest_path);
      gst_rtsp_token_unref (token);
      goto failed;
    }

    gst_rtsp_token_unref (token);
  }

  if (realm)
    gst_rtsp_auth_set_realm (auth, realm);

  /* make admin token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "admin", NULL);
  gst_rtsp_auth_add_digest (auth, "admin", "power", token);
  gst_rtsp_token_unref (token);

  /* make admin2 token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "admin2", NULL);
  gst_rtsp_auth_add_digest (auth, "admin2", "power2", token);
  gst_rtsp_token_unref (token);

  /* set as the server authentication manager */
  gst_rtsp_server_set_auth (server, auth);
  g_object_unref (auth);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);
  g_timeout_add_seconds (10, (GSourceFunc) remove_sessions, server);

  /* start serving */
  g_print ("stream with user:password ready at rtsp://127.0.0.1:8554/test\n");
  g_print ("stream with admin:power ready at rtsp://127.0.0.1:8554/test\n");
  g_print ("stream with admin2:power2 ready at rtsp://127.0.0.1:8554/test2\n");

  if (htdigest_path)
    g_print
        ("stream with htdigest users ready at rtsp://127.0.0.1:8554/test\n");

  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
   07070100000017000081A400000000000000000000000169D554BF00001993000000000000000000000000000000000000002C00000000gst-rtsp-server-1.28.2/examples/test-auth.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

static gboolean
remove_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
    GstRTSPServer * server)
{
  return GST_RTSP_FILTER_REMOVE;
}

static gboolean
remove_sessions (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;
  GList *sessions G_GNUC_UNUSED;

  g_print ("removing all sessions\n");
  pool = gst_rtsp_server_get_session_pool (server);
  sessions = gst_rtsp_session_pool_filter (pool,
      (GstRTSPSessionPoolFilterFunc) remove_func, server);
  g_object_unref (pool);

  return FALSE;
}

static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  gchar *basic;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mounts for this server, every server has a default mapper object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);


  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");
  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* allow user and admin to access this resource */
  gst_rtsp_media_factory_add_role (factory, "user",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_media_factory_add_role (factory, "admin",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  /* admin2 can look at the media but not construct so he gets a
   * 401 Unauthorized */
  gst_rtsp_media_factory_add_role (factory, "admin2",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, FALSE, NULL);
  /* Anonymous user can do the same things as admin2 on this resource */
  gst_rtsp_media_factory_add_role (factory, "anonymous",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, FALSE, NULL);

  /* make another factory */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=30/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 )");
  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test2", factory);

  /* allow admin2 to access this resource */
  /* user and admin have no permissions so they can't even see the
   * media and get a 404 Not Found */
  gst_rtsp_media_factory_add_role (factory, "admin2",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* make a new authentication manager */
  auth = gst_rtsp_auth_new ();

  /* make default token, it has no permissions */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "anonymous", NULL);
  gst_rtsp_auth_set_default_token (auth, token);
  gst_rtsp_token_unref (token);

  /* make user token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  basic = gst_rtsp_auth_make_basic ("user", "password");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* make admin token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "admin", NULL);
  basic = gst_rtsp_auth_make_basic ("admin", "power");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* make admin2 token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "admin2", NULL);
  basic = gst_rtsp_auth_make_basic ("admin2", "power2");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* set as the server authentication manager */
  gst_rtsp_server_set_auth (server, auth);
  g_object_unref (auth);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);
  g_timeout_add_seconds (10, (GSourceFunc) remove_sessions, server);

  /* start serving */
  g_print ("stream with user:password ready at rtsp://127.0.0.1:8554/test\n");
  g_print ("stream with admin:power ready at rtsp://127.0.0.1:8554/test\n");
  g_print ("stream with admin2:power2 ready at rtsp://127.0.0.1:8554/test2\n");
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
 07070100000018000081A400000000000000000000000169D554BF000022AE000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/examples/test-cgroups.c    /* GStreamer
 * Copyright (C) 2013 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* Runs a pipeline and clasifies the media pipelines based on the
 * authenticated user.
 *
 * This test requires 2 cpu cgroups to exist named 'user' and 'admin'.
 * The rtsp server should have permission to add its threads to the
 * cgroups.
 *
 *   sudo cgcreate -t uid:gid -g cpu:/user
 *   sudo cgcreate -t uid:gid -g cpu:/admin
 *
 * With -t you can give the user and group access to the task file to
 * write the thread ids. The user running the server can be used.
 *
 * Then you would want to change the cpu shares assigned to each group:
 *
 *   sudo cgset -r cpu.shares=100 user
 *   sudo cgset -r cpu.shares=1024 admin
 *
 * Then start clients for 'user' until the stream is degraded because of
 * lack of CPU. Then start a client for 'admin' and check that the stream
 * is not degraded.
 */

#include <libcgroup.h>

#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>

typedef struct _GstRTSPCGroupPool GstRTSPCGroupPool;
typedef struct _GstRTSPCGroupPoolClass GstRTSPCGroupPoolClass;

#define GST_TYPE_RTSP_CGROUP_POOL              (gst_rtsp_cgroup_pool_get_type ())
#define GST_IS_RTSP_CGROUP_POOL(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_CGROUP_POOL))
#define GST_IS_RTSP_CGROUP_POOL_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_CGROUP_POOL))
#define GST_RTSP_CGROUP_POOL_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_CGROUP_POOL, GstRTSPCGroupPoolClass))
#define GST_RTSP_CGROUP_POOL(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_CGROUP_POOL, GstRTSPCGroupPool))
#define GST_RTSP_CGROUP_POOL_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_CGROUP_POOL, GstRTSPCGroupPoolClass))
#define GST_RTSP_CGROUP_POOL_CAST(obj)         ((GstRTSPCGroupPool*)(obj))
#define GST_RTSP_CGROUP_POOL_CLASS_CAST(klass) ((GstRTSPCGroupPoolClass*)(klass))

struct _GstRTSPCGroupPool
{
  GstRTSPThreadPool parent;

  struct cgroup *user;
  struct cgroup *admin;
};

struct _GstRTSPCGroupPoolClass
{
  GstRTSPThreadPoolClass parent_class;
};

static GQuark thread_cgroup;

static void gst_rtsp_cgroup_pool_finalize (GObject * obj);

static void default_thread_enter (GstRTSPThreadPool * pool,
    GstRTSPThread * thread);
static void default_configure_thread (GstRTSPThreadPool * pool,
    GstRTSPThread * thread, GstRTSPContext * ctx);

static GType gst_rtsp_cgroup_pool_get_type (void);

G_DEFINE_TYPE (GstRTSPCGroupPool, gst_rtsp_cgroup_pool,
    GST_TYPE_RTSP_THREAD_POOL);

static void
gst_rtsp_cgroup_pool_class_init (GstRTSPCGroupPoolClass * klass)
{
  GObjectClass *gobject_class;
  GstRTSPThreadPoolClass *tpool_class;

  gobject_class = G_OBJECT_CLASS (klass);
  tpool_class = GST_RTSP_THREAD_POOL_CLASS (klass);

  gobject_class->finalize = gst_rtsp_cgroup_pool_finalize;

  tpool_class->configure_thread = default_configure_thread;
  tpool_class->thread_enter = default_thread_enter;

  thread_cgroup = g_quark_from_string ("cgroup.pool.thread.cgroup");

  cgroup_init ();
}

static void
gst_rtsp_cgroup_pool_init (GstRTSPCGroupPool * pool)
{
  pool->user = cgroup_new_cgroup ("user");
  if (cgroup_add_controller (pool->user, "cpu") == NULL)
    g_error ("Failed to add cpu controller to user cgroup");
  pool->admin = cgroup_new_cgroup ("admin");
  if (cgroup_add_controller (pool->admin, "cpu") == NULL)
    g_error ("Failed to add cpu controller to admin cgroup");
}

static void
gst_rtsp_cgroup_pool_finalize (GObject * obj)
{
  GstRTSPCGroupPool *pool = GST_RTSP_CGROUP_POOL (obj);

  GST_INFO ("finalize pool %p", pool);

  cgroup_free (&pool->user);
  cgroup_free (&pool->admin);

  G_OBJECT_CLASS (gst_rtsp_cgroup_pool_parent_class)->finalize (obj);
}

static void
default_thread_enter (GstRTSPThreadPool * pool, GstRTSPThread * thread)
{
  struct cgroup *cgroup;

  cgroup = gst_mini_object_get_qdata (GST_MINI_OBJECT (thread), thread_cgroup);
  if (cgroup) {
    gint res = 0;

    res = cgroup_attach_task (cgroup);

    if (res != 0)
      GST_ERROR ("error: %d (%s)", res, cgroup_strerror (res));
  }
}

static void
default_configure_thread (GstRTSPThreadPool * pool,
    GstRTSPThread * thread, GstRTSPContext * ctx)
{
  GstRTSPCGroupPool *cpool = GST_RTSP_CGROUP_POOL (pool);
  const gchar *cls;
  struct cgroup *cgroup;

  if (ctx->token)
    cls = gst_rtsp_token_get_string (ctx->token, "cgroup.pool.media.class");
  else
    cls = NULL;

  GST_DEBUG ("manage cgroup %s", cls);

  if (!g_strcmp0 (cls, "admin"))
    cgroup = cpool->admin;
  else
    cgroup = cpool->user;

  /* attach the cgroup to the thread */
  gst_mini_object_set_qdata (GST_MINI_OBJECT (thread), thread_cgroup,
      cgroup, NULL);
}

static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  gchar *basic;
  GstRTSPThreadPool *thread_pool;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mounts for this server, every server has a default mapper object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=640,height=480,framerate=50/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");
  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* allow user and admin to access this resource */
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_media_factory_add_role (factory, "admin",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* make a new authentication manager */
  auth = gst_rtsp_auth_new ();

  /* make user token */
  token = gst_rtsp_token_new ("cgroup.pool.media.class", G_TYPE_STRING, "user",
      "media.factory.role", G_TYPE_STRING, "user", NULL);
  basic = gst_rtsp_auth_make_basic ("user", "password");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* make admin token */
  token = gst_rtsp_token_new ("cgroup.pool.media.class", G_TYPE_STRING, "admin",
      "media.factory.role", G_TYPE_STRING, "admin", NULL);
  basic = gst_rtsp_auth_make_basic ("admin", "power");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* set as the server authentication manager */
  gst_rtsp_server_set_auth (server, auth);
  g_object_unref (auth);

  thread_pool = g_object_new (GST_TYPE_RTSP_CGROUP_POOL, NULL);
  gst_rtsp_server_set_thread_pool (server, thread_pool);
  g_object_unref (thread_pool);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving */
  g_print ("stream with user:password ready at rtsp://127.0.0.1:8554/test\n");
  g_print ("stream with admin:power ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
  07070100000019000081A400000000000000000000000169D554BF000010A5000000000000000000000000000000000000002E00000000gst-rtsp-server-1.28.2/examples/test-launch.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#define DEFAULT_RTSP_PORT "8554"
#define DEFAULT_MOUNT "/test"
#define DEFAULT_DISABLE_RTCP FALSE

static char *port = (char *) DEFAULT_RTSP_PORT;
static char *mount = (char *) DEFAULT_MOUNT;
static gboolean disable_rtcp = DEFAULT_DISABLE_RTCP;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {"mount", 'm', 0, G_OPTION_ARG_STRING, &mount,
      "Mountpoint (default: " DEFAULT_MOUNT ")", "MOUNT"},
  {"disable-rtcp", '\0', 0, G_OPTION_ARG_NONE, &disable_rtcp,
      "Whether RTCP should be disabled (default false)", NULL},
  {NULL}
};

static gboolean
dump_debug (gpointer user_data)
{
  GstObject *pipe;
  GWeakRef *ref = user_data;
  GstElement *e = g_weak_ref_get (ref);
  if (!e)
    return G_SOURCE_REMOVE;
  pipe = gst_element_get_parent (GST_ELEMENT (e));
  GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pipe), GST_DEBUG_GRAPH_SHOW_ALL,
      "rtsp-server-test-launch");
  return G_SOURCE_CONTINUE;
}

static void
constructed (GstRTSPMediaFactory * self G_GNUC_UNUSED, GstRTSPMedia * m,
    gpointer user_data G_GNUC_UNUSED)
{
  GstElement *e = gst_rtsp_media_get_element (m);
  GWeakRef *ref = g_new0 (GWeakRef, 1);
  g_weak_ref_set (ref, e);
  dump_debug (ref);
  g_timeout_add_seconds (5, dump_debug, ref);
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;

  optctx = g_option_context_new ("<launch line> - Test RTSP Server, Launch\n\n"
      "Example: \"( videotestsrc ! x264enc ! rtph264pay name=pay0 pt=96 )\"");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();
  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, argv[1]);
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  gst_rtsp_media_factory_set_enable_rtcp (factory, !disable_rtcp);

  g_signal_connect (factory, "media-constructed", G_CALLBACK (constructed),
      NULL);

  /* attach the test factory to the mount url */
  gst_rtsp_mount_points_add_factory (mounts, mount, factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:%s%s\n", port, mount);
  g_main_loop_run (loop);

  return 0;
}
   0707010000001A000081A400000000000000000000000169D554BF000014E9000000000000000000000000000000000000002B00000000gst-rtsp-server-1.28.2/examples/test-mp4.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#define DEFAULT_RTSP_PORT "8554"

static char *port = (char *) DEFAULT_RTSP_PORT;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {NULL}
};

/* called when a stream has received an RTCP packet from the client */
static void
on_ssrc_active (GObject * session, GObject * source, GstRTSPMedia * media)
{
  GstStructure *stats;

  GST_INFO ("source %p in session %p is active", source, session);

  g_object_get (source, "stats", &stats, NULL);
  if (stats) {
    gchar *sstr;

    sstr = gst_structure_to_string (stats);
    g_print ("structure: %s\n", sstr);
    g_free (sstr);

    gst_structure_free (stats);
  }
}

static void
on_sender_ssrc_active (GObject * session, GObject * source,
    GstRTSPMedia * media)
{
  GstStructure *stats;

  GST_INFO ("source %p in session %p is active", source, session);

  g_object_get (source, "stats", &stats, NULL);
  if (stats) {
    gchar *sstr;

    sstr = gst_structure_to_string (stats);
    g_print ("Sender stats:\nstructure: %s\n", sstr);
    g_free (sstr);

    gst_structure_free (stats);
  }
}

/* signal callback when the media is prepared for streaming. We can get the
 * session manager for each of the streams and connect to some signals. */
static void
media_prepared_cb (GstRTSPMedia * media)
{
  guint i, n_streams;

  n_streams = gst_rtsp_media_n_streams (media);

  GST_INFO ("media %p is prepared and has %u streams", media, n_streams);

  for (i = 0; i < n_streams; i++) {
    GstRTSPStream *stream;
    GObject *session;

    stream = gst_rtsp_media_get_stream (media, i);
    if (stream == NULL)
      continue;

    session = gst_rtsp_stream_get_rtpsession (stream);
    GST_INFO ("watching session %p on stream %u", session, i);

    g_signal_connect (session, "on-ssrc-active",
        (GCallback) on_ssrc_active, media);
    g_signal_connect (session, "on-sender-ssrc-active",
        (GCallback) on_sender_ssrc_active, media);
  }
}

static void
media_configure_cb (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
{
  /* connect our prepared signal so that we can see when this media is
   * prepared for streaming */
  g_signal_connect (media, "prepared", (GCallback) media_prepared_cb, factory);
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  gchar *str;

  optctx = g_option_context_new ("<filename.mp4> - Test RTSP Server, MP4");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }

  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();
  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  str = g_strdup_printf ("( "
      "filesrc location=\"%s\" ! qtdemux name=d "
      "d. ! queue ! rtph264pay pt=96 name=pay0 "
      "d. ! queue ! rtpmp4apay pt=97 name=pay1 " ")", argv[1]);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, str);
  g_signal_connect (factory, "media-configure", (GCallback) media_configure_cb,
      factory);
  g_free (str);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port);
  g_main_loop_run (loop);

  return 0;
}
   0707010000001B000081A400000000000000000000000169D554BF00000C6F000000000000000000000000000000000000003100000000gst-rtsp-server-1.28.2/examples/test-multicast.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>


static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *pool;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");

  gst_rtsp_media_factory_set_shared (factory, TRUE);

  /* make a new address pool */
  pool = gst_rtsp_address_pool_new ();
  gst_rtsp_address_pool_add_range (pool,
      "224.3.0.0", "224.3.0.10", 5000, 5010, 16);
  gst_rtsp_media_factory_set_address_pool (factory, pool);
  /* only allow multicast */
  gst_rtsp_media_factory_set_protocols (factory,
      GST_RTSP_LOWER_TRANS_UDP_MCAST);
  g_object_unref (pool);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
 0707010000001C000081A400000000000000000000000169D554BF00000E25000000000000000000000000000000000000003200000000gst-rtsp-server-1.28.2/examples/test-multicast2.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>


static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

static void
media_constructed (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
{
  guint i, n_streams;

  n_streams = gst_rtsp_media_n_streams (media);

  for (i = 0; i < n_streams; i++) {
    GstRTSPAddressPool *pool;
    GstRTSPStream *stream;
    gchar *min, *max;

    stream = gst_rtsp_media_get_stream (media, i);

    /* make a new address pool */
    pool = gst_rtsp_address_pool_new ();

    min = g_strdup_printf ("224.3.0.%d", (2 * i) + 1);
    max = g_strdup_printf ("224.3.0.%d", (2 * i) + 2);
    gst_rtsp_address_pool_add_range (pool, min, max,
        5000 + (10 * i), 5010 + (10 * i), 1);
    g_free (min);
    g_free (max);

    gst_rtsp_stream_set_address_pool (stream, pool);
    g_object_unref (pool);
  }
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");

  gst_rtsp_media_factory_set_shared (factory, TRUE);

  g_signal_connect (factory, "media-constructed", (GCallback)
      media_constructed, NULL);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
   0707010000001D000081A400000000000000000000000169D554BF00000FC4000000000000000000000000000000000000003700000000gst-rtsp-server-1.28.2/examples/test-netclock-client.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <stdlib.h>

#include <gst/gst.h>
#include <gst/net/gstnet.h>

#define PLAYBACK_DELAY_MS 40

static void
source_created (GstElement * pipe, GstElement * source)
{
  g_object_set (source, "latency", PLAYBACK_DELAY_MS,
      "ntp-time-source", 3, "buffer-mode", 4, "ntp-sync", TRUE, NULL);
}

static gboolean
message (GstBus * bus, GstMessage * message, gpointer user_data)
{
  GMainLoop *loop = user_data;

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:{
      GError *err = NULL;
      gchar *name, *debug = NULL;

      name = gst_object_get_path_string (message->src);
      gst_message_parse_error (message, &err, &debug);

      g_printerr ("ERROR: from element %s: %s\n", name, err->message);
      if (debug != NULL)
        g_printerr ("Additional debug info:\n%s\n", debug);

      g_error_free (err);
      g_free (debug);
      g_free (name);

      g_main_loop_quit (loop);
      break;
    }
    case GST_MESSAGE_WARNING:{
      GError *err = NULL;
      gchar *name, *debug = NULL;

      name = gst_object_get_path_string (message->src);
      gst_message_parse_warning (message, &err, &debug);

      g_printerr ("ERROR: from element %s: %s\n", name, err->message);
      if (debug != NULL)
        g_printerr ("Additional debug info:\n%s\n", debug);

      g_error_free (err);
      g_free (debug);
      g_free (name);
      break;
    }
    case GST_MESSAGE_EOS:
      g_print ("Got EOS\n");
      g_main_loop_quit (loop);
      break;
    default:
      break;
  }

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GstClock *net_clock;
  gchar *server;
  gint clock_port;
  GstElement *pipe;
  GMainLoop *loop;

  gst_init (&argc, &argv);

  if (argc < 2) {
    g_print ("usage: %s rtsp://URI clock-IP clock-PORT\n"
        "example: %s rtsp://localhost:8554/test 127.0.0.1 8554\n",
        argv[0], argv[0]);
    return -1;
  }

  server = argv[2];
  clock_port = atoi (argv[3]);

  net_clock = gst_net_client_clock_new ("net_clock", server, clock_port, 0);
  if (net_clock == NULL) {
    g_print ("Failed to create net clock client for %s:%d\n",
        server, clock_port);
    return 1;
  }

  /* Wait for the clock to stabilise */
  gst_clock_wait_for_sync (net_clock, GST_CLOCK_TIME_NONE);

  loop = g_main_loop_new (NULL, FALSE);

  pipe = gst_element_factory_make ("playbin", NULL);
  g_object_set (pipe, "uri", argv[1], NULL);
  g_signal_connect (pipe, "source-setup", G_CALLBACK (source_created), NULL);

  gst_pipeline_use_clock (GST_PIPELINE (pipe), net_clock);

  /* Set this high enough so that it's higher than the minimum latency
   * on all receivers */
  gst_pipeline_set_latency (GST_PIPELINE (pipe), 500 * GST_MSECOND);

  if (gst_element_set_state (pipe,
          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
    g_print ("Failed to set state to PLAYING\n");
    goto exit;
  };

  gst_bus_add_signal_watch (GST_ELEMENT_BUS (pipe));
  g_signal_connect (GST_ELEMENT_BUS (pipe), "message", G_CALLBACK (message),
      loop);

  g_main_loop_run (loop);

exit:
  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);
  g_main_loop_unref (loop);

  return 0;
}
0707010000001E000081A400000000000000000000000169D554BF00000F19000000000000000000000000000000000000003000000000gst-rtsp-server-1.28.2/examples/test-netclock.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/net/gstnettimeprovider.h>
#include <gst/rtsp-server/rtsp-server.h>

GstClock *global_clock;

#define TEST_TYPE_RTSP_MEDIA_FACTORY      (test_rtsp_media_factory_get_type ())
#define TEST_TYPE_RTSP_MEDIA              (test_rtsp_media_get_type ())

GType test_rtsp_media_get_type (void);

typedef struct TestRTSPMediaClass TestRTSPMediaClass;
typedef struct TestRTSPMedia TestRTSPMedia;

struct TestRTSPMediaClass
{
  GstRTSPMediaClass parent;
};

struct TestRTSPMedia
{
  GstRTSPMedia parent;
};

static gboolean custom_setup_rtpbin (GstRTSPMedia * media, GstElement * rtpbin);

G_DEFINE_TYPE (TestRTSPMedia, test_rtsp_media, GST_TYPE_RTSP_MEDIA);

static void
test_rtsp_media_class_init (TestRTSPMediaClass * test_klass)
{
  GstRTSPMediaClass *klass = (GstRTSPMediaClass *) (test_klass);
  klass->setup_rtpbin = custom_setup_rtpbin;
}

static void
test_rtsp_media_init (TestRTSPMedia * media)
{
}

static gboolean
custom_setup_rtpbin (GstRTSPMedia * media, GstElement * rtpbin)
{
  g_object_set (rtpbin, "ntp-time-source", 3, NULL);
  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GstNetTimeProvider *provider;

  gst_init (&argc, &argv);

  if (argc < 2) {
    g_print ("usage: %s <launch line> \n"
        "example: %s \"( videotestsrc is-live=true ! x264enc ! rtph264pay name=pay0 pt=96 )\"\n"
        "Pipeline must be live for synchronisation to work properly with this method!\n",
        argv[0], argv[0]);
    return -1;
  }

  loop = g_main_loop_new (NULL, FALSE);

  global_clock = gst_system_clock_obtain ();
  provider = gst_net_time_provider_new (global_clock, "0.0.0.0", 8554);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, argv[1]);
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  gst_rtsp_media_factory_set_media_gtype (factory, TEST_TYPE_RTSP_MEDIA);
  gst_rtsp_media_factory_set_clock (factory, global_clock);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  gst_clear_object (&provider);

  return 0;
}
   0707010000001F000081A400000000000000000000000169D554BF00000C2D000000000000000000000000000000000000002B00000000gst-rtsp-server-1.28.2/examples/test-ogg.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#define DEFAULT_RTSP_PORT "8554"

static char *port = (char *) DEFAULT_RTSP_PORT;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {NULL}
};

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  gchar *str;

  optctx = g_option_context_new ("<filename.ogg> - Test RTSP Server, OGG");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();
  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  str = g_strdup_printf ("( "
      "filesrc location=%s ! oggdemux name=d "
      "d. ! queue ! rtptheorapay name=pay0 pt=96 "
      "d. ! queue ! rtpvorbispay name=pay1 pt=97 " ")", argv[1]);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, str);
  g_free (str);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port);
  g_main_loop_run (loop);

  return 0;
}
   07070100000020000081A400000000000000000000000169D554BF00000A91000000000000000000000000000000000000003900000000gst-rtsp-server-1.28.2/examples/test-onvif-backchannel.c  /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-onvif-server.h>

#include <string.h>

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_onvif_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_onvif_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc is-live=true ! x264enc ! rtph264pay name=pay0 pt=96 audiotestsrc is-live=true ! mulawenc ! rtppcmupay name=pay1 )");
  gst_rtsp_onvif_media_factory_set_backchannel_launch
      (GST_RTSP_ONVIF_MEDIA_FACTORY (factory),
      "( capsfilter caps=\"application/x-rtp, media=audio, payload=0, clock-rate=8000, encoding-name=PCMU\" name=depay_backchannel ! rtppcmudepay ! fakesink async=false  )");
  gst_rtsp_media_factory_set_shared (factory, FALSE);
  gst_rtsp_media_factory_set_media_gtype (factory, GST_TYPE_RTSP_ONVIF_MEDIA);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;
}
   07070100000021000081A400000000000000000000000169D554BF00005823000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/examples/test-onvif-client.c   /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <stdio.h>

#include <gst/gst.h>
#include <gst/rtsp/rtsp.h>

/* Uncomment to enable audio output */
// #define OUTPUT_AUDIO

typedef struct
{
  gchar *range;
  gdouble speed;
  gchar *frames;
  gchar *rate_control;
  gboolean reverse;
} SeekParameters;

typedef struct
{
  GstElement *src;
  GstElement *vconv;            /* decodebin -> vconv */

  GstElement *sink;
  GstElement *pipe;
  SeekParameters *seek_params;
  GMainLoop *loop;
  GIOChannel *io;
  gboolean new_range;
  guint io_watch_id;
  gboolean reset_sync;
} Context;

typedef struct
{
  const gchar *name;
  gboolean has_argument;
  const gchar *help;
    gboolean (*func) (Context * ctx, gchar * arg, gboolean * async);
} Command;

static gboolean cmd_help (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_pause (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_play (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_reverse (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_range (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_speed (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_frames (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_rate_control (Context * ctx, gchar * arg, gboolean * async);
static gboolean cmd_step_forward (Context * ctx, gchar * arg, gboolean * async);

static Command commands[] = {
  {"help", FALSE, "Display list of valid commands", cmd_help},
  {"pause", FALSE, "Pause playback", cmd_pause},
  {"play", FALSE, "Resume playback", cmd_play},
  {"reverse", FALSE, "Reverse playback direction", cmd_reverse},
  {"range", TRUE,
        "Seek to the specified range, example: \"range: 19000101T000000Z-19000101T000200Z\"",
      cmd_range},
  {"speed", TRUE, "Set the playback speed, example: \"speed: 1.0\"", cmd_speed},
  {"frames", TRUE,
        "Set the frames trickmode, example: \"frames: intra\", \"frames: predicted\", \"frames: intra/1000\"",
      cmd_frames},
  {"rate-control", TRUE,
        "Set the rate control mode, example: \"rate-control: no\"",
      cmd_rate_control},
  {"s", FALSE, "Step to the following frame (in current playback direction)",
      cmd_step_forward},
  {NULL},
};

static gchar *rtsp_address;

#define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
G_STMT_START { \
  if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
    GST_ERROR ("Could not create element %s", name); \
    goto label; \
  } \
  if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
    GST_ERROR ("Could not add element %s", name); \
    goto label; \
  } \
} G_STMT_END

#define DEFAULT_RANGE "19000101T000000Z-19000101T000200Z"
#define DEFAULT_SPEED 1.0
#define DEFAULT_FRAMES "none"
#define DEFAULT_RATE_CONTROL "yes"
#define DEFAULT_REVERSE FALSE

static void
decode_pad_added_cb (GstElement * src, GstPad * srcpad, Context * ctx)
{
  /* Try to link to video output */
  GstCaps *caps = gst_pad_get_current_caps (srcpad);
  if (caps == NULL)
    goto no_caps;

  const GstStructure *s = gst_caps_get_structure (caps, 0);
  if (s == NULL) {
    goto no_caps;
  }

  GstElement *peer = NULL;
  if (gst_structure_has_name (s, "video/x-raw")) {
    GST_INFO ("Have decoded video stream");
    peer = ctx->vconv;
  }
#if defined(OUTPUT_AUDIO)
  else if (gst_structure_has_name (s, "audio/x-raw")) {
    GstElement *aconv, *asink;

    GST_INFO ("Have decoded audio stream");
    MAKE_AND_ADD (aconv, ctx->pipe, "audioconvert", fail, NULL);
    MAKE_AND_ADD (asink, ctx->pipe, "autoaudiosink", fail, NULL);

    gst_element_set_state (aconv, GST_STATE_PLAYING);
    gst_element_set_state (asink, GST_STATE_PLAYING);

    if (!gst_element_link (aconv, asink)) {
      goto fail;
    }
    peer = aconv;
  }
#endif

  gst_caps_replace (&caps, NULL);

  if (peer != NULL) {
    GstPad *sinkpad = gst_element_get_static_pad (peer, "sink");
    if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) {
      goto not_negotiated;
    }
    gst_object_unref (sinkpad);
  }

  return;

no_caps:
  GST_ELEMENT_ERROR (ctx->pipe, CORE, NEGOTIATION, (NULL),
      ("decoded pad %" GST_PTR_FORMAT " did not have caps", srcpad));
  gst_caps_replace (&caps, NULL);
  return;
not_negotiated:
  GST_ELEMENT_ERROR (ctx->pipe, CORE, NEGOTIATION, (NULL),
      ("Failed to link decoded pad %" GST_PTR_FORMAT " to output", srcpad));
  return;
#if defined(OUTPUT_AUDIO)
fail:
  GST_ELEMENT_ERROR (ctx->pipe, CORE, NEGOTIATION, (NULL),
      ("Failed to connect %" GST_PTR_FORMAT " to output", srcpad));
  return;
#endif
}

static void
pad_added_cb (GstElement * src, GstPad * srcpad, Context * ctx)
{
  GstCaps *caps = gst_pad_get_current_caps (srcpad);
  if (caps == NULL)
    goto no_caps;

  const GstStructure *s = gst_caps_get_structure (caps, 0);
  if (s == NULL) {
    goto no_caps;
  }

  /* If the media is not audio or video, ignore it. (Could put ONVIF metadata to an appsink here though */
  const gchar *media = gst_structure_get_string (s, "media");
  if (media == NULL || (!g_str_equal (media, "video") &&
          !g_str_equal (media, "audio"))) {
    GST_INFO ("Ignoring rtsp pad %" GST_PTR_FORMAT " with caps %"
        GST_PTR_FORMAT, srcpad, caps);
    return;
  }

  GstElement *queue, *onvifparse, *dec;

  /* Create decode chain */
  MAKE_AND_ADD (queue, ctx->pipe, "queue", fail, NULL);
  MAKE_AND_ADD (onvifparse, ctx->pipe, "rtponvifparse", fail, NULL);
  MAKE_AND_ADD (dec, ctx->pipe, "decodebin", fail, NULL);

  gst_element_set_state (queue, GST_STATE_PLAYING);
  gst_element_set_state (onvifparse, GST_STATE_PLAYING);
  gst_element_set_state (dec, GST_STATE_PLAYING);

  g_signal_connect (dec, "pad-added", G_CALLBACK (decode_pad_added_cb), ctx);

  if (!gst_element_link_many (queue, onvifparse, dec, NULL)) {
    goto fail;
  }

  /* Connect srcpad to decode chain */
  GstPad *sinkpad = gst_element_get_static_pad (queue, "sink");
  if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) {
    goto fail;
  }
  gst_object_unref (sinkpad);

  return;

no_caps:
  GST_ELEMENT_ERROR (ctx->pipe, CORE, NEGOTIATION, (NULL),
      ("decoded pad %" GST_PTR_FORMAT " did not have caps", srcpad));
  return;
fail:
  GST_ELEMENT_ERROR (ctx->pipe, CORE, NEGOTIATION, (NULL),
      ("Could not link input stream to decoders handling pad %" GST_PTR_FORMAT,
          srcpad));
  return;
}

static gboolean
setup (Context * ctx)
{
  GstElement *toverlay, *tee, *vqueue;
  gboolean ret = FALSE;

  MAKE_AND_ADD (ctx->src, ctx->pipe, "rtspsrc", done, NULL);

  MAKE_AND_ADD (ctx->vconv, ctx->pipe, "videoconvert", done, NULL);
  MAKE_AND_ADD (toverlay, ctx->pipe, "timeoverlay", done, NULL);
  MAKE_AND_ADD (tee, ctx->pipe, "tee", done, NULL);
  MAKE_AND_ADD (vqueue, ctx->pipe, "queue", done, NULL);
  MAKE_AND_ADD (ctx->sink, ctx->pipe, "autovideosink", done, NULL);

  g_object_set (ctx->src, "location", rtsp_address, NULL);
  g_object_set (ctx->src, "onvif-mode", TRUE, NULL);
  g_object_set (ctx->src, "tcp-timeout", 0, NULL);
  g_object_set (toverlay, "show-times-as-dates", TRUE, NULL);

  g_object_set (toverlay, "datetime-format", "%a %d, %b %Y - %T", NULL);

  g_signal_connect (ctx->src, "pad-added", G_CALLBACK (pad_added_cb), ctx);

  if (!gst_element_link_many (ctx->vconv, toverlay, tee, vqueue, ctx->sink,
          NULL)) {
    goto done;
  }

  g_object_set (ctx->src, "onvif-rate-control", FALSE, "is-live", FALSE, NULL);

  if (!g_strcmp0 (ctx->seek_params->rate_control, "no")) {
    g_object_set (ctx->sink, "sync", FALSE, NULL);
  }

  ret = TRUE;

done:
  return ret;
}

/* Retrieve the actual sink from inside an auto*sink */
static GstElement *
get_actual_sink (GstElement * autosink)
{
  GstPad *sinkpad = gst_element_get_static_pad (autosink, "sink");
  GstPad *actual_sinkpad = gst_ghost_pad_get_target (GST_GHOST_PAD (sinkpad));
  gst_object_unref (GST_OBJECT (sinkpad));

  if (actual_sinkpad == NULL) {
    return NULL;
  }

  GstElement *actual_sink =
      (GstElement *) gst_object_get_parent (GST_OBJECT (actual_sinkpad));
  gst_object_unref (GST_OBJECT (actual_sinkpad));

  return actual_sink;
}

static GstClockTime
get_current_position (Context * ctx, gboolean reverse)
{
  GstSample *sample = NULL;
  GstBuffer *buffer;
  GstClockTime ret;

  GstElement *sink = get_actual_sink (ctx->sink);
  if (sink == NULL) {
    return 0;
  }

  g_object_get (sink, "last-sample", &sample, NULL);
  gst_object_unref (sink);

  if (sample == NULL) {
    return 0;
  }

  buffer = gst_sample_get_buffer (sample);

  ret = GST_BUFFER_PTS (buffer);

  if (reverse && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer)))
    ret += GST_BUFFER_DURATION (buffer);

  gst_sample_unref (sample);

  return ret;
}

static GstEvent *
translate_seek_parameters (Context * ctx, SeekParameters * seek_params)
{
  GstEvent *ret = NULL;
  gchar *range_str = NULL;
  GstRTSPTimeRange *rtsp_range;
  GstSeekType start_type, stop_type;
  GstClockTime start, stop;
  gdouble rate;
  GstSeekFlags flags;
  gchar **split = NULL;
  GstClockTime trickmode_interval = 0;

  range_str = g_strdup_printf ("clock=%s", seek_params->range);

  if (gst_rtsp_range_parse (range_str, &rtsp_range) != GST_RTSP_OK) {
    GST_ERROR ("Failed to parse range %s", range_str);
    goto done;
  }

  gst_rtsp_range_get_times (rtsp_range, &start, &stop);

  if (start > stop) {
    GST_ERROR ("Invalid range, start > stop: %s", seek_params->range);
    goto done;
  }

  start_type = GST_SEEK_TYPE_SET;
  stop_type = GST_SEEK_TYPE_SET;

  if (!ctx->new_range) {
    GstClockTime current_position =
        get_current_position (ctx, seek_params->reverse);

    if (seek_params->reverse) {
      stop_type = GST_SEEK_TYPE_SET;
      stop = current_position;
    } else {
      start_type = GST_SEEK_TYPE_SET;
      start = current_position;
    }
  }

  ctx->new_range = FALSE;

  flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE;

  split = g_strsplit (seek_params->frames, "/", 2);

  if (!g_strcmp0 (split[0], "intra")) {
    if (split[1]) {
      guint64 interval;
      gchar *end;

      interval = g_ascii_strtoull (split[1], &end, 10);

      if (!end || *end != '\0') {
        GST_ERROR ("Unexpected interval value %s", split[1]);
        goto done;
      }

      trickmode_interval = interval * GST_MSECOND;
    }
    flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
  } else if (!g_strcmp0 (split[0], "predicted")) {
    if (split[1]) {
      GST_ERROR ("Predicted frames mode does not allow an interval (%s)",
          seek_params->frames);
      goto done;
    }
    flags |= GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED;
  } else if (g_strcmp0 (split[0], "none")) {
    GST_ERROR ("Invalid frames mode (%s)", seek_params->frames);
    goto done;
  }

  if (seek_params->reverse) {
    rate = -1.0 * seek_params->speed;
  } else {
    rate = 1.0 * seek_params->speed;
  }

  ret = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
      start_type, start, stop_type, stop);

  if (trickmode_interval)
    gst_event_set_seek_trickmode_interval (ret, trickmode_interval);

done:
  if (split)
    g_strfreev (split);
  g_free (range_str);
  return ret;
}

static void prompt_on (Context * ctx);
static void prompt_off (Context * ctx);

static gboolean
cmd_help (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = TRUE;
  guint i;

  *async = FALSE;

  for (i = 0; commands[i].name; i++) {
    g_print ("%s: %s\n", commands[i].name, commands[i].help);
  }

  return ret;
}

static gboolean
cmd_pause (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret;
  GstStateChangeReturn state_ret;

  g_print ("Pausing\n");

  state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PAUSED);

  *async = state_ret == GST_STATE_CHANGE_ASYNC;
  ret = state_ret != GST_STATE_CHANGE_FAILURE;

  return ret;
}

static gboolean
cmd_play (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret;
  GstStateChangeReturn state_ret;

  g_print ("Playing\n");

  state_ret = gst_element_set_state (ctx->pipe, GST_STATE_PLAYING);

  *async = state_ret == GST_STATE_CHANGE_ASYNC;
  ret = state_ret != GST_STATE_CHANGE_FAILURE;

  return ret;
}

static gboolean
do_seek (Context * ctx)
{
  gboolean ret = FALSE;
  GstEvent *event;

  if (!(event = translate_seek_parameters (ctx, ctx->seek_params))) {
    GST_ERROR ("Failed to create seek event");
    goto done;
  }

  if (ctx->seek_params->reverse)
    g_object_set (ctx->src, "onvif-rate-control", FALSE, NULL);

  if (ctx->reset_sync) {
    g_object_set (ctx->sink, "sync", TRUE, NULL);
    ctx->reset_sync = FALSE;
  }

  if (!gst_element_send_event (ctx->src, event)) {
    GST_ERROR ("Failed to seek rtspsrc");
    g_main_loop_quit (ctx->loop);
    goto done;
  }

  ret = TRUE;

done:
  return ret;
}

static gboolean
cmd_reverse (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = TRUE;

  g_print ("Reversing playback direction\n");

  ctx->seek_params->reverse = !ctx->seek_params->reverse;

  ret = do_seek (ctx);

  *async = ret == TRUE;

  return ret;
}

static gboolean
cmd_range (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = TRUE;

  g_print ("Switching to new range\n");

  g_free (ctx->seek_params->range);
  ctx->seek_params->range = g_strdup (arg);
  ctx->new_range = TRUE;

  ret = do_seek (ctx);

  *async = ret == TRUE;

  return ret;
}

static gboolean
cmd_speed (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = FALSE;
  gchar *endptr = NULL;
  gdouble new_speed;

  new_speed = g_ascii_strtod (arg, &endptr);

  g_print ("Switching gears\n");

  if (endptr == NULL || *endptr != '\0' || new_speed <= 0.0) {
    GST_ERROR ("Invalid value for speed: %s", arg);
    goto done;
  }

  ctx->seek_params->speed = new_speed;
  ret = do_seek (ctx);

done:
  *async = ret == TRUE;
  return ret;
}

static gboolean
cmd_frames (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = TRUE;

  g_print ("Changing Frames trickmode\n");

  g_free (ctx->seek_params->frames);
  ctx->seek_params->frames = g_strdup (arg);
  ret = do_seek (ctx);
  *async = ret == TRUE;

  return ret;
}

static gboolean
cmd_rate_control (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = FALSE;

  *async = FALSE;

  if (!g_strcmp0 (arg, "no")) {
    g_object_set (ctx->sink, "sync", FALSE, NULL);
    ret = TRUE;
  } else if (!g_strcmp0 (arg, "yes")) {
    /* TODO: there probably is a solution that doesn't involve sending
     * a request to the server to reset our position */
    ctx->reset_sync = TRUE;
    ret = do_seek (ctx);
    *async = TRUE;
  } else {
    GST_ERROR ("Invalid rate-control: %s", arg);
    goto done;
  }

  ret = TRUE;

done:
  return ret;
}

static gboolean
cmd_step_forward (Context * ctx, gchar * arg, gboolean * async)
{
  gboolean ret = FALSE;
  GstEvent *event;

  event = gst_event_new_step (GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE);

  g_print ("Stepping\n");

  if (!gst_element_send_event (ctx->sink, event)) {
    GST_ERROR ("Failed to step forward");
    goto done;
  }

  ret = TRUE;

done:
  *async = ret == TRUE;
  return ret;
}

static void
handle_command (Context * ctx, gchar * cmd)
{
  gchar **split;
  guint i;
  gboolean valid_command = FALSE;

  split = g_strsplit (cmd, ":", 0);

  cmd = g_strstrip (split[0]);

  if (cmd == NULL || *cmd == '\0') {
    g_print ("> ");
    goto done;
  }

  for (i = 0; commands[i].name; i++) {
    if (!g_strcmp0 (commands[i].name, cmd)) {
      valid_command = TRUE;
      if (commands[i].has_argument && g_strv_length (split) != 2) {
        g_print ("Command %s expects exactly one argument:\n%s: %s\n", cmd,
            commands[i].name, commands[i].help);
      } else if (!commands[i].has_argument && g_strv_length (split) != 1) {
        g_print ("Command %s expects no argument:\n%s: %s\n", cmd,
            commands[i].name, commands[i].help);
      } else {
        gboolean async = FALSE;

        if (commands[i].func (ctx,
                commands[i].has_argument ? g_strstrip (split[1]) : NULL, &async)
            && async)
          prompt_off (ctx);
        else
          g_print ("> ");
      }
      break;
    }
  }

  if (!valid_command) {
    g_print ("Invalid command %s\n> ", cmd);
  }

done:
  g_strfreev (split);
}

static gboolean
io_callback (GIOChannel * io, GIOCondition condition, Context * ctx)
{
  gboolean ret = TRUE;
  gchar *str;
  GError *error = NULL;

  switch (condition) {
    case G_IO_PRI:
    case G_IO_IN:
      switch (g_io_channel_read_line (io, &str, NULL, NULL, &error)) {
        case G_IO_STATUS_ERROR:
          GST_ERROR ("Failed to read commands from stdin: %s", error->message);
          g_clear_error (&error);
          g_main_loop_quit (ctx->loop);
          break;
        case G_IO_STATUS_EOF:
          g_print ("EOF received, bye\n");
          g_main_loop_quit (ctx->loop);
          break;
        case G_IO_STATUS_AGAIN:
          break;
        case G_IO_STATUS_NORMAL:
          handle_command (ctx, str);
          g_free (str);
          break;
      }
      break;
    case G_IO_ERR:
    case G_IO_HUP:
      GST_ERROR ("Failed to read commands from stdin");
      g_main_loop_quit (ctx->loop);
      break;
    case G_IO_OUT:
    default:
      break;
  }

  return ret;
}

#ifndef STDIN_FILENO
#ifdef G_OS_WIN32
#define STDIN_FILENO _fileno(stdin)
#else /* !G_OS_WIN32 */
#define STDIN_FILENO 0
#endif /* G_OS_WIN32 */
#endif /* STDIN_FILENO */

static void
prompt_on (Context * ctx)
{
  g_assert (!ctx->io);
  ctx->io = g_io_channel_unix_new (STDIN_FILENO);
  ctx->io_watch_id =
      g_io_add_watch (ctx->io, G_IO_IN, (GIOFunc) io_callback, ctx);
  g_print ("> ");
}

static void
prompt_off (Context * ctx)
{
  g_assert (ctx->io);
  g_source_remove (ctx->io_watch_id);
  g_io_channel_unref (ctx->io);
  ctx->io = NULL;
}

static gboolean
bus_message_cb (GstBus * bus, GstMessage * message, Context * ctx)
{
  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_STATE_CHANGED:{
      GstState olds, news, pendings;

      if (GST_MESSAGE_SRC (message) == GST_OBJECT (ctx->pipe)) {
        gst_message_parse_state_changed (message, &olds, &news, &pendings);
        GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (ctx->pipe),
            GST_DEBUG_GRAPH_SHOW_ALL, "playing");
      }
      break;
    }
    case GST_MESSAGE_ERROR:{
      GError *error = NULL;
      gchar *debug;

      gst_message_parse_error (message, &error, &debug);

      gst_printerr ("Error: %s (%s)\n", error->message, debug);
      g_clear_error (&error);
      g_free (debug);
      g_main_loop_quit (ctx->loop);
      break;
    }
    case GST_MESSAGE_LATENCY:{
      gst_bin_recalculate_latency (GST_BIN (ctx->pipe));
      break;
    }
    case GST_MESSAGE_ASYNC_DONE:{
      prompt_on (ctx);
    }
    default:
      break;
  }

  return TRUE;
}

int
main (int argc, char **argv)
{
  GOptionContext *optctx;
  Context ctx = { 0, };
  GstBus *bus;
  gint ret = 1;
  GError *error = NULL;
  const gchar *range = NULL;
  const gchar *frames = NULL;
  const gchar *rate_control = NULL;
  gchar *default_speed =
      g_strdup_printf ("Speed to request (default: %.1f)", DEFAULT_SPEED);
  SeekParameters seek_params =
      { NULL, DEFAULT_SPEED, NULL, NULL, DEFAULT_REVERSE };
  GOptionEntry entries[] = {
    {"range", 0, 0, G_OPTION_ARG_STRING, &range,
        "Range to seek (default: " DEFAULT_RANGE ")", "RANGE"},
    {"speed", 0, 0, G_OPTION_ARG_DOUBLE, &seek_params.speed,
        default_speed, "SPEED"},
    {"frames", 0, 0, G_OPTION_ARG_STRING, &frames,
        "Frames to request (default: " DEFAULT_FRAMES ")", "FRAMES"},
    {"rate-control", 0, 0, G_OPTION_ARG_STRING, &rate_control,
        "Apply rate control on the client side (default: "
          DEFAULT_RATE_CONTROL ")", "RATE_CONTROL"},
    {"reverse", 0, 0, G_OPTION_ARG_NONE, &seek_params.reverse,
        "Playback direction", ""},
    {NULL}
  };

  optctx = g_option_context_new ("<rtsp-url> - ONVIF RTSP Client");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }
  rtsp_address = argv[1];
  g_option_context_free (optctx);

  seek_params.range = g_strdup (range ? range : DEFAULT_RANGE);
  seek_params.frames = g_strdup (frames ? frames : DEFAULT_FRAMES);
  seek_params.rate_control =
      g_strdup (rate_control ? rate_control : DEFAULT_RATE_CONTROL);

  if (seek_params.speed <= 0.0) {
    GST_ERROR ("SPEED must be a positive number");
    return 1;
  }

  ctx.seek_params = &seek_params;
  ctx.new_range = TRUE;
  ctx.reset_sync = FALSE;

  ctx.pipe = gst_pipeline_new (NULL);
  if (!setup (&ctx)) {
    g_printerr ("Damn\n");
    goto done;
  }

  g_print ("Type help for the list of available commands\n");

  do_seek (&ctx);

  ctx.loop = g_main_loop_new (NULL, FALSE);

  bus = gst_pipeline_get_bus (GST_PIPELINE (ctx.pipe));
  gst_bus_add_watch (bus, (GstBusFunc) bus_message_cb, &ctx);

  /* This will make rtspsrc progress to the OPEN state, at which point we can seek it */
  if (!gst_element_set_state (ctx.pipe, GST_STATE_PLAYING))
    goto done;

  g_main_loop_run (ctx.loop);

  g_main_loop_unref (ctx.loop);

  gst_bus_remove_watch (bus);
  gst_object_unref (bus);
  gst_element_set_state (ctx.pipe, GST_STATE_NULL);
  gst_object_unref (ctx.pipe);

  ret = 0;

done:
  g_free (seek_params.range);
  g_free (seek_params.frames);
  g_free (seek_params.rate_control);
  g_free (default_speed);
  return ret;
}
 07070100000022000081A400000000000000000000000169D554BF000043DD000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/examples/test-onvif-server.c   /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#include "test-onvif-server.h"

GST_DEBUG_CATEGORY_STATIC (onvif_server_debug);
#define GST_CAT_DEFAULT (onvif_server_debug)

#define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
G_STMT_START { \
  if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
    GST_ERROR ("Could not create element %s", name); \
    goto label; \
  } \
  if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
    GST_ERROR ("Could not add element %s", name); \
    goto label; \
  } \
} G_STMT_END

/* This simulates an archive of recordings running from 01-01-1900 to 01-01-2000.
 *
 * This is implemented by repeating the file provided at the command line, with
 * an empty interval of 5 seconds in-between. We intercept relevant events to
 * translate them, and update the timestamps on the output buffers.
 */

#define INTERVAL (5 * GST_SECOND)

/* January the first, 2000 */
#define END_DATE 3155673600 * GST_SECOND

static gchar *filename;

struct _ReplayBin
{
  GstBin parent;

  GstEvent *incoming_seek;
  GstEvent *outgoing_seek;
  GstClockTime trickmode_interval;

  GstSegment segment;
  const GstSegment *incoming_segment;
  gboolean sent_segment;
  GstClockTime ts_offset;
  gint64 remainder;
  GstClockTime min_pts;
};

G_DEFINE_TYPE (ReplayBin, replay_bin, GST_TYPE_BIN);

static void
replay_bin_init (ReplayBin * self)
{
  self->incoming_seek = NULL;
  self->outgoing_seek = NULL;
  self->trickmode_interval = 0;
  self->ts_offset = 0;
  self->sent_segment = FALSE;
  self->min_pts = GST_CLOCK_TIME_NONE;
}

static void
replay_bin_class_init (ReplayBinClass * klass)
{
}

static GstElement *
replay_bin_new (void)
{
  return GST_ELEMENT (g_object_new (replay_bin_get_type (), NULL));
}

static void
demux_pad_added_cb (GstElement * demux, GstPad * pad, GstGhostPad * ghost)
{
  GstCaps *caps = gst_pad_get_current_caps (pad);
  GstStructure *s = gst_caps_get_structure (caps, 0);

  if (gst_structure_has_name (s, "video/x-h264")) {
    gst_ghost_pad_set_target (ghost, pad);
  }

  gst_caps_unref (caps);
}

static void
query_seekable (GstPad * ghost, gint64 * start, gint64 * stop)
{
  GstPad *target;
  GstQuery *query;
  GstFormat format;
  gboolean seekable;

  target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghost));

  query = gst_query_new_seeking (GST_FORMAT_TIME);

  gst_pad_query (target, query);

  gst_query_parse_seeking (query, &format, &seekable, start, stop);

  g_assert (seekable);

  gst_object_unref (target);
}

static GstEvent *
translate_seek (ReplayBin * self, GstPad * pad, GstEvent * ievent)
{
  GstEvent *oevent = NULL;
  gdouble rate;
  GstFormat format;
  GstSeekFlags flags;
  GstSeekType start_type, stop_type;
  gint64 start, stop;
  gint64 istart, istop;         /* Incoming */
  gint64 ustart, ustop;         /* Upstream */
  gint64 ostart, ostop;         /* Outgoing */
  guint32 seqnum = gst_event_get_seqnum (ievent);

  gst_event_parse_seek (ievent, &rate, &format, &flags, &start_type, &start,
      &stop_type, &stop);

  if (!GST_CLOCK_TIME_IS_VALID (stop))
    stop = END_DATE;

  gst_event_parse_seek_trickmode_interval (ievent, &self->trickmode_interval);

  istart = start;
  istop = stop;

  query_seekable (pad, &ustart, &ustop);

  if (rate > 0) {
    /* First, from where we should seek the file */
    ostart = istart % (ustop + INTERVAL);

    /* This may end up in our empty interval */
    if (ostart > ustop) {
      istart += ostart - ustop;
      ostart = 0;
    }

    /* Then, up to where we should seek it */
    ostop = MIN (ustop, ostart + (istop - istart));
  } else {
    /* First up to where we should seek the file */
    ostop = istop % (ustop + INTERVAL);

    /* This may end up in our empty interval */
    if (ostop > ustop) {
      istop -= ostop - ustop;
      ostop = ustop;
    }

    ostart = MAX (0, ostop - (istop - istart));
  }

  /* We may be left with nothing to actually play, in this
   * case we won't seek upstream, and emit the expected events
   * ourselves */
  if (istart > istop) {
    GstSegment segment;
    GstEvent *event;
    gboolean update;

    event = gst_event_new_flush_start ();
    gst_event_set_seqnum (event, seqnum);
    gst_pad_push_event (pad, event);

    event = gst_event_new_flush_stop (TRUE);
    gst_event_set_seqnum (event, seqnum);
    gst_pad_push_event (pad, event);

    gst_segment_init (&segment, format);
    gst_segment_do_seek (&segment, rate, format, flags, start_type, start,
        stop_type, stop, &update);

    event = gst_event_new_segment (&segment);
    gst_event_set_seqnum (event, seqnum);
    gst_pad_push_event (pad, event);

    event = gst_event_new_eos ();
    gst_event_set_seqnum (event, seqnum);
    gst_pad_push_event (pad, event);

    goto done;
  }

  /* Lastly, how much will remain to play back (this remainder includes the interval) */
  if (stop - start > ostop - ostart)
    self->remainder = (stop - start) - (ostop - ostart);

  flags |= GST_SEEK_FLAG_SEGMENT;

  oevent =
      gst_event_new_seek (rate, format, flags, start_type, ostart, stop_type,
      ostop);
  gst_event_set_seek_trickmode_interval (oevent, self->trickmode_interval);
  gst_event_set_seqnum (oevent, seqnum);

  GST_DEBUG ("Translated event to %" GST_PTR_FORMAT
      " (remainder: %" G_GINT64_FORMAT ")", oevent, self->remainder);

done:
  return oevent;
}

static gboolean
replay_bin_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
{
  ReplayBin *self = REPLAY_BIN (parent);
  gboolean ret = TRUE;
  gboolean forward = TRUE;

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
    {
      GST_DEBUG ("Processing seek event %" GST_PTR_FORMAT, event);

      self->incoming_seek = event;

      gst_event_replace (&self->outgoing_seek, NULL);
      self->sent_segment = FALSE;

      event = translate_seek (self, pad, event);

      if (!event)
        forward = FALSE;
      else
        self->outgoing_seek = gst_event_ref (event);
      break;
    }
    default:
      break;
  }

  if (forward)
    return gst_pad_event_default (pad, parent, event);
  else
    return ret;
}

static gboolean
replay_bin_query_func (GstPad * pad, GstObject * parent, GstQuery * query)
{
  ReplayBin *self = REPLAY_BIN (parent);
  gboolean ret = TRUE;
  gboolean forward = TRUE;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_SEEKING:
      /* We are seekable from the beginning till the end of time */
      gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0,
          GST_CLOCK_TIME_NONE);
      forward = FALSE;
      break;
    case GST_QUERY_SEGMENT:
      gst_query_set_segment (query, self->segment.rate, self->segment.format,
          self->segment.start, self->segment.stop);
      forward = FALSE;
    default:
      break;
  }

  GST_DEBUG ("Processed query %" GST_PTR_FORMAT, query);

  if (forward)
    return gst_pad_query_default (pad, parent, query);
  else
    return ret;
}

static GstEvent *
translate_segment (GstPad * pad, GstEvent * ievent)
{
  ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
  GstEvent *ret;
  gdouble irate, orate;
  GstFormat iformat, oformat;
  GstSeekFlags iflags, oflags;
  GstSeekType istart_type, ostart_type, istop_type, ostop_type;
  gint64 istart, ostart, istop, ostop;
  gboolean update;

  gst_event_parse_segment (ievent, &self->incoming_segment);

  if (!self->outgoing_seek) {
    GstSegment segment;
    gboolean update;

    gst_segment_init (&segment, GST_FORMAT_TIME);

    gst_segment_do_seek (&segment, 1.0, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_SET,
        0, GST_SEEK_TYPE_SET, END_DATE, &update);

    ret = gst_event_new_segment (&segment);
    gst_event_unref (ievent);
    goto done;
  }

  if (!self->sent_segment) {
    gst_event_parse_seek (self->incoming_seek, &irate, &iformat, &iflags,
        &istart_type, &istart, &istop_type, &istop);
    gst_event_parse_seek (self->outgoing_seek, &orate, &oformat, &oflags,
        &ostart_type, &ostart, &ostop_type, &ostop);

    if (istop == -1)
      istop = END_DATE;

    if (self->incoming_segment->rate > 0)
      self->ts_offset = istart - ostart;
    else
      self->ts_offset = istop - ostop;

    istart += self->incoming_segment->start - ostart;
    istop += self->incoming_segment->stop - ostop;

    gst_segment_init (&self->segment, self->incoming_segment->format);

    gst_segment_do_seek (&self->segment, self->incoming_segment->rate,
        self->incoming_segment->format,
        (GstSeekFlags) self->incoming_segment->flags, GST_SEEK_TYPE_SET,
        (guint64) istart, GST_SEEK_TYPE_SET, (guint64) istop, &update);

    self->min_pts = istart;

    ret = gst_event_new_segment (&self->segment);

    self->sent_segment = TRUE;

    gst_event_unref (ievent);

    GST_DEBUG ("Translated segment: %" GST_PTR_FORMAT ", "
        "ts_offset: %" G_GUINT64_FORMAT, ret, self->ts_offset);
  } else {
    ret = NULL;
  }

done:
  return ret;
}

static void
handle_segment_done (ReplayBin * self, GstPad * pad)
{
  GstEvent *event;

  if (self->remainder < INTERVAL) {
    self->remainder = 0;
    event = gst_event_new_eos ();
    gst_event_set_seqnum (event, gst_event_get_seqnum (self->incoming_seek));
    gst_pad_push_event (pad, event);
  } else {
    gint64 ustart, ustop;
    gint64 ostart, ostop;
    GstPad *target;
    GstStructure *s;

    /* Signify the end of a contiguous section of recording */
    s = gst_structure_new ("GstOnvifTimestamp",
        "ntp-offset", G_TYPE_UINT64, 0, "discont", G_TYPE_BOOLEAN, TRUE, NULL);

    event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);

    gst_pad_push_event (pad, event);

    query_seekable (pad, &ustart, &ustop);

    self->remainder -= INTERVAL;

    if (self->incoming_segment->rate > 0) {
      ostart = 0;
      ostop = MIN (ustop, self->remainder);
    } else {
      ostart = MAX (ustop - self->remainder, 0);
      ostop = ustop;
    }

    self->remainder = MAX (self->remainder - ostop - ostart, 0);

    event =
        gst_event_new_seek (self->segment.rate, self->segment.format,
        self->segment.flags & ~GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, ostart,
        GST_SEEK_TYPE_SET, ostop);
    gst_event_set_seek_trickmode_interval (event, self->trickmode_interval);

    if (self->incoming_segment->rate > 0)
      self->ts_offset += INTERVAL + ustop;
    else
      self->ts_offset -= INTERVAL + ustop;

    GST_DEBUG ("New offset: %" GST_TIME_FORMAT,
        GST_TIME_ARGS (self->ts_offset));

    GST_DEBUG ("Seeking to %" GST_PTR_FORMAT, event);
    target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
    gst_pad_send_event (target, event);
    gst_object_unref (target);
  }
}

static GstPadProbeReturn
replay_bin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
{
  ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
  GstPadProbeReturn ret = GST_PAD_PROBE_OK;

  GST_DEBUG ("Probed %" GST_PTR_FORMAT, info->data);

  switch (GST_EVENT_TYPE (info->data)) {
    case GST_EVENT_SEGMENT:
    {
      GstEvent *translated;

      GST_DEBUG ("Probed segment %" GST_PTR_FORMAT, info->data);

      translated = translate_segment (pad, GST_EVENT (info->data));
      if (translated)
        info->data = translated;
      else
        ret = GST_PAD_PROBE_HANDLED;

      break;
    }
    case GST_EVENT_SEGMENT_DONE:
    {
      handle_segment_done (self, pad);
      ret = GST_PAD_PROBE_HANDLED;
      break;
    }
    default:
      break;
  }

  return ret;
}

static GstPadProbeReturn
replay_bin_buffer_probe (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
{
  ReplayBin *self = REPLAY_BIN (GST_OBJECT_PARENT (pad));
  GstPadProbeReturn ret = GST_PAD_PROBE_OK;

  if (GST_BUFFER_PTS (info->data) > self->incoming_segment->stop) {
    ret = GST_PAD_PROBE_DROP;
    goto done;
  }

  if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (info->data)))
    GST_BUFFER_PTS (info->data) += self->ts_offset;
  if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (info->data)))
    GST_BUFFER_DTS (info->data) += self->ts_offset;

  GST_LOG ("Pushing buffer %" GST_PTR_FORMAT, info->data);

done:
  return ret;
}

static GstElement *
create_replay_bin (GstElement * parent)
{
  GstElement *ret, *src, *demux;
  GstPad *ghost;

  ret = replay_bin_new ();
  if (!gst_bin_add (GST_BIN (parent), ret)) {
    gst_object_unref (ret);
    goto fail;
  }

  MAKE_AND_ADD (src, ret, "filesrc", fail, NULL);
  MAKE_AND_ADD (demux, ret, "qtdemux", fail, NULL);

  ghost = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC);
  gst_element_add_pad (ret, ghost);

  gst_pad_set_event_function (ghost, replay_bin_event_func);
  gst_pad_add_probe (ghost, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
      replay_bin_event_probe, NULL, NULL);
  gst_pad_add_probe (ghost, GST_PAD_PROBE_TYPE_BUFFER, replay_bin_buffer_probe,
      NULL, NULL);
  gst_pad_set_query_function (ghost, replay_bin_query_func);

  if (!gst_element_link (src, demux))
    goto fail;

  g_object_set (src, "location", filename, NULL);
  g_signal_connect (demux, "pad-added", G_CALLBACK (demux_pad_added_cb), ghost);

done:
  return ret;

fail:
  ret = NULL;
  goto done;
}

/* A simple factory to set up our replay bin */

struct _OnvifFactory
{
  GstRTSPOnvifMediaFactory parent;
};

G_DEFINE_TYPE (OnvifFactory, onvif_factory, GST_TYPE_RTSP_MEDIA_FACTORY);

static void
onvif_factory_init (OnvifFactory * factory)
{
}

static GstElement *
onvif_factory_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstElement *replay_bin, *q1, *parse, *pay, *onvifts, *q2;
  GstElement *ret = gst_bin_new (NULL);
  GstElement *pbin = gst_bin_new ("pay0");
  GstPad *sinkpad, *srcpad;

  if (!(replay_bin = create_replay_bin (ret)))
    goto fail;

  MAKE_AND_ADD (q1, pbin, "queue", fail, NULL);
  MAKE_AND_ADD (parse, pbin, "h264parse", fail, NULL);
  MAKE_AND_ADD (pay, pbin, "rtph264pay", fail, NULL);
  MAKE_AND_ADD (onvifts, pbin, "rtponviftimestamp", fail, NULL);
  MAKE_AND_ADD (q2, pbin, "queue", fail, NULL);

  gst_bin_add (GST_BIN (ret), pbin);

  if (!gst_element_link_many (q1, parse, pay, onvifts, q2, NULL))
    goto fail;

  sinkpad = gst_element_get_static_pad (q1, "sink");
  gst_element_add_pad (pbin, gst_ghost_pad_new ("sink", sinkpad));
  gst_object_unref (sinkpad);

  if (!gst_element_link (replay_bin, pbin))
    goto fail;

  srcpad = gst_element_get_static_pad (q2, "src");
  gst_element_add_pad (pbin, gst_ghost_pad_new ("src", srcpad));
  gst_object_unref (srcpad);

  g_object_set (onvifts, "set-t-bit", TRUE, "set-e-bit", TRUE, "ntp-offset",
      G_GUINT64_CONSTANT (0), "drop-out-of-segment", FALSE, NULL);

  gst_element_set_clock (onvifts, gst_system_clock_obtain ());

done:
  return ret;

fail:
  gst_object_unref (ret);
  ret = NULL;
  goto done;
}

static void
onvif_factory_class_init (OnvifFactoryClass * klass)
{
  GstRTSPMediaFactoryClass *mf_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);

  mf_class->create_element = onvif_factory_create_element;
}

static GstRTSPMediaFactory *
onvif_factory_new (void)
{
  GstRTSPMediaFactory *result;

  result =
      GST_RTSP_MEDIA_FACTORY (g_object_new (onvif_factory_get_type (), NULL));

  return result;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  gchar *service;

  optctx = g_option_context_new ("<filename.mp4> - ONVIF RTSP Server, MP4");
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }
  filename = argv[1];
  g_option_context_free (optctx);

  GST_DEBUG_CATEGORY_INIT (onvif_server_debug, "onvif-server", 0,
      "ONVIF server");

  loop = g_main_loop_new (NULL, FALSE);

  server = gst_rtsp_onvif_server_new ();

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = onvif_factory_new ();
  gst_rtsp_media_factory_set_media_gtype (factory, GST_TYPE_RTSP_ONVIF_MEDIA);

  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  g_object_unref (mounts);

  gst_rtsp_server_attach (server, NULL);

  service = gst_rtsp_server_get_service (server);
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", service);
  g_free (service);
  g_main_loop_run (loop);

  return 0;
}
   07070100000023000081A400000000000000000000000169D554BF00000441000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/examples/test-onvif-server.h   /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE (ReplayBin, replay_bin, REPLAY, BIN, GstBin);

G_DECLARE_FINAL_TYPE (OnvifFactory, onvif_factory, ONVIF, FACTORY,
    GstRTSPOnvifMediaFactory);

G_END_DECLS
   07070100000024000081A400000000000000000000000169D554BF000008C4000000000000000000000000000000000000002E00000000gst-rtsp-server-1.28.2/examples/test-readme.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc is-live=1 ! x264enc ! rtph264pay name=pay0 pt=96 )");

  gst_rtsp_media_factory_set_shared (factory, TRUE);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
  g_main_loop_run (loop);

  return 0;
}
07070100000025000081A400000000000000000000000169D554BF00001A94000000000000000000000000000000000000003300000000gst-rtsp-server-1.28.2/examples/test-record-auth.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

/* define this if you want the server to use TLS */
//#define WITH_TLS

#define DEFAULT_RTSP_PORT "8554"

static char *port = (char *) DEFAULT_RTSP_PORT;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {NULL}
};

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  gchar *basic;
#ifdef WITH_TLS
  GTlsCertificate *cert;
#endif

  optctx = g_option_context_new ("<launch line> - Test RTSP Server, Launch\n\n"
      "Example: \"( decodebin name=depay0 ! autovideosink )\"");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    return -1;
  }

  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();
  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named depay%d. Each
   * element with depay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_transport_mode (factory,
      GST_RTSP_TRANSPORT_MODE_RECORD);
  gst_rtsp_media_factory_set_launch (factory, argv[1]);
  gst_rtsp_media_factory_set_latency (factory, 2000);
#ifdef WITH_TLS
  gst_rtsp_media_factory_set_profiles (factory,
      GST_RTSP_PROFILE_SAVP | GST_RTSP_PROFILE_SAVPF);
#else
  gst_rtsp_media_factory_set_profiles (factory,
      GST_RTSP_PROFILE_AVP | GST_RTSP_PROFILE_AVPF);
#endif

  /* allow user to access this resource */
  gst_rtsp_media_factory_add_role (factory, "user",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  /* Anonymous users can see but not construct, so get UNAUTHORIZED */
  gst_rtsp_media_factory_add_role (factory, "anonymous",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, FALSE, NULL);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* Set up the auth for user account */
  /* make a new authentication manager */
  auth = gst_rtsp_auth_new ();
#ifdef WITH_TLS
  cert = g_tls_certificate_new_from_pem ("-----BEGIN CERTIFICATE-----"
      "MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk"
      "ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp"
      "ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq"
      "hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx"
      "NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW"
      "B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3"
      "DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf"
      "hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC"
      "MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1"
      "Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk"
      "PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH"
      "QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4="
      "-----END CERTIFICATE-----"
      "-----BEGIN PRIVATE KEY-----"
      "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA2EnE8ZOeVwZmwzPc"
      "88DvoK1ckhOK7nVrsx9j6TmyKJ6m34UaAGpSlZbhvY72xyPNXl8QnUjm79SgT9bG"
      "zeUc6QIDAQABAkBRFJZ32VbqWMP9OVwDJLiwC01AlYLnka0mIQZbT/2xq9dUc9GW"
      "U3kiVw4lL8v/+sPjtTPCYYdzHHOyDen6znVhAiEA9qJT7BtQvRxCvGrAhr9MS022"
      "tTdPbW829BoUtIeH64cCIQDggG5i48v7HPacPBIH1RaSVhXl8qHCpQD3qrIw3FMw"
      "DwIga8PqH5Sf5sHedy2+CiK0V4MRfoU4c3zQ6kArI+bEgSkCIQCLA1vXBiE31B5s"
      "bdHoYa1BXebfZVd+1Hd95IfEM5mbRwIgSkDuQwV55BBlvWph3U8wVIMIb4GStaH8"
      "W535W8UBbEg=" "-----END PRIVATE KEY-----", -1, &error);
  if (cert == NULL) {
    g_printerr ("failed to parse PEM: %s\n", error->message);
    return -1;
  }
  gst_rtsp_auth_set_tls_certificate (auth, cert);
  g_object_unref (cert);
#endif

  /* make default token - anonymous unauthenticated access */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "anonymous", NULL);
  gst_rtsp_auth_set_default_token (auth, token);
  gst_rtsp_token_unref (token);

  /* make user token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  basic = gst_rtsp_auth_make_basic ("user", "password");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* set as the server authentication manager */
  gst_rtsp_server_set_auth (server, auth);
  g_object_unref (auth);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
#ifdef WITH_TLS
  g_print ("stream ready at rtsps://127.0.0.1:%s/test\n", port);
#else
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port);
#endif
  g_main_loop_run (loop);

  return 0;
}
07070100000026000081A400000000000000000000000169D554BF00000DAD000000000000000000000000000000000000002E00000000gst-rtsp-server-1.28.2/examples/test-record.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#define DEFAULT_RTSP_PORT "8554"

static char *port = (char *) DEFAULT_RTSP_PORT;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {NULL}
};

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;

  optctx = g_option_context_new ("<launch line> - Test RTSP Server, Launch\n\n"
      "Example: \"( decodebin name=depay0 ! autovideosink )\"");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }

  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }
  g_option_context_free (optctx);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named depay%d. Each
   * element with depay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_transport_mode (factory,
      GST_RTSP_TRANSPORT_MODE_RECORD);
  gst_rtsp_media_factory_set_launch (factory, argv[1]);
  gst_rtsp_media_factory_set_latency (factory, 2000);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port);
  g_print ("On the sender, send a stream with rtspclientsink:\n"
      "  gst-launch-1.0 videotestsrc ! x264enc ! rtspclientsink location=rtsp://127.0.0.1:%s/test\n",
      port);
  g_main_loop_run (loop);

  return 0;
}
   07070100000027000081A400000000000000000000000169D554BF000067AB000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/examples/test-replay-server.c  /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#include "test-replay-server.h"

GST_DEBUG_CATEGORY_STATIC (replay_server_debug);
#define GST_CAT_DEFAULT (replay_server_debug)

static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw");
static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw");

static GList
    * gst_rtsp_media_factory_replay_get_demuxers (GstRTSPMediaFactoryReplay *
    factory);
static GList
    * gst_rtsp_media_factory_replay_get_payloaders (GstRTSPMediaFactoryReplay *
    factory);
static GList
    * gst_rtsp_media_factory_replay_get_decoders (GstRTSPMediaFactoryReplay *
    factory);

typedef struct
{
  GstPad *srcpad;
  gulong block_id;
} GstReplayBinPad;

static void
gst_replay_bin_pad_unblock_and_free (GstReplayBinPad * pad)
{
  if (pad->srcpad && pad->block_id) {
    GST_DEBUG_OBJECT (pad->srcpad, "Unblock");
    gst_pad_remove_probe (pad->srcpad, pad->block_id);
    pad->block_id = 0;
  }

  gst_clear_object (&pad->srcpad);
  g_free (pad);
}

/* NOTE: this bin implementation is almost completely taken from rtsp-media-factory-uri
 * but this example doesn't use the GstRTSPMediaFactoryURI object so that
 * we can handle events and messages ourselves.
 * Specifically,
 * - Handle segment-done message for looping given source
 * - Drop all incoming seek event because client seek is not implemented
 *   and do initial segment seeking on no-more-pads signal
 */
struct _GstReplayBin
{
  GstBin parent;

  gint64 num_loops;

  GstCaps *raw_vcaps;
  GstCaps *raw_acaps;

  guint pt;

  /* without ref */
  GstElement *uridecodebin;
  GstElement *inner_bin;

  /* holds ref */
  GstRTSPMediaFactoryReplay *factory;

  GMutex lock;

  GList *srcpads;
};

static void gst_replay_bin_dispose (GObject * object);
static void gst_replay_bin_finalize (GObject * object);
static void gst_replay_bin_handle_message (GstBin * bin, GstMessage * message);

static gboolean autoplug_continue_cb (GstElement * dbin, GstPad * pad,
    GstCaps * caps, GstReplayBin * self);
static void pad_added_cb (GstElement * dbin, GstPad * pad, GstReplayBin * self);
static void no_more_pads_cb (GstElement * uribin, GstReplayBin * self);

#define gst_replay_bin_parent_class bin_parent_class
G_DEFINE_TYPE (GstReplayBin, gst_replay_bin, GST_TYPE_BIN);

static void
gst_replay_bin_class_init (GstReplayBinClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstBinClass *bin_class = GST_BIN_CLASS (klass);

  gobject_class->dispose = gst_replay_bin_dispose;
  gobject_class->finalize = gst_replay_bin_finalize;

  bin_class->handle_message = GST_DEBUG_FUNCPTR (gst_replay_bin_handle_message);
}

static void
gst_replay_bin_init (GstReplayBin * self)
{
  self->raw_vcaps = gst_static_caps_get (&raw_video_caps);
  self->raw_acaps = gst_static_caps_get (&raw_audio_caps);

  self->uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
  if (!self->uridecodebin) {
    GST_ERROR_OBJECT (self, "uridecodebin is unavailable");
    return;
  }

  /* our bin will dynamically expose payloaded pads */
  self->inner_bin = gst_bin_new ("dynpay0");
  gst_bin_add (GST_BIN_CAST (self), self->inner_bin);
  gst_bin_add (GST_BIN_CAST (self->inner_bin), self->uridecodebin);

  g_signal_connect (self->uridecodebin, "autoplug-continue",
      G_CALLBACK (autoplug_continue_cb), self);
  g_signal_connect (self->uridecodebin, "pad-added",
      G_CALLBACK (pad_added_cb), self);
  g_signal_connect (self->uridecodebin, "no-more-pads",
      G_CALLBACK (no_more_pads_cb), self);

  self->pt = 96;

  g_mutex_init (&self->lock);
}

static void
gst_replay_bin_dispose (GObject * object)
{
  GstReplayBin *self = GST_REPLAY_BIN (object);

  GST_DEBUG_OBJECT (self, "dispose");

  gst_clear_caps (&self->raw_vcaps);
  gst_clear_caps (&self->raw_acaps);
  gst_clear_object (&self->factory);

  if (self->srcpads) {
    g_list_free_full (self->srcpads,
        (GDestroyNotify) gst_replay_bin_pad_unblock_and_free);
    self->srcpads = NULL;
  }

  G_OBJECT_CLASS (bin_parent_class)->dispose (object);
}

static void
gst_replay_bin_finalize (GObject * object)
{
  GstReplayBin *self = GST_REPLAY_BIN (object);

  g_mutex_clear (&self->lock);

  G_OBJECT_CLASS (bin_parent_class)->finalize (object);
}

static gboolean
send_eos_foreach_srcpad (GstElement * element, GstPad * pad, gpointer user_data)
{
  GST_DEBUG_OBJECT (pad, "Sending EOS to downstream");
  gst_pad_push_event (pad, gst_event_new_eos ());

  return TRUE;
}

static void
gst_replay_bin_do_segment_seek (GstElement * element, GstReplayBin * self)
{
  gboolean ret;

  ret = gst_element_seek (element, 1.0, GST_FORMAT_TIME,
      GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_SEGMENT,
      GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);

  if (!ret) {
    GST_WARNING_OBJECT (self, "segment seeking failed");
    gst_element_foreach_src_pad (element,
        (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL);
  }
}

static void
gst_replay_bin_handle_message (GstBin * bin, GstMessage * message)
{
  GstReplayBin *self = GST_REPLAY_BIN (bin);

  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_DONE) {
    gboolean next_loop = TRUE;

    GST_DEBUG_OBJECT (self, "Have segment done message");

    g_mutex_lock (&self->lock);
    if (self->num_loops != -1) {
      self->num_loops--;

      if (self->num_loops < 1)
        next_loop = FALSE;
    }

    if (next_loop) {
      /* Send seek event from non-streaming thread */
      gst_object_call_async (GST_OBJECT_CAST (self->uridecodebin),
          (GstObjectCallAsyncFunc) gst_replay_bin_do_segment_seek, self);
    } else {
      gst_element_foreach_src_pad (GST_ELEMENT_CAST (self->uridecodebin),
          (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL);
    }

    g_mutex_unlock (&self->lock);
  }

  GST_BIN_CLASS (bin_parent_class)->handle_message (bin, message);
}

static GstElementFactory *
find_payloader (GstReplayBin * self, GstCaps * caps)
{
  GList *list;
  GstElementFactory *factory = NULL;
  gboolean autoplug_more = FALSE;
  GList *demuxers = NULL;
  GList *payloaders = NULL;

  demuxers = gst_rtsp_media_factory_replay_get_demuxers (self->factory);

  /* first find a demuxer that can link */
  list = gst_element_factory_list_filter (demuxers, caps, GST_PAD_SINK, FALSE);

  if (list) {
    GstStructure *structure = gst_caps_get_structure (caps, 0);
    gboolean parsed = FALSE;
    gint mpegversion = 0;

    if (!gst_structure_get_boolean (structure, "parsed", &parsed) &&
        gst_structure_has_name (structure, "audio/mpeg") &&
        gst_structure_get_int (structure, "mpegversion", &mpegversion) &&
        (mpegversion == 2 || mpegversion == 4)) {
      /* for AAC it's framed=true instead of parsed=true */
      gst_structure_get_boolean (structure, "framed", &parsed);
    }

    /* Avoid plugging parsers in a loop. This is not 100% correct, as some
     * parsers don't set parsed=true in caps. We should do something like
     * decodebin does and track decode chains and elements plugged in those
     * chains...
     */
    if (parsed) {
      GList *walk;
      const gchar *klass;

      for (walk = list; walk; walk = walk->next) {
        factory = GST_ELEMENT_FACTORY (walk->data);
        klass = gst_element_factory_get_metadata (factory,
            GST_ELEMENT_METADATA_KLASS);
        if (strstr (klass, "Parser"))
          /* caps have parsed=true, so skip this parser to avoid loops */
          continue;

        autoplug_more = TRUE;
        break;
      }
    } else {
      /* caps don't have parsed=true set and we have a demuxer/parser */
      autoplug_more = TRUE;
    }

    gst_plugin_feature_list_free (list);
  }

  if (autoplug_more)
    /* we have a demuxer, try that one first */
    return NULL;

  payloaders = gst_rtsp_media_factory_replay_get_payloaders (self->factory);

  /* no demuxer try a depayloader */
  list = gst_element_factory_list_filter (payloaders,
      caps, GST_PAD_SINK, FALSE);

  if (list == NULL) {
    GList *decoders =
        gst_rtsp_media_factory_replay_get_decoders (self->factory);
    /* no depayloader, try a decoder, we'll get to a payloader for a decoded
     * video or audio format, worst case. */
    list = gst_element_factory_list_filter (decoders,
        caps, GST_PAD_SINK, FALSE);

    if (list != NULL) {
      /* we have a decoder, try that one first */
      gst_plugin_feature_list_free (list);
      return NULL;
    }
  }

  if (list != NULL) {
    factory = GST_ELEMENT_FACTORY_CAST (list->data);
    g_object_ref (factory);
    gst_plugin_feature_list_free (list);
  }

  return factory;
}

static gboolean
autoplug_continue_cb (GstElement * dbin, GstPad * pad, GstCaps * caps,
    GstReplayBin * self)
{
  GstElementFactory *factory;

  GST_DEBUG_OBJECT (self, "found pad %s:%s of caps %" GST_PTR_FORMAT,
      GST_DEBUG_PAD_NAME (pad), caps);

  if (!(factory = find_payloader (self, caps)))
    goto no_factory;

  /* we found a payloader, stop autoplugging so we can plug the
   * payloader. */
  GST_DEBUG_OBJECT (self, "found factory %s",
      gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
  gst_object_unref (factory);

  return FALSE;

no_factory:
  {
    /* no payloader, continue autoplugging */
    GST_DEBUG_OBJECT (self, "no payloader found for caps %" GST_PTR_FORMAT,
        caps);
    return TRUE;
  }
}

static GstPadProbeReturn
replay_bin_sink_probe (GstPad * pad, GstPadProbeInfo * info,
    GstReplayBin * self)
{
  GstPadProbeReturn ret = GST_PAD_PROBE_OK;

  if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
    GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);

    switch (GST_EVENT_TYPE (event)) {
      case GST_EVENT_SEEK:
        /* Ideally this shouldn't happen because we are responding
         * seeking query with non-seekable */
        GST_DEBUG_OBJECT (pad, "Drop seek event");
        ret = GST_PAD_PROBE_DROP;
        break;
      default:
        break;
    }
  } else if (GST_IS_QUERY (GST_PAD_PROBE_INFO_DATA (info))) {
    GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info);

    switch (GST_QUERY_TYPE (query)) {
      case GST_QUERY_SEEKING:
      {
        /* FIXME: client seek is not implemented */
        gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, 0,
            GST_CLOCK_TIME_NONE);
        ret = GST_PAD_PROBE_HANDLED;
        break;
      }
      case GST_QUERY_SEGMENT:
        /* client seeking is not considered in here */
        gst_query_set_segment (query,
            1.0, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE);
        ret = GST_PAD_PROBE_HANDLED;
        break;
      default:
        break;
    }
  }

  return ret;
}

static GstPadProbeReturn
replay_bin_src_block (GstPad * pad, GstPadProbeInfo * info, GstReplayBin * self)
{
  GST_DEBUG_OBJECT (pad, "Block pad");

  return GST_PAD_PROBE_OK;
}

static void
pad_added_cb (GstElement * dbin, GstPad * pad, GstReplayBin * self)
{
  GstElementFactory *factory;
  GstElement *payloader;
  GstCaps *caps;
  GstPad *sinkpad, *srcpad, *ghostpad;
  GstPad *dpad = pad;
  GstElement *convert;
  gchar *padname, *payloader_name;
  GstElement *inner_bin = self->inner_bin;
  GstReplayBinPad *bin_pad;

  GST_DEBUG_OBJECT (self, "added pad %s:%s", GST_DEBUG_PAD_NAME (pad));

  /* ref to make refcounting easier later */
  gst_object_ref (pad);
  padname = gst_pad_get_name (pad);

  /* get pad caps first, then call get_caps, then fail */
  if ((caps = gst_pad_get_current_caps (pad)) == NULL)
    if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
      goto no_caps;

  /* check for raw caps */
  if (gst_caps_can_intersect (caps, self->raw_vcaps)) {
    /* we have raw video caps, insert converter */
    convert = gst_element_factory_make ("videoconvert", NULL);
  } else if (gst_caps_can_intersect (caps, self->raw_acaps)) {
    /* we have raw audio caps, insert converter */
    convert = gst_element_factory_make ("audioconvert", NULL);
  } else {
    convert = NULL;
  }

  if (convert) {
    gst_bin_add (GST_BIN_CAST (inner_bin), convert);
    gst_element_sync_state_with_parent (convert);

    sinkpad = gst_element_get_static_pad (convert, "sink");
    gst_pad_link (pad, sinkpad);
    gst_object_unref (sinkpad);

    /* unref old pad, we reffed before */
    gst_object_unref (pad);

    /* continue with new pad and caps */
    pad = gst_element_get_static_pad (convert, "src");
    if ((caps = gst_pad_get_current_caps (pad)) == NULL)
      if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
        goto no_caps;
  }

  if (!(factory = find_payloader (self, caps)))
    goto no_factory;

  gst_caps_unref (caps);

  /* we have a payloader now */
  GST_DEBUG_OBJECT (self, "found payloader factory %s",
      gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));

  payloader_name = g_strdup_printf ("pay_%s", padname);
  payloader = gst_element_factory_create (factory, payloader_name);
  g_free (payloader_name);
  if (payloader == NULL)
    goto no_payloader;

  g_object_set (payloader, "pt", self->pt, NULL);
  self->pt++;

  if (g_object_class_find_property (G_OBJECT_GET_CLASS (payloader),
          "buffer-list"))
    g_object_set (payloader, "buffer-list", TRUE, NULL);

  /* add the payloader to the pipeline */
  gst_bin_add (GST_BIN_CAST (inner_bin), payloader);
  gst_element_sync_state_with_parent (payloader);

  /* link the pad to the sinkpad of the payloader */
  sinkpad = gst_element_get_static_pad (payloader, "sink");
  gst_pad_link (pad, sinkpad);
  gst_object_unref (pad);

  /* Add pad probe to handle events */
  gst_pad_add_probe (sinkpad,
      GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
      (GstPadProbeCallback) replay_bin_sink_probe, self, NULL);
  gst_object_unref (sinkpad);

  /* block data for initial segment seeking */
  bin_pad = g_new0 (GstReplayBinPad, 1);

  /* Move ownership of pad to this struct */
  bin_pad->srcpad = gst_object_ref (dpad);
  bin_pad->block_id =
      gst_pad_add_probe (dpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
      (GstPadProbeCallback) replay_bin_src_block, self, NULL);
  g_mutex_lock (&self->lock);
  self->srcpads = g_list_append (self->srcpads, bin_pad);
  g_mutex_unlock (&self->lock);

  /* now expose the srcpad of the payloader as a ghostpad with the same name
   * as the uridecodebin pad name. */
  srcpad = gst_element_get_static_pad (payloader, "src");
  ghostpad = gst_ghost_pad_new (padname, srcpad);
  gst_object_unref (srcpad);
  g_free (padname);

  gst_pad_set_active (ghostpad, TRUE);
  gst_element_add_pad (inner_bin, ghostpad);

  return;

  /* ERRORS */
no_caps:
  {
    GST_WARNING ("could not get caps from pad");
    g_free (padname);
    gst_object_unref (pad);
    return;
  }
no_factory:
  {
    GST_DEBUG ("no payloader found");
    g_free (padname);
    gst_caps_unref (caps);
    gst_object_unref (pad);
    return;
  }
no_payloader:
  {
    GST_ERROR ("could not create payloader from factory");
    g_free (padname);
    gst_caps_unref (caps);
    gst_object_unref (pad);
    return;
  }
}

static void
gst_replay_bin_do_initial_segment_seek (GstElement * element,
    GstReplayBin * self)
{
  gboolean ret;
  GstQuery *query;
  gboolean seekable;

  query = gst_query_new_seeking (GST_FORMAT_TIME);
  ret = gst_element_query (element, query);

  if (!ret) {
    GST_WARNING_OBJECT (self, "Cannot query seeking");
    gst_query_unref (query);
    goto done;
  }

  gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
  gst_query_unref (query);

  if (!seekable) {
    GST_WARNING_OBJECT (self, "Source is not seekable");
    ret = FALSE;
    goto done;
  }

  ret = gst_element_seek (element, 1.0, GST_FORMAT_TIME,
      GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH,
      GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);

  if (!ret)
    GST_WARNING_OBJECT (self, "segment seeking failed");

done:
  /* Unblock all pads then */
  g_mutex_lock (&self->lock);
  if (self->srcpads) {
    g_list_free_full (self->srcpads,
        (GDestroyNotify) gst_replay_bin_pad_unblock_and_free);
    self->srcpads = NULL;
  }
  g_mutex_unlock (&self->lock);

  if (!ret) {
    GST_WARNING_OBJECT (self, "Sending eos to all pads");
    gst_element_foreach_src_pad (element,
        (GstElementForeachPadFunc) send_eos_foreach_srcpad, NULL);
  }
}

static void
no_more_pads_cb (GstElement * uribin, GstReplayBin * self)
{
  GST_DEBUG_OBJECT (self, "no-more-pads");
  gst_element_no_more_pads (GST_ELEMENT_CAST (self->inner_bin));

  /* Flush seeking from streaming thread might not be good idea.
   * Do this from another (non-streaming) thread */
  gst_object_call_async (GST_OBJECT_CAST (self->uridecodebin),
      (GstObjectCallAsyncFunc) gst_replay_bin_do_initial_segment_seek, self);
}

static GstElement *
gst_replay_bin_new (const gchar * uri, gint64 num_loops,
    GstRTSPMediaFactoryReplay * factory, const gchar * name)
{
  GstReplayBin *self;

  g_return_val_if_fail (uri != NULL, NULL);
  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);

  if (!name)
    name = "GstRelayBin";

  self = GST_REPLAY_BIN (g_object_new (GST_TYPE_REPLAY_BIN,
          "name", name, NULL));

  if (!self->uridecodebin) {
    gst_object_unref (self);
    return NULL;
  }

  g_object_set (self->uridecodebin, "uri", uri, NULL);
  self->factory = g_object_ref (factory);
  self->num_loops = num_loops;

  return GST_ELEMENT_CAST (self);
}

struct _GstRTSPMediaFactoryReplay
{
  GstRTSPMediaFactory parent;

  gchar *uri;

  GList *demuxers;
  GList *payloaders;
  GList *decoders;

  gint64 num_loops;
};

enum
{
  PROP_0,
  PROP_URI,
  PROP_NUM_LOOPS,
};

#define DEFAULT_NUM_LOOPS (-1)

static void gst_rtsp_media_factory_replay_get_property (GObject * object,
    guint propid, GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_replay_set_property (GObject * object,
    guint propid, const GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_replay_finalize (GObject * object);

static GstElement
    * gst_rtsp_media_factory_replay_create_element (GstRTSPMediaFactory *
    factory, const GstRTSPUrl * url);

typedef struct
{
  GList *demux;
  GList *payload;
  GList *decode;
} FilterData;

static gboolean
payloader_filter (GstPluginFeature * feature, FilterData * self);

#define gst_rtsp_media_factory_replay_parent_class parent_class
G_DEFINE_TYPE (GstRTSPMediaFactoryReplay,
    gst_rtsp_media_factory_replay, GST_TYPE_RTSP_MEDIA_FACTORY);

static void
gst_rtsp_media_factory_replay_class_init (GstRTSPMediaFactoryReplayClass
    * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstRTSPMediaFactoryClass *mf_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);

  gobject_class->get_property = gst_rtsp_media_factory_replay_get_property;
  gobject_class->set_property = gst_rtsp_media_factory_replay_set_property;
  gobject_class->finalize = gst_rtsp_media_factory_replay_finalize;

  g_object_class_install_property (gobject_class, PROP_URI,
      g_param_spec_string ("uri", "URI",
          "The URI of the resource to stream", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_NUM_LOOPS,
      g_param_spec_int64 ("num-loops", "Num Loops",
          "The number of loops (-1 = infinite)", -1, G_MAXINT64,
          DEFAULT_NUM_LOOPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  mf_class->create_element =
      GST_DEBUG_FUNCPTR (gst_rtsp_media_factory_replay_create_element);
}

static void
gst_rtsp_media_factory_replay_init (GstRTSPMediaFactoryReplay * self)
{
  FilterData data = { NULL, };
  GList *unused;

  /* get the feature list using the filter */
  unused = gst_registry_feature_filter (gst_registry_get (),
      (GstPluginFeatureFilter) payloader_filter, FALSE, &data);

  gst_plugin_feature_list_free (unused);

  /* sort */
  self->demuxers =
      g_list_sort (data.demux, gst_plugin_feature_rank_compare_func);
  self->payloaders =
      g_list_sort (data.payload, gst_plugin_feature_rank_compare_func);
  self->decoders =
      g_list_sort (data.decode, gst_plugin_feature_rank_compare_func);

  self->num_loops = DEFAULT_NUM_LOOPS;
}

static void
gst_rtsp_media_factory_replay_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object);

  switch (propid) {
    case PROP_URI:
      g_value_take_string (value, self->uri);
      break;
    case PROP_NUM_LOOPS:
      g_value_set_int64 (value, self->num_loops);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_media_factory_replay_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object);

  switch (propid) {
    case PROP_URI:
      g_free (self->uri);
      self->uri = g_value_dup_string (value);
      break;
    case PROP_NUM_LOOPS:
      self->num_loops = g_value_get_int64 (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_media_factory_replay_finalize (GObject * object)
{
  GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (object);

  g_free (self->uri);

  gst_plugin_feature_list_free (self->demuxers);
  gst_plugin_feature_list_free (self->payloaders);
  gst_plugin_feature_list_free (self->decoders);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static GstElement *
gst_rtsp_media_factory_replay_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMediaFactoryReplay *self = GST_RTSP_MEDIA_FACTORY_REPLAY (factory);

  return gst_replay_bin_new (self->uri, self->num_loops, self,
      "GstRTSPMediaFactoryReplay");
}

static gboolean
payloader_filter (GstPluginFeature * feature, FilterData * data)
{
  const gchar *klass;
  GstElementFactory *fact;
  GList **list = NULL;

  /* we only care about element factories */
  if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature)))
    return FALSE;

  if (gst_plugin_feature_get_rank (feature) < GST_RANK_MARGINAL)
    return FALSE;

  fact = GST_ELEMENT_FACTORY_CAST (feature);

  klass = gst_element_factory_get_metadata (fact, GST_ELEMENT_METADATA_KLASS);

  if (strstr (klass, "Decoder"))
    list = &data->decode;
  else if (strstr (klass, "Demux"))
    list = &data->demux;
  else if (strstr (klass, "Parser") && strstr (klass, "Codec"))
    list = &data->demux;
  else if (strstr (klass, "Payloader") && strstr (klass, "RTP"))
    list = &data->payload;

  if (list) {
    GST_LOG ("adding %s", GST_OBJECT_NAME (fact));
    *list = g_list_prepend (*list, gst_object_ref (fact));
  }

  return FALSE;
}

static GList *
gst_rtsp_media_factory_replay_get_demuxers (GstRTSPMediaFactoryReplay * factory)
{
  return factory->demuxers;
}

static GList *
gst_rtsp_media_factory_replay_get_payloaders (GstRTSPMediaFactoryReplay *
    factory)
{
  return factory->payloaders;
}

static GList *
gst_rtsp_media_factory_replay_get_decoders (GstRTSPMediaFactoryReplay * factory)
{
  return factory->decoders;
}

static GstRTSPMediaFactory *
gst_rtsp_media_factory_replay_new (const gchar * uri, gint64 num_loops)
{
  GstRTSPMediaFactory *factory;

  factory =
      GST_RTSP_MEDIA_FACTORY (g_object_new
      (GST_TYPE_RTSP_MEDIA_FACTORY_REPLAY, "uri", uri, "num-loops", num_loops,
          NULL));

  return factory;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  gchar *service;
  gchar *uri = NULL;
  gint64 num_loops = -1;
  GOptionEntry options[] = {
    {"num-loops", 0, 0, G_OPTION_ARG_INT64, &num_loops,
        "The number of loops (default = -1, infinite)", NULL},
    {NULL}
  };

  optctx = g_option_context_new ("RTSP Replay Server");
  g_option_context_add_main_entries (optctx, options, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  if (argc < 2) {
    g_print ("%s\n", g_option_context_get_help (optctx, TRUE, NULL));
    return 1;
  }

  g_option_context_free (optctx);

  /* check if URI is valid, otherwise convert filename to URI if it's a file */
  if (gst_uri_is_valid (argv[1])) {
    uri = g_strdup (argv[1]);
  } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) {
    uri = gst_filename_to_uri (argv[1], NULL);
  } else {
    g_printerr ("Unrecognised command line argument '%s'.\n"
        "Please pass an URI or file as argument!\n", argv[1]);
    return -1;
  }

  if (num_loops < -1 || num_loops == 0) {
    g_printerr ("num-loop should be non-zero or -1");
    return -1;
  }

  GST_DEBUG_CATEGORY_INIT (replay_server_debug, "replay-server", 0,
      "RTSP replay server");

  if (num_loops != -1)
    g_print ("Run loop %" G_GINT64_FORMAT " times\n", num_loops);

  loop = g_main_loop_new (NULL, FALSE);

  server = gst_rtsp_server_new ();

  mounts = gst_rtsp_server_get_mount_points (server);
  factory = gst_rtsp_media_factory_replay_new (uri, num_loops);
  g_free (uri);

  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  g_object_unref (mounts);

  gst_rtsp_server_attach (server, NULL);

  service = gst_rtsp_server_get_service (server);
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", service);
  g_free (service);
  g_main_loop_run (loop);

  return 0;
}
 07070100000028000081A400000000000000000000000169D554BF00000545000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/examples/test-replay-server.h  /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

G_BEGIN_DECLS

#define GST_TYPE_REPLAY_BIN (gst_replay_bin_get_type ())
G_DECLARE_FINAL_TYPE (GstReplayBin, gst_replay_bin, GST, REPLAY_BIN, GstBin);

#define GST_TYPE_RTSP_MEDIA_FACTORY_REPLAY (gst_rtsp_media_factory_replay_get_type ())
G_DECLARE_FINAL_TYPE (GstRTSPMediaFactoryReplay,
    gst_rtsp_media_factory_replay, GST, RTSP_MEDIA_FACTORY_REPLAY,
    GstRTSPMediaFactory);

G_END_DECLS
   07070100000029000081A400000000000000000000000169D554BF00000B11000000000000000000000000000000000000002B00000000gst-rtsp-server-1.28.2/examples/test-sdp.c    /* GStreamer
 * Copyright (C) 2009 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>


static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

static void
media_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
{
  gst_rtsp_media_set_reusable (media, TRUE);
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  gchar *str;

  gst_init (&argc, &argv);

  if (argc < 2) {
    g_message ("usage: %s <filename.sdp>", argv[0]);
    return -1;
  }

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines. 
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();

  str =
      g_strdup_printf ("( filesrc location=%s ! sdpdemux name=dynpay0 )",
      argv[1]);
  gst_rtsp_media_factory_set_launch (factory, str);
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  g_signal_connect (factory, "media-configure", (GCallback) media_configure,
      NULL);
  g_free (str);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  gst_rtsp_server_attach (server, NULL);

  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving */
  g_main_loop_run (loop);

  return 0;
}
   0707010000002A000081A400000000000000000000000169D554BF00001220000000000000000000000000000000000000002B00000000gst-rtsp-server-1.28.2/examples/test-uri.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>
#include <gst/rtsp-server/rtsp-media-factory-uri.h>

#define DEFAULT_RTSP_PORT "8554"

static char *port = (char *) DEFAULT_RTSP_PORT;

static GOptionEntry entries[] = {
  {"port", 'p', 0, G_OPTION_ARG_STRING, &port,
      "Port to listen on (default: " DEFAULT_RTSP_PORT ")", "PORT"},
  {NULL}
};


static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

#if 0
static gboolean
remove_map (GstRTSPServer * server)
{
  GstRTSPMountPoints *mounts;

  g_print ("removing /test mount point\n");
  mounts = gst_rtsp_server_get_mount_points (server);
  gst_rtsp_mount_points_remove_factory (mounts, "/test");
  g_object_unref (mounts);

  return FALSE;
}
#endif

int
main (int argc, gchar * argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactoryURI *factory;
  GOptionContext *optctx;
  GError *error = NULL;
  gchar *uri;

  optctx = g_option_context_new ("<uri> - Test RTSP Server, URI");
  g_option_context_add_main_entries (optctx, entries, NULL);
  g_option_context_add_group (optctx, gst_init_get_option_group ());
  if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
    g_printerr ("Error parsing options: %s\n", error->message);
    g_option_context_free (optctx);
    g_clear_error (&error);
    return -1;
  }
  g_option_context_free (optctx);

  if (argc < 2) {
    g_printerr ("Please pass an URI or file as argument!\n");
    return -1;
  }

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();
  g_object_set (server, "service", port, NULL);

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a URI media factory for a test stream. */
  factory = gst_rtsp_media_factory_uri_new ();

  /* when using GStreamer as a client, one can use the gst payloader, which is
   * more efficient when there is no payloader for the compressed format */
  /* g_object_set (factory, "use-gstpay", TRUE, NULL); */

  /* check if URI is valid, otherwise convert filename to URI if it's a file */
  if (gst_uri_is_valid (argv[1])) {
    uri = g_strdup (argv[1]);
  } else if (g_file_test (argv[1], G_FILE_TEST_EXISTS)) {
    uri = gst_filename_to_uri (argv[1], NULL);
  } else {
    g_printerr ("Unrecognised command line argument '%s'.\n"
        "Please pass an URI or file as argument!\n", argv[1]);
    return -1;
  }

  gst_rtsp_media_factory_uri_set_uri (factory, uri);
  g_free (uri);

  /* if you want multiple clients to see the same video, set the shared property
   * to TRUE */
  /* gst_rtsp_media_factory_set_shared ( GST_RTSP_MEDIA_FACTORY (factory), TRUE); */

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test",
      GST_RTSP_MEDIA_FACTORY (factory));

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  /* do session cleanup every 2 seconds */
  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

#if 0
  /* remove the mount point after 10 seconds, new clients won't be able to use
   * the /test url anymore */
  g_timeout_add_seconds (10, (GSourceFunc) remove_map, server);
#endif

  /* start serving */
  g_print ("stream ready at rtsp://127.0.0.1:%s/test\n", port);
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
0707010000002B000081A400000000000000000000000169D554BF00001E62000000000000000000000000000000000000003800000000gst-rtsp-server-1.28.2/examples/test-video-disconnect.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2018 Jan Schmidt <jan at centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* This example disconnects any clients and exits 10 seconds
 * after the first client connects */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

guint exit_timeout_id = 0;

/* define this if you want the resource to only be available when using
 * user/password as the password */
#undef WITH_AUTH

/* define this if you want the server to use TLS (it will also need WITH_AUTH
 * to be defined) */
#undef WITH_TLS

/* this timeout is periodically run to clean up the expired sessions from the
 * pool. This needs to be run explicitly currently but might be done
 * automatically as part of the mainloop. */
static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

static GstRTSPFilterResult
client_filter (GstRTSPServer * server, GstRTSPClient * client,
    gpointer user_data)
{
  /* Simple filter that shuts down all clients. */
  return GST_RTSP_FILTER_REMOVE;
}

/* Timeout that runs 10 seconds after the first client connects and triggers
 * the shutdown of the server */
static gboolean
shutdown_timeout (GstRTSPServer * server)
{
  GstRTSPMountPoints *mounts;
  g_print ("Time for everyone to go. Removing mount point\n");
  /* Remove the mount point to prevent new clients connecting */
  mounts = gst_rtsp_server_get_mount_points (server);
  gst_rtsp_mount_points_remove_factory (mounts, "/test");
  g_object_unref (mounts);

  /* Filter existing clients and remove them */
  g_print ("Disconnecting existing clients\n");
  gst_rtsp_server_client_filter (server, client_filter, NULL);
  return FALSE;
}

static void
client_connected (GstRTSPServer * server, GstRTSPClient * client)
{
  if (exit_timeout_id == 0) {
    g_print ("First Client connected. Disconnecting everyone in 10 seconds\n");
    exit_timeout_id =
        g_timeout_add_seconds (10, (GSourceFunc) shutdown_timeout, server);
  }
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
#ifdef WITH_AUTH
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  gchar *basic;
  GstRTSPPermissions *permissions;
#endif
#ifdef WITH_TLS
  GTlsCertificate *cert;
  GError *error = NULL;
#endif

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

#ifdef WITH_AUTH
  /* make a new authentication manager. it can be added to control access to all
   * the factories on the server or on individual factories. */
  auth = gst_rtsp_auth_new ();
#ifdef WITH_TLS
  cert = g_tls_certificate_new_from_pem ("-----BEGIN CERTIFICATE-----"
      "MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk"
      "ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp"
      "ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq"
      "hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx"
      "NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW"
      "B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3"
      "DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf"
      "hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC"
      "MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1"
      "Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk"
      "PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH"
      "QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4="
      "-----END CERTIFICATE-----"
      "-----BEGIN PRIVATE KEY-----"
      "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA2EnE8ZOeVwZmwzPc"
      "88DvoK1ckhOK7nVrsx9j6TmyKJ6m34UaAGpSlZbhvY72xyPNXl8QnUjm79SgT9bG"
      "zeUc6QIDAQABAkBRFJZ32VbqWMP9OVwDJLiwC01AlYLnka0mIQZbT/2xq9dUc9GW"
      "U3kiVw4lL8v/+sPjtTPCYYdzHHOyDen6znVhAiEA9qJT7BtQvRxCvGrAhr9MS022"
      "tTdPbW829BoUtIeH64cCIQDggG5i48v7HPacPBIH1RaSVhXl8qHCpQD3qrIw3FMw"
      "DwIga8PqH5Sf5sHedy2+CiK0V4MRfoU4c3zQ6kArI+bEgSkCIQCLA1vXBiE31B5s"
      "bdHoYa1BXebfZVd+1Hd95IfEM5mbRwIgSkDuQwV55BBlvWph3U8wVIMIb4GStaH8"
      "W535W8UBbEg=" "-----END PRIVATE KEY-----", -1, &error);
  if (cert == NULL) {
    g_printerr ("failed to parse PEM: %s\n", error->message);
    return -1;
  }
  gst_rtsp_auth_set_tls_certificate (auth, cert);
  g_object_unref (cert);
#endif

  /* make user token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  basic = gst_rtsp_auth_make_basic ("user", "password");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* configure in the server */
  gst_rtsp_server_set_auth (server, auth);
#endif

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");
#ifdef WITH_AUTH
  /* add permissions for the user media role */
  permissions = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (permissions, "user",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_media_factory_set_permissions (factory, permissions);
  gst_rtsp_permissions_unref (permissions);
#ifdef WITH_TLS
  gst_rtsp_media_factory_set_profiles (factory, GST_RTSP_PROFILE_SAVP);
#endif
#endif

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  g_signal_connect (server, "client-connected", (GCallback) client_connected,
      NULL);

  /* add a timeout for the session cleanup */
  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving, this never stops */
#ifdef WITH_TLS
  g_print ("stream ready at rtsps://127.0.0.1:8554/test\n");
#else
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
#endif
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
  0707010000002C000081A400000000000000000000000169D554BF00000C7F000000000000000000000000000000000000003100000000gst-rtsp-server-1.28.2/examples/test-video-rtx.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

/* this timeout is periodically run to clean up the expired sessions from the
 * pool. This needs to be run explicitly currently but might be done
 * automatically as part of the mainloop. */
static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=8 " ")");

  gst_rtsp_media_factory_set_profiles (factory, GST_RTSP_PROFILE_AVPF);

  /* store up to 0.4 seconds of retransmission data */
  gst_rtsp_media_factory_set_retransmission_time (factory, 400 * GST_MSECOND);

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  /* add a timeout for the session cleanup */
  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving, this never stops */
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");

  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
 0707010000002D000081A400000000000000000000000169D554BF000018D3000000000000000000000000000000000000002D00000000gst-rtsp-server-1.28.2/examples/test-video.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

/* define this if you want the resource to only be available when using
 * user/password as the password */
#undef WITH_AUTH

/* define this if you want the server to use TLS (it will also need WITH_AUTH
 * to be defined) */
#undef WITH_TLS

/* this timeout is periodically run to clean up the expired sessions from the
 * pool. This needs to be run explicitly currently but might be done
 * automatically as part of the mainloop. */
static gboolean
timeout (GstRTSPServer * server)
{
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_session_pool_cleanup (pool);
  g_object_unref (pool);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
#ifdef WITH_AUTH
  GstRTSPAuth *auth;
  GstRTSPToken *token;
  gchar *basic;
  GstRTSPPermissions *permissions;
#endif
#ifdef WITH_TLS
  GTlsCertificate *cert;
  GError *error = NULL;
#endif

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

#ifdef WITH_AUTH
  /* make a new authentication manager. it can be added to control access to all
   * the factories on the server or on individual factories. */
  auth = gst_rtsp_auth_new ();
#ifdef WITH_TLS
  cert = g_tls_certificate_new_from_pem ("-----BEGIN CERTIFICATE-----"
      "MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk"
      "ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp"
      "ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq"
      "hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx"
      "NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW"
      "B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3"
      "DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf"
      "hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC"
      "MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1"
      "Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk"
      "PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH"
      "QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4="
      "-----END CERTIFICATE-----"
      "-----BEGIN PRIVATE KEY-----"
      "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA2EnE8ZOeVwZmwzPc"
      "88DvoK1ckhOK7nVrsx9j6TmyKJ6m34UaAGpSlZbhvY72xyPNXl8QnUjm79SgT9bG"
      "zeUc6QIDAQABAkBRFJZ32VbqWMP9OVwDJLiwC01AlYLnka0mIQZbT/2xq9dUc9GW"
      "U3kiVw4lL8v/+sPjtTPCYYdzHHOyDen6znVhAiEA9qJT7BtQvRxCvGrAhr9MS022"
      "tTdPbW829BoUtIeH64cCIQDggG5i48v7HPacPBIH1RaSVhXl8qHCpQD3qrIw3FMw"
      "DwIga8PqH5Sf5sHedy2+CiK0V4MRfoU4c3zQ6kArI+bEgSkCIQCLA1vXBiE31B5s"
      "bdHoYa1BXebfZVd+1Hd95IfEM5mbRwIgSkDuQwV55BBlvWph3U8wVIMIb4GStaH8"
      "W535W8UBbEg=" "-----END PRIVATE KEY-----", -1, &error);
  if (cert == NULL) {
    g_printerr ("failed to parse PEM: %s\n", error->message);
    return -1;
  }
  gst_rtsp_auth_set_tls_certificate (auth, cert);
  g_object_unref (cert);
#endif

  /* make user token */
  token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  basic = gst_rtsp_auth_make_basic ("user", "password");
  gst_rtsp_auth_add_basic (auth, basic, token);
  g_free (basic);
  gst_rtsp_token_unref (token);

  /* configure in the server */
  gst_rtsp_server_set_auth (server, auth);
#endif

  /* get the mount points for this server, every server has a default object
   * that be used to map uri mount points to media factories */
  mounts = gst_rtsp_server_get_mount_points (server);

  /* make a media factory for a test stream. The default media factory can use
   * gst-launch syntax to create pipelines.
   * any launch line works as long as it contains elements named pay%d. Each
   * element with pay%d names will be a stream */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory, "( "
      "videotestsrc ! video/x-raw,width=352,height=288,framerate=15/1 ! "
      "x264enc ! rtph264pay name=pay0 pt=96 "
      "audiotestsrc ! audio/x-raw,rate=8000 ! "
      "alawenc ! rtppcmapay name=pay1 pt=97 " ")");
#ifdef WITH_AUTH
  /* add permissions for the user media role */
  permissions = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (permissions, "user",
      GST_RTSP_PERM_MEDIA_FACTORY_ACCESS, G_TYPE_BOOLEAN, TRUE,
      GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_media_factory_set_permissions (factory, permissions);
  gst_rtsp_permissions_unref (permissions);
#ifdef WITH_TLS
  gst_rtsp_media_factory_set_profiles (factory, GST_RTSP_PROFILE_SAVP);
#endif
#endif

  /* attach the test factory to the /test url */
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  /* don't need the ref to the mapper anymore */
  g_object_unref (mounts);

  /* attach the server to the default maincontext */
  if (gst_rtsp_server_attach (server, NULL) == 0)
    goto failed;

  /* add a timeout for the session cleanup */
  g_timeout_add_seconds (2, (GSourceFunc) timeout, server);

  /* start serving, this never stops */
#ifdef WITH_TLS
  g_print ("stream ready at rtsps://127.0.0.1:8554/test\n");
#else
  g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
#endif
  g_main_loop_run (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
 0707010000002E000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000001B00000000gst-rtsp-server-1.28.2/gst    0707010000002F000081A400000000000000000000000169D554BF00004FAF000000000000000000000000000000000000002C00000000gst-rtsp-server-1.28.2/gst-rtsp-server.doap   <Project
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
  xmlns="http://usefulinc.com/ns/doap#"
  xmlns:foaf="http://xmlns.com/foaf/0.1/"
  xmlns:admin="http://webns.net/mvcb/">

 <name>GStreamer RTSP Server</name>
 <shortname>gst-rtsp-server</shortname>
 <homepage rdf:resource="http://gstreamer.freedesktop.org/modules/gst-rtsp-server.html" />
 <created>1999-10-31</created>
 <shortdesc xml:lang="en">
RTSP server library based on GStreamer
</shortdesc>
 <description xml:lang="en">
RTSP server library based on GStreamer
 </description>
 <category></category>
 <bug-database rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/" />
 <screenshots></screenshots>
 <mailing-list rdf:resource="http://lists.freedesktop.org/mailman/listinfo/gstreamer-devel" />
 <programming-language>C</programming-language>
 <license rdf:resource="http://usefulinc.com/doap/licenses/lgpl" />
 <download-page rdf:resource="http://gstreamer.freedesktop.org/download/" />

 <repository>
   <GitRepository>
     <location rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server"/>
     <browse rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server"/>
   </GitRepository>
</repository>

 <release>
  <Version>
   <revision>1.28.2</revision>
   <branch>1.28</branch>
   <name></name>
   <created>2026-04-07</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.28.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.28.1</revision>
   <branch>1.28</branch>
   <name></name>
   <created>2026-02-26</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.28.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.28.0</revision>
   <branch>main</branch>
   <name></name>
   <created>2026-01-27</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.28.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.27.90</revision>
   <branch>main</branch>
   <name></name>
   <created>2026-01-05</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.27.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.27.50</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-12-09</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.27.50.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.27.2</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-09-07</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.27.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.27.1</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-07-08</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.27.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.0</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-03-11</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.25.90</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-02-23</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.25.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.25.50</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-02-09</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.25.50.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.25.1</revision>
   <branch>main</branch>
   <name></name>
   <created>2025-01-14</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.25.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.24.0</revision>
   <branch>main</branch>
   <name></name>
   <created>2024-03-04</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.24.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.23.90</revision>
   <branch>main</branch>
   <name></name>
   <created>2024-02-23</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.23.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.23.2</revision>
   <branch>main</branch>
   <name></name>
   <created>2024-02-15</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.23.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.23.1</revision>
   <branch>main</branch>
   <name></name>
   <created>2024-02-06</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.23.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.22.0</revision>
   <branch>main</branch>
   <name></name>
   <created>2023-01-23</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.22.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.21.90</revision>
   <branch>main</branch>
   <name></name>
   <created>2023-01-13</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.21.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.21.3</revision>
   <branch>main</branch>
   <name></name>
   <created>2022-12-05</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.21.3.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.21.2</revision>
   <branch>main</branch>
   <name></name>
   <created>2022-11-07</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.21.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.21.1</revision>
   <branch>main</branch>
   <name></name>
   <created>2022-10-04</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.21.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.20.0</revision>
   <branch>main</branch>
   <name></name>
   <created>2022-02-03</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.20.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.19.90</revision>
   <branch>main</branch>
   <name></name>
   <created>2022-01-28</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.19.3</revision>
   <branch>main</branch>
   <name></name>
   <created>2021-11-03</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.3.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.19.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2021-09-23</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.19.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2021-06-01</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.18.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2020-09-08</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.18.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.17.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2020-08-20</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.17.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2020-07-03</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.17.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2020-06-19</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.16.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2019-04-19</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.16.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.15.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2019-04-11</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.15.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2019-02-26</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.15.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2019-01-17</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.14.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2018-03-19</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.14.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.13.91</revision>
   <branch>master</branch>
   <name></name>
   <created>2018-03-13</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.91.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.13.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2018-03-03</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.13.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2018-02-15</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.12.4</revision>
   <branch>1.12</branch>
   <name></name>
   <created>2017-12-07</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.4.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.12.3</revision>
   <branch>1.12</branch>
   <name></name>
   <created>2017-09-18</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.3.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.12.2</revision>
   <branch>1.12</branch>
   <name></name>
   <created>2017-07-14</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.12.1</revision>
   <branch>1.12</branch>
   <name></name>
   <created>2017-06-20</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.12.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2017-05-04</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.11.91</revision>
   <branch>master</branch>
   <name></name>
   <created>2017-04-27</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.91.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.11.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2017-04-07</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.11.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2017-02-24</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.11.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2017-01-12</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.10.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-11-01</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.10.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.9.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-09-30</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.9.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-09-01</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.9.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-06-06</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.8.0</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-03-24</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.8.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.7.91</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-03-15</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.91.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.7.90</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-03-01</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.7.2</revision>
   <branch>master</branch>
   <name></name>
   <created>2016-02-19</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.7.1</revision>
   <branch>master</branch>
   <name></name>
   <created>2015-12-24</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.6.2</revision>
   <branch>1.6</branch>
   <name></name>
   <created>2015-12-14</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.6.1</revision>
   <branch>1.6</branch>
   <name></name>
   <created>2015-10-30</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.6.0</revision>
   <branch>1.6</branch>
   <name></name>
   <created>2015-09-25</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.5.91</revision>
   <branch>1.5</branch>
   <name></name>
   <created>2015-09-18</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.91.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.5.90</revision>
   <branch>1.5</branch>
   <name></name>
   <created>2015-08-19</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.5.2</revision>
   <branch>1.5</branch>
   <name></name>
   <created>2015-06-24</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.5.1</revision>
   <branch>1.5</branch>
   <name></name>
   <created>2015-06-07</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.4.0</revision>
   <branch>1.4</branch>
   <name></name>
   <created>2014-07-19</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.4.0.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.3.91</revision>
   <branch>1.3</branch>
   <name></name>
   <created>2014-07-11</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.91.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.3.90</revision>
   <branch>1.3</branch>
   <name></name>
   <created>2014-06-28</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.90.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.3.3</revision>
   <branch>1.3</branch>
   <name></name>
   <created>2014-06-22</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.3.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.3.2</revision>
   <branch>1.3</branch>
   <name></name>
   <created>2014-05-21</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.3.1</revision>
   <branch>1.3</branch>
   <name></name>
   <created>2014-05-03</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.1.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.1.90</revision>
   <branch>1.1</branch>
   <name></name>
   <created>2014-02-09</created>
   <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.1.90.tar.xz" />
  </Version>
 </release>

 <maintainer>
  <foaf:Person>
     <foaf:name>Wim Taymans</foaf:name>
     <foaf:mbox_sha1sum>0d93fde052812d51a05fd86de9bdbf674423daa2</foaf:mbox_sha1sum>
  </foaf:Person>
 </maintainer>

</Project>
 07070100000030000081A400000000000000000000000169D554BF0000084E000000000000000000000000000000000000003100000000gst-rtsp-server-1.28.2/gst/glib-compat-private.h  /*
 * glib-compat.c
 * Functions copied from glib 2.10
 *
 * Copyright 2005 David Schleef <ds@schleef.org>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GLIB_COMPAT_PRIVATE_H__
#define __GLIB_COMPAT_PRIVATE_H__

#include <glib.h>

G_BEGIN_DECLS

/* copies */

#if !GLIB_CHECK_VERSION(2,68,0)
#ifndef g_string_replace
#define g_string_replace gst_g_string_replace
#endif

static inline guint
gst_g_string_replace (GString     *string,
                      const gchar *find,
                      const gchar *replace,
                      guint        limit)
{
  gsize f_len, r_len, pos;
  gchar *cur, *next;
  guint n = 0;

  g_return_val_if_fail (string != NULL, 0);
  g_return_val_if_fail (find != NULL, 0);
  g_return_val_if_fail (replace != NULL, 0);

  f_len = strlen (find);
  r_len = strlen (replace);
  cur = string->str;

  while ((next = strstr (cur, find)) != NULL)
    {
      pos = next - string->str;
      g_string_erase (string, pos, f_len);
      g_string_insert (string, pos, replace);
      cur = string->str + pos + r_len;
      n++;
      /* Only match the empty string once at any given position, to
       * avoid infinite loops */
      if (f_len == 0)
        {
          if (cur[0] == '\0')
            break;
          else
            cur++;
        }
      if (n == limit)
        break;
    }

  return n;
}
#endif /* GLIB_CHECK_VERSION */

/* adaptations */

G_END_DECLS

#endif
  07070100000031000081A400000000000000000000000169D554BF0000002A000000000000000000000000000000000000002700000000gst-rtsp-server-1.28.2/gst/meson.build    subdir('rtsp-server')
subdir('rtsp-sink')
  07070100000032000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002700000000gst-rtsp-server-1.28.2/gst/rtsp-server    07070100000033000081A400000000000000000000000169D554BF00000CDA000000000000000000000000000000000000003300000000gst-rtsp-server-1.28.2/gst/rtsp-server/meson.build    rtsp_server_sources = files(
  'rtsp-address-pool.c',
  'rtsp-auth.c',
  'rtsp-client.c',
  'rtsp-context.c',
  'rtsp-latency-bin.c',
  'rtsp-media.c',
  'rtsp-media-factory.c',
  'rtsp-media-factory-uri.c',
  'rtsp-mount-points.c',
  'rtsp-params.c',
  'rtsp-permissions.c',
  'rtsp-sdp.c',
  'rtsp-server.c',
  'rtsp-session.c',
  'rtsp-session-media.c',
  'rtsp-session-pool.c',
  'rtsp-stream.c',
  'rtsp-stream-transport.c',
  'rtsp-thread-pool.c',
  'rtsp-token.c',
  'rtsp-onvif-server.c',
  'rtsp-onvif-client.c',
  'rtsp-onvif-media-factory.c',
  'rtsp-onvif-media.c',
)

rtsp_server_headers = files(
  'rtsp-auth.h',
  'rtsp-address-pool.h',
  'rtsp-context.h',
  'rtsp-params.h',
  'rtsp-sdp.h',
  'rtsp-thread-pool.h',
  'rtsp-media.h',
  'rtsp-media-factory.h',
  'rtsp-media-factory-uri.h',
  'rtsp-mount-points.h',
  'rtsp-permissions.h',
  'rtsp-stream.h',
  'rtsp-stream-transport.h',
  'rtsp-session.h',
  'rtsp-session-media.h',
  'rtsp-session-pool.h',
  'rtsp-token.h',
  'rtsp-client.h',
  'rtsp-server.h',
  'rtsp-server-object.h',
  'rtsp-server-prelude.h',
  'rtsp-onvif-server.h',
  'rtsp-onvif-client.h',
  'rtsp-onvif-media-factory.h',
  'rtsp-onvif-media.h',
)

install_headers(rtsp_server_headers, subdir : 'gstreamer-1.0/gst/rtsp-server')

gst_rtsp_server_deps = [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep, gst_dep, gstvideo_dep]
gst_rtsp_server = library('gstrtspserver-@0@'.format(api_version),
  rtsp_server_sources,
  include_directories : rtspserver_incs,
  c_args: rtspserver_args + ['-DBUILDING_GST_RTSP_SERVER', '-DG_LOG_DOMAIN="GStreamer-RTSP-Server"'],
  version : libversion,
  soversion : soversion,
  darwin_versions : osxversion,
  install : true,
  dependencies : gst_rtsp_server_deps)

library_def = {'lib': gst_rtsp_server}
pkg_name = 'gstreamer-rtsp-server-' + api_version
pkgconfig.generate(gst_rtsp_server,
  libraries : [gst_dep],
  subdirs : pkgconfig_subdirs,
  name : 'gstreamer-rtsp-server-1.0',
  description : 'GStreamer based RTSP server',
)

rtsp_server_gen_sources = []
if build_gir
  gst_gir_extra_args = gir_init_section + ['--c-include=gst/rtsp-server/rtsp-server.h']
  gir = {
    'sources' : rtsp_server_headers + rtsp_server_sources,
    'namespace' : 'GstRtspServer',
    'nsversion' : api_version,
    'identifier_prefix' : 'Gst',
    'symbol_prefix' : 'gst',
    'export_packages' : pkg_name,
    'install' : true,
    'extra_args' : gst_gir_extra_args,
    'includes' : ['Gst-1.0', 'GstRtsp-1.0', 'GstNet-1.0'],
    'dependencies' : gst_rtsp_server_deps,
  }
  # FIXME: Expose gir for use in gstreamer-full
  # Disabled for now as we get `undefined reference` to many symbols
  # library_def = {'gir': [gir]}
  if not static_build
    rtsp_server_gir = gnome.generate_gir(gst_rtsp_server, kwargs: gir)
    library_def += {'gir_targets':  library_def.get('gir_targets', []) + [rtsp_server_gir]}
    rtsp_server_gen_sources += [rtsp_server_gir]
  endif
endif
gst_libraries += [[pkg_name, library_def]]

gst_rtsp_server_dep = declare_dependency(link_with : gst_rtsp_server,
  include_directories : rtspserver_incs,
  sources : rtsp_server_gen_sources,
  dependencies : [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep, gstvideo_dep])

meson.override_dependency(pkg_name, gst_rtsp_server_dep)
  07070100000034000081A400000000000000000000000169D554BF0000502F000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-address-pool.c    /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-address-pool
 * @short_description: A pool of network addresses
 * @see_also: #GstRTSPStream, #GstRTSPStreamTransport
 *
 * The #GstRTSPAddressPool is an object that maintains a collection of network
 * addresses. It is used to allocate server ports and server multicast addresses
 * but also to reserve client provided destination addresses.
 *
 * A range of addresses can be added with gst_rtsp_address_pool_add_range().
 * Both multicast and unicast addresses can be added.
 *
 * With gst_rtsp_address_pool_acquire_address() an unused address and port range
 * can be acquired from the pool. With gst_rtsp_address_pool_reserve_address() a
 * specific address can be retrieved. Both methods return a boxed
 * #GstRTSPAddress that should be freed with gst_rtsp_address_free() after
 * usage, which brings the address back into the pool.
 *
 * Last reviewed on 2013-07-16 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <gio/gio.h>

#include "rtsp-address-pool.h"

/**
 * gst_rtsp_address_copy:
 * @addr: a #GstRTSPAddress
 *
 * Make a copy of @addr.
 *
 * Returns: a copy of @addr.
 */
GstRTSPAddress *
gst_rtsp_address_copy (GstRTSPAddress * addr)
{
  GstRTSPAddress *copy;

  g_return_val_if_fail (addr != NULL, NULL);

  copy = g_memdup2 (addr, sizeof (GstRTSPAddress));
  /* only release to the pool when the original is freed. It's a bit
   * weird but this will do for now as it avoid us to use refcounting. */
  copy->pool = NULL;
  copy->address = g_strdup (copy->address);

  return copy;
}

static void gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool,
    GstRTSPAddress * addr);

/**
 * gst_rtsp_address_free:
 * @addr: a #GstRTSPAddress
 *
 * Free @addr and releasing it back into the pool when owned by a
 * pool.
 */
void
gst_rtsp_address_free (GstRTSPAddress * addr)
{
  g_return_if_fail (addr != NULL);

  if (addr->pool) {
    /* unrefs the pool and sets it to NULL */
    gst_rtsp_address_pool_release_address (addr->pool, addr);
  }
  g_free (addr->address);
  g_free (addr);
}

G_DEFINE_BOXED_TYPE (GstRTSPAddress, gst_rtsp_address,
    (GBoxedCopyFunc) gst_rtsp_address_copy,
    (GBoxedFreeFunc) gst_rtsp_address_free);

GST_DEBUG_CATEGORY_STATIC (rtsp_address_pool_debug);
#define GST_CAT_DEFAULT rtsp_address_pool_debug

#define GST_RTSP_ADDRESS_POOL_GET_PRIVATE(obj)  \
     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPoolPrivate))

struct _GstRTSPAddressPoolPrivate
{
  GMutex lock;                  /* protects everything in this struct */
  GList *addresses;
  GList *allocated;

  gboolean has_unicast_addresses;
};

#define ADDR_IS_IPV4(a)      ((a)->size == 4)
#define ADDR_IS_IPV6(a)      ((a)->size == 16)
#define ADDR_IS_EVEN_PORT(a) (((a)->port & 1) == 0)

typedef struct
{
  guint8 bytes[16];
  gsize size;
  guint16 port;
} Addr;

typedef struct
{
  Addr min;
  Addr max;
  guint8 ttl;
} AddrRange;

#define RANGE_IS_SINGLE(r) (memcmp ((r)->min.bytes, (r)->max.bytes, (r)->min.size) == 0)

#define gst_rtsp_address_pool_parent_class parent_class
G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPAddressPool, gst_rtsp_address_pool,
    G_TYPE_OBJECT);

static void gst_rtsp_address_pool_finalize (GObject * obj);

static void
gst_rtsp_address_pool_class_init (GstRTSPAddressPoolClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gst_rtsp_address_pool_finalize;

  GST_DEBUG_CATEGORY_INIT (rtsp_address_pool_debug, "rtspaddresspool", 0,
      "GstRTSPAddressPool");
}

static void
gst_rtsp_address_pool_init (GstRTSPAddressPool * pool)
{
  pool->priv = gst_rtsp_address_pool_get_instance_private (pool);

  g_mutex_init (&pool->priv->lock);
}

static void
free_range (AddrRange * range)
{
  g_free (range);
}

static void
gst_rtsp_address_pool_finalize (GObject * obj)
{
  GstRTSPAddressPool *pool;

  pool = GST_RTSP_ADDRESS_POOL (obj);

  g_list_free_full (pool->priv->addresses, (GDestroyNotify) free_range);
  g_list_free_full (pool->priv->allocated, (GDestroyNotify) free_range);
  g_mutex_clear (&pool->priv->lock);

  G_OBJECT_CLASS (parent_class)->finalize (obj);
}

/**
 * gst_rtsp_address_pool_new:
 *
 * Make a new #GstRTSPAddressPool.
 *
 * Returns: (transfer full): a new #GstRTSPAddressPool
 */
GstRTSPAddressPool *
gst_rtsp_address_pool_new (void)
{
  GstRTSPAddressPool *pool;

  pool = g_object_new (GST_TYPE_RTSP_ADDRESS_POOL, NULL);

  return pool;
}

/**
 * gst_rtsp_address_pool_clear:
 * @pool: a #GstRTSPAddressPool
 *
 * Clear all addresses in @pool. There should be no outstanding
 * allocations.
 */
void
gst_rtsp_address_pool_clear (GstRTSPAddressPool * pool)
{
  GstRTSPAddressPoolPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
  g_return_if_fail (pool->priv->allocated == NULL);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  g_list_free_full (priv->addresses, (GDestroyNotify) free_range);
  priv->addresses = NULL;
  g_mutex_unlock (&priv->lock);
}

static gboolean
fill_address (const gchar * address, guint16 port, Addr * addr,
    gboolean is_multicast)
{
  GInetAddress *inet;

  inet = g_inet_address_new_from_string (address);
  if (inet == NULL)
    return FALSE;

  if (is_multicast != g_inet_address_get_is_multicast (inet)) {
    g_object_unref (inet);
    return FALSE;
  }

  addr->size = g_inet_address_get_native_size (inet);
  memcpy (addr->bytes, g_inet_address_to_bytes (inet), addr->size);
  g_object_unref (inet);
  addr->port = port;

  return TRUE;
}

static gchar *
get_address_string (Addr * addr)
{
  gchar *res;
  GInetAddress *inet;

  inet = g_inet_address_new_from_bytes (addr->bytes,
      addr->size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
  res = g_inet_address_to_string (inet);
  g_object_unref (inet);

  return res;
}

/**
 * gst_rtsp_address_pool_add_range:
 * @pool: a #GstRTSPAddressPool
 * @min_address: a minimum address to add
 * @max_address: a maximum address to add
 * @min_port: the minimum port
 * @max_port: the maximum port
 * @ttl: a TTL or 0 for unicast addresses
 *
 * Adds the addresses from @min_addess to @max_address (inclusive)
 * to @pool. The valid port range for the addresses will be from @min_port to
 * @max_port inclusive.
 *
 * When @ttl is 0, @min_address and @max_address should be unicast addresses.
 * @min_address and @max_address can be set to
 * #GST_RTSP_ADDRESS_POOL_ANY_IPV4 or #GST_RTSP_ADDRESS_POOL_ANY_IPV6 to bind
 * to all available IPv4 or IPv6 addresses.
 *
 * When @ttl > 0, @min_address and @max_address should be multicast addresses.
 *
 * Returns: %TRUE if the addresses could be added.
 */
gboolean
gst_rtsp_address_pool_add_range (GstRTSPAddressPool * pool,
    const gchar * min_address, const gchar * max_address,
    guint16 min_port, guint16 max_port, guint8 ttl)
{
  AddrRange *range;
  GstRTSPAddressPoolPrivate *priv;
  gboolean is_multicast;

  g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), FALSE);
  g_return_val_if_fail (min_port <= max_port, FALSE);

  priv = pool->priv;

  is_multicast = ttl != 0;

  range = g_new0 (AddrRange, 1);

  if (!fill_address (min_address, min_port, &range->min, is_multicast))
    goto invalid;
  if (!fill_address (max_address, max_port, &range->max, is_multicast))
    goto invalid;

  if (range->min.size != range->max.size)
    goto invalid;
  if (memcmp (range->min.bytes, range->max.bytes, range->min.size) > 0)
    goto invalid;

  range->ttl = ttl;

  GST_DEBUG_OBJECT (pool, "adding %s-%s:%u-%u ttl %u", min_address, max_address,
      min_port, max_port, ttl);

  g_mutex_lock (&priv->lock);
  priv->addresses = g_list_prepend (priv->addresses, range);

  if (!is_multicast)
    priv->has_unicast_addresses = TRUE;
  g_mutex_unlock (&priv->lock);

  return TRUE;

  /* ERRORS */
invalid:
  {
    GST_ERROR_OBJECT (pool, "invalid address range %s-%s", min_address,
        max_address);
    g_free (range);
    return FALSE;
  }
}

static void
inc_address (Addr * addr, guint count)
{
  gint i;
  guint carry;

  carry = count;
  for (i = addr->size - 1; i >= 0 && carry > 0; i--) {
    carry += addr->bytes[i];
    addr->bytes[i] = carry & 0xff;
    carry >>= 8;
  }
}

/* tells us the number of addresses between min_addr and max_addr */
static guint
diff_address (Addr * max_addr, Addr * min_addr)
{
  gint i;
  guint result = 0;

  g_return_val_if_fail (min_addr->size == max_addr->size, 0);

  for (i = 0; i < min_addr->size; i++) {
    g_return_val_if_fail (result < (1 << 24), result);

    result <<= 8;
    result += max_addr->bytes[i] - min_addr->bytes[i];
  }

  return result;
}

static AddrRange *
split_range (GstRTSPAddressPool * pool, AddrRange * range, guint skip_addr,
    guint skip_port, gint n_ports)
{
  GstRTSPAddressPoolPrivate *priv = pool->priv;
  AddrRange *temp;

  if (skip_addr) {
    temp = g_memdup2 (range, sizeof (AddrRange));
    memcpy (temp->max.bytes, temp->min.bytes, temp->min.size);
    inc_address (&temp->max, skip_addr - 1);
    priv->addresses = g_list_prepend (priv->addresses, temp);

    inc_address (&range->min, skip_addr);
  }

  if (!RANGE_IS_SINGLE (range)) {
    /* min and max are not the same, we have more than one address. */
    temp = g_memdup2 (range, sizeof (AddrRange));
    /* increment the range min address */
    inc_address (&temp->min, 1);
    /* and store back in pool */
    priv->addresses = g_list_prepend (priv->addresses, temp);

    /* adjust range with only the first address */
    memcpy (range->max.bytes, range->min.bytes, range->min.size);
  }

  /* range now contains only one single address */
  if (skip_port > 0) {
    /* make a range with the skipped ports */
    temp = g_memdup2 (range, sizeof (AddrRange));
    temp->max.port = temp->min.port + skip_port - 1;
    /* and store back in pool */
    priv->addresses = g_list_prepend (priv->addresses, temp);

    /* increment range port */
    range->min.port += skip_port;
  }
  /* range now contains single address with desired port number */
  if (range->max.port - range->min.port + 1 > n_ports) {
    /* make a range with the remaining ports */
    temp = g_memdup2 (range, sizeof (AddrRange));
    temp->min.port += n_ports;
    /* and store back in pool */
    priv->addresses = g_list_prepend (priv->addresses, temp);

    /* and truncate port */
    range->max.port = range->min.port + n_ports - 1;
  }
  return range;
}

/**
 * gst_rtsp_address_pool_acquire_address:
 * @pool: a #GstRTSPAddressPool
 * @flags: flags
 * @n_ports: the amount of ports
 *
 * Take an address and ports from @pool. @flags can be used to control the
 * allocation. @n_ports consecutive ports will be allocated of which the first
 * one can be found in @port.
 *
 * Returns: (nullable): a #GstRTSPAddress that should be freed with
 * gst_rtsp_address_free after use or %NULL when no address could be
 * acquired.
 */
GstRTSPAddress *
gst_rtsp_address_pool_acquire_address (GstRTSPAddressPool * pool,
    GstRTSPAddressFlags flags, gint n_ports)
{
  GstRTSPAddressPoolPrivate *priv;
  GList *walk, *next;
  AddrRange *result;
  GstRTSPAddress *addr;

  g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), NULL);
  g_return_val_if_fail (n_ports > 0, NULL);

  priv = pool->priv;
  result = NULL;
  addr = NULL;

  g_mutex_lock (&priv->lock);
  /* go over available ranges */
  for (walk = priv->addresses; walk; walk = next) {
    AddrRange *range;
    gint ports, skip;

    range = walk->data;
    next = walk->next;

    /* check address type when given */
    if (flags & GST_RTSP_ADDRESS_FLAG_IPV4 && !ADDR_IS_IPV4 (&range->min))
      continue;
    if (flags & GST_RTSP_ADDRESS_FLAG_IPV6 && !ADDR_IS_IPV6 (&range->min))
      continue;
    if (flags & GST_RTSP_ADDRESS_FLAG_MULTICAST && range->ttl == 0)
      continue;
    if (flags & GST_RTSP_ADDRESS_FLAG_UNICAST && range->ttl != 0)
      continue;

    /* check for enough ports */
    ports = range->max.port - range->min.port + 1;
    if (flags & GST_RTSP_ADDRESS_FLAG_EVEN_PORT
        && !ADDR_IS_EVEN_PORT (&range->min))
      skip = 1;
    else
      skip = 0;
    if (ports - skip < n_ports)
      continue;

    /* we found a range, remove from the list */
    priv->addresses = g_list_delete_link (priv->addresses, walk);
    /* now split and exit our loop */
    result = split_range (pool, range, 0, skip, n_ports);
    priv->allocated = g_list_prepend (priv->allocated, result);
    break;
  }
  g_mutex_unlock (&priv->lock);

  if (result) {
    addr = g_new0 (GstRTSPAddress, 1);
    addr->pool = g_object_ref (pool);
    addr->address = get_address_string (&result->min);
    addr->n_ports = n_ports;
    addr->port = result->min.port;
    addr->ttl = result->ttl;
    addr->priv = result;

    GST_DEBUG_OBJECT (pool, "got address %s:%u ttl %u", addr->address,
        addr->port, addr->ttl);
  }

  return addr;
}

/**
 * gst_rtsp_address_pool_release_address:
 * @pool: a #GstRTSPAddressPool
 * @id: an address id
 *
 * Release a previously acquired address (with
 * gst_rtsp_address_pool_acquire_address()) back into @pool.
 */
static void
gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool,
    GstRTSPAddress * addr)
{
  GstRTSPAddressPoolPrivate *priv;
  GList *find;
  AddrRange *range;

  g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
  g_return_if_fail (addr != NULL);
  g_return_if_fail (addr->pool == pool);

  priv = pool->priv;
  range = addr->priv;

  /* we don't want to free twice */
  addr->priv = NULL;
  addr->pool = NULL;

  g_mutex_lock (&priv->lock);
  find = g_list_find (priv->allocated, range);
  if (find == NULL)
    goto not_found;

  priv->allocated = g_list_delete_link (priv->allocated, find);

  /* FIXME, merge and do something clever */
  priv->addresses = g_list_prepend (priv->addresses, range);
  g_mutex_unlock (&priv->lock);

  g_object_unref (pool);

  return;

  /* ERRORS */
not_found:
  {
    g_warning ("Released unknown address %p", addr);
    g_mutex_unlock (&priv->lock);
    return;
  }
}

static void
dump_range (AddrRange * range, GstRTSPAddressPool * pool)
{
  gchar *addr1, *addr2;

  addr1 = get_address_string (&range->min);
  addr2 = get_address_string (&range->max);
  g_print ("  address %s-%s, port %u-%u, ttl %u\n", addr1, addr2,
      range->min.port, range->max.port, range->ttl);
  g_free (addr1);
  g_free (addr2);
}

/**
 * gst_rtsp_address_pool_dump:
 * @pool: a #GstRTSPAddressPool
 *
 * Dump the free and allocated addresses to stdout.
 */
void
gst_rtsp_address_pool_dump (GstRTSPAddressPool * pool)
{
  GstRTSPAddressPoolPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  g_print ("free:\n");
  g_list_foreach (priv->addresses, (GFunc) dump_range, pool);
  g_print ("allocated:\n");
  g_list_foreach (priv->allocated, (GFunc) dump_range, pool);
  g_mutex_unlock (&priv->lock);
}

static GList *
find_address_in_ranges (GList * addresses, Addr * addr, guint port,
    guint n_ports, guint ttl)
{
  GList *walk, *next;

  /* go over available ranges */
  for (walk = addresses; walk; walk = next) {
    AddrRange *range;

    range = walk->data;
    next = walk->next;

    /* Not the right type of address */
    if (range->min.size != addr->size)
      continue;

    /* Check that the address is in the interval */
    if (memcmp (range->min.bytes, addr->bytes, addr->size) > 0 ||
        memcmp (range->max.bytes, addr->bytes, addr->size) < 0)
      continue;

    /* Make sure the requested ports are inside the range */
    if (port < range->min.port || port + n_ports - 1 > range->max.port)
      continue;

    if (ttl != range->ttl)
      continue;

    break;
  }

  return walk;
}

/**
 * gst_rtsp_address_pool_reserve_address:
 * @pool: a #GstRTSPAddressPool
 * @ip_address: The IP address to reserve
 * @port: The first port to reserve
 * @n_ports: The number of ports
 * @ttl: The requested ttl
 * @address: (out): storage for a #GstRTSPAddress
 *
 * Take a specific address and ports from @pool. @n_ports consecutive
 * ports will be allocated of which the first one can be found in
 * @port.
 *
 * If @ttl is 0, @address should be a unicast address. If @ttl > 0, @address
 * should be a valid multicast address.
 *
 * Returns: #GST_RTSP_ADDRESS_POOL_OK if an address was reserved. The address
 * is returned in @address and should be freed with gst_rtsp_address_free
 * after use.
 */
GstRTSPAddressPoolResult
gst_rtsp_address_pool_reserve_address (GstRTSPAddressPool * pool,
    const gchar * ip_address, guint port, guint n_ports, guint ttl,
    GstRTSPAddress ** address)
{
  GstRTSPAddressPoolPrivate *priv;
  Addr input_addr;
  GList *list;
  AddrRange *addr_range;
  GstRTSPAddress *addr;
  gboolean is_multicast;
  GstRTSPAddressPoolResult result;

  g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool),
      GST_RTSP_ADDRESS_POOL_EINVAL);
  g_return_val_if_fail (ip_address != NULL, GST_RTSP_ADDRESS_POOL_EINVAL);
  g_return_val_if_fail (port > 0, GST_RTSP_ADDRESS_POOL_EINVAL);
  g_return_val_if_fail (n_ports > 0, GST_RTSP_ADDRESS_POOL_EINVAL);
  g_return_val_if_fail (address != NULL, GST_RTSP_ADDRESS_POOL_EINVAL);

  priv = pool->priv;
  addr_range = NULL;
  addr = NULL;
  is_multicast = ttl != 0;

  if (!fill_address (ip_address, port, &input_addr, is_multicast))
    goto invalid;

  g_mutex_lock (&priv->lock);
  list = find_address_in_ranges (priv->addresses, &input_addr, port, n_ports,
      ttl);
  if (list != NULL) {
    AddrRange *range = list->data;
    guint skip_port, skip_addr;

    skip_addr = diff_address (&input_addr, &range->min);
    skip_port = port - range->min.port;

    GST_DEBUG_OBJECT (pool, "diff 0x%08x/%u", skip_addr, skip_port);

    /* we found a range, remove from the list */
    priv->addresses = g_list_delete_link (priv->addresses, list);
    /* now split and exit our loop */
    addr_range = split_range (pool, range, skip_addr, skip_port, n_ports);
    priv->allocated = g_list_prepend (priv->allocated, addr_range);
  }

  if (addr_range) {
    addr = g_new0 (GstRTSPAddress, 1);
    addr->pool = g_object_ref (pool);
    addr->address = get_address_string (&addr_range->min);
    addr->n_ports = n_ports;
    addr->port = addr_range->min.port;
    addr->ttl = addr_range->ttl;
    addr->priv = addr_range;

    result = GST_RTSP_ADDRESS_POOL_OK;
    GST_DEBUG_OBJECT (pool, "reserved address %s:%u ttl %u", addr->address,
        addr->port, addr->ttl);
  } else {
    /* We failed to reserve the address. Check if it was because the address
     * was already in use or if it wasn't in the pool to begin with */
    list = find_address_in_ranges (priv->allocated, &input_addr, port, n_ports,
        ttl);
    if (list != NULL) {
      result = GST_RTSP_ADDRESS_POOL_ERESERVED;
    } else {
      result = GST_RTSP_ADDRESS_POOL_ERANGE;
    }
  }
  g_mutex_unlock (&priv->lock);

  *address = addr;
  return result;

  /* ERRORS */
invalid:
  {
    GST_ERROR_OBJECT (pool, "invalid address %s:%u/%u/%u", ip_address,
        port, n_ports, ttl);
    *address = NULL;
    return GST_RTSP_ADDRESS_POOL_EINVAL;
  }
}

/**
 * gst_rtsp_address_pool_has_unicast_addresses:
 * @pool: a #GstRTSPAddressPool
 *
 * Used to know if the pool includes any unicast addresses.
 *
 * Returns: %TRUE if the pool includes any unicast addresses, %FALSE otherwise
 */

gboolean
gst_rtsp_address_pool_has_unicast_addresses (GstRTSPAddressPool * pool)
{
  GstRTSPAddressPoolPrivate *priv;
  gboolean has_unicast_addresses;

  g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), FALSE);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  has_unicast_addresses = priv->has_unicast_addresses;
  g_mutex_unlock (&priv->lock);

  return has_unicast_addresses;
}
 07070100000035000081A400000000000000000000000169D554BF00001BA7000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-address-pool.h    /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_ADDRESS_POOL_H__
#define __GST_RTSP_ADDRESS_POOL_H__

#include <gst/gst.h>
#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

#define GST_TYPE_RTSP_ADDRESS_POOL              (gst_rtsp_address_pool_get_type ())
#define GST_IS_RTSP_ADDRESS_POOL(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_ADDRESS_POOL))
#define GST_IS_RTSP_ADDRESS_POOL_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_ADDRESS_POOL))
#define GST_RTSP_ADDRESS_POOL_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPoolClass))
#define GST_RTSP_ADDRESS_POOL(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPool))
#define GST_RTSP_ADDRESS_POOL_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPoolClass))
#define GST_RTSP_ADDRESS_POOL_CAST(obj)         ((GstRTSPAddressPool*)(obj))
#define GST_RTSP_ADDRESS_POOL_CLASS_CAST(klass) ((GstRTSPAddressPoolClass*)(klass))

/**
 * GstRTSPAddressPoolResult:
 * @GST_RTSP_ADDRESS_POOL_OK: no error
 * @GST_RTSP_ADDRESS_POOL_EINVAL:invalid arguments were provided to a function
 * @GST_RTSP_ADDRESS_POOL_ERESERVED: the addres has already been reserved
 * @GST_RTSP_ADDRESS_POOL_ERANGE: the address is not in the pool
 * @GST_RTSP_ADDRESS_POOL_ELAST: last error
 *
 * Result codes from RTSP address pool functions.
 */
typedef enum {
  GST_RTSP_ADDRESS_POOL_OK         =  0,
  /* errors */
  GST_RTSP_ADDRESS_POOL_EINVAL     = -1,
  GST_RTSP_ADDRESS_POOL_ERESERVED  = -2,
  GST_RTSP_ADDRESS_POOL_ERANGE     = -3,

  GST_RTSP_ADDRESS_POOL_ELAST      = -4,
} GstRTSPAddressPoolResult;


typedef struct _GstRTSPAddress GstRTSPAddress;

typedef struct _GstRTSPAddressPool GstRTSPAddressPool;
typedef struct _GstRTSPAddressPoolClass GstRTSPAddressPoolClass;
typedef struct _GstRTSPAddressPoolPrivate GstRTSPAddressPoolPrivate;

/**
 * GstRTSPAddress:
 * @pool: the #GstRTSPAddressPool owner of this address
 * @address: the address
 * @port: the port number
 * @n_ports: number of ports
 * @ttl: TTL or 0 for unicast addresses
 *
 * An address
 */
struct _GstRTSPAddress {
  GstRTSPAddressPool *pool;

  gchar *address;
  guint16 port;
  gint n_ports;
  guint8 ttl;

  /*<private >*/
  gpointer priv;
};

GST_RTSP_SERVER_API
GType gst_rtsp_address_get_type        (void);

GST_RTSP_SERVER_API
GstRTSPAddress * gst_rtsp_address_copy (GstRTSPAddress *addr) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void             gst_rtsp_address_free (GstRTSPAddress *addr);

/**
 * GstRTSPAddressFlags:
 * @GST_RTSP_ADDRESS_FLAG_NONE: no flags
 * @GST_RTSP_ADDRESS_FLAG_IPV4: an IPv4 address
 * @GST_RTSP_ADDRESS_FLAG_IPV6: and IPv6 address
 * @GST_RTSP_ADDRESS_FLAG_EVEN_PORT: address with an even port
 * @GST_RTSP_ADDRESS_FLAG_MULTICAST: a multicast address
 * @GST_RTSP_ADDRESS_FLAG_UNICAST: a unicast address
 *
 * Flags used to control allocation of addresses
 */
typedef enum {
  GST_RTSP_ADDRESS_FLAG_NONE      = 0,
  GST_RTSP_ADDRESS_FLAG_IPV4      = (1 << 0),
  GST_RTSP_ADDRESS_FLAG_IPV6      = (1 << 1),
  GST_RTSP_ADDRESS_FLAG_EVEN_PORT = (1 << 2),
  GST_RTSP_ADDRESS_FLAG_MULTICAST = (1 << 3),
  GST_RTSP_ADDRESS_FLAG_UNICAST   = (1 << 4),
} GstRTSPAddressFlags;

/**
 * GST_RTSP_ADDRESS_POOL_ANY_IPV4:
 *
 * Used with gst_rtsp_address_pool_add_range() to bind to all
 * IPv4 addresses
 */
#define GST_RTSP_ADDRESS_POOL_ANY_IPV4  "0.0.0.0"

/**
 * GST_RTSP_ADDRESS_POOL_ANY_IPV6:
 *
 * Used with gst_rtsp_address_pool_add_range() to bind to all
 * IPv6 addresses
 */
#define GST_RTSP_ADDRESS_POOL_ANY_IPV6  "::"

/**
 * GstRTSPAddressPool:
 * @parent: the parent GObject
 *
 * An address pool, all member are private
 */
struct _GstRTSPAddressPool {
  GObject       parent;

  /*< private >*/
  GstRTSPAddressPoolPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPAddressPoolClass:
 *
 * Opaque Address pool class.
 */
struct _GstRTSPAddressPoolClass {
  GObjectClass  parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                  gst_rtsp_address_pool_get_type        (void);

/* create a new address pool */

GST_RTSP_SERVER_API
GstRTSPAddressPool *   gst_rtsp_address_pool_new             (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                   gst_rtsp_address_pool_clear           (GstRTSPAddressPool * pool);

GST_RTSP_SERVER_API
void                   gst_rtsp_address_pool_dump            (GstRTSPAddressPool * pool);

GST_RTSP_SERVER_API
gboolean               gst_rtsp_address_pool_add_range       (GstRTSPAddressPool * pool,
                                                              const gchar *min_address,
                                                              const gchar *max_address,
                                                              guint16 min_port,
                                                              guint16 max_port,
                                                              guint8 ttl);

GST_RTSP_SERVER_API
GstRTSPAddress *       gst_rtsp_address_pool_acquire_address (GstRTSPAddressPool * pool,
                                                              GstRTSPAddressFlags flags,
                                                              gint n_ports) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPAddressPoolResult  gst_rtsp_address_pool_reserve_address (GstRTSPAddressPool * pool,
                                                              const gchar *ip_address,
                                                              guint port,
                                                              guint n_ports,
                                                              guint ttl,
                                                              GstRTSPAddress ** address);

GST_RTSP_SERVER_API
gboolean               gst_rtsp_address_pool_has_unicast_addresses (GstRTSPAddressPool * pool);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPAddress, gst_rtsp_address_free)
#endif

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPAddressPool, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_ADDRESS_POOL_H__ */
 07070100000036000081A400000000000000000000000169D554BF00007F4A000000000000000000000000000000000000003300000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-auth.c    /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-auth
 * @short_description: Authentication and authorization
 * @see_also: #GstRTSPPermissions, #GstRTSPToken
 *
 * The #GstRTSPAuth object is responsible for checking if the current user is
 * allowed to perform requested actions. The default implementation has some
 * reasonable checks but subclasses can implement custom security policies.
 *
 * A new auth object is made with gst_rtsp_auth_new(). It is usually configured
 * on the #GstRTSPServer object.
 *
 * The RTSP server will call gst_rtsp_auth_check() with a string describing the
 * check to perform. The possible checks are prefixed with
 * GST_RTSP_AUTH_CHECK_*. Depending on the check, the default implementation
 * will use the current #GstRTSPToken, #GstRTSPContext and
 * #GstRTSPPermissions on the object to check if an operation is allowed.
 *
 * The default #GstRTSPAuth object has support for basic authentication. With
 * gst_rtsp_auth_add_basic() you can add a basic authentication string together
 * with the #GstRTSPToken that will become active when successfully
 * authenticated.
 *
 * When a TLS certificate has been set with gst_rtsp_auth_set_tls_certificate(),
 * the default auth object will require the client to connect with a TLS
 * connection.
 *
 * Last reviewed on 2013-07-16 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-auth.h"

struct _GstRTSPAuthPrivate
{
  GMutex lock;

  /* the TLS certificate */
  GTlsCertificate *certificate;
  GTlsDatabase *database;
  GTlsAuthenticationMode mode;
  GHashTable *basic;            /* protected by lock */
  GHashTable *digest, *nonces;  /* protected by lock */
  guint64 last_nonce_check;
  GstRTSPToken *default_token;
  GstRTSPMethod methods;
  GstRTSPAuthMethod auth_methods;
  gchar *realm;
};

typedef struct
{
  GstRTSPToken *token;
  gchar *pass;
  gchar *md5_pass;
} GstRTSPDigestEntry;

typedef struct
{
  gchar *nonce;
  gchar *ip;
  guint64 timestamp;
  gpointer client;
} GstRTSPDigestNonce;

static void
gst_rtsp_digest_entry_free (GstRTSPDigestEntry * entry)
{
  gst_rtsp_token_unref (entry->token);
  g_free (entry->pass);
  g_free (entry->md5_pass);
  g_free (entry);
}

static void
gst_rtsp_digest_nonce_free (GstRTSPDigestNonce * nonce)
{
  g_free (nonce->nonce);
  g_free (nonce->ip);
  g_free (nonce);
}

enum
{
  PROP_0,
  PROP_LAST
};

enum
{
  SIGNAL_ACCEPT_CERTIFICATE,
  SIGNAL_LAST
};

static guint signals[SIGNAL_LAST] = { 0 };

GST_DEBUG_CATEGORY_STATIC (rtsp_auth_debug);
#define GST_CAT_DEFAULT rtsp_auth_debug

static void gst_rtsp_auth_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_auth_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_auth_finalize (GObject * obj);

static gboolean default_authenticate (GstRTSPAuth * auth, GstRTSPContext * ctx);
static gboolean default_check (GstRTSPAuth * auth, GstRTSPContext * ctx,
    const gchar * check);
static void default_generate_authenticate_header (GstRTSPAuth * auth,
    GstRTSPContext * ctx);


G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPAuth, gst_rtsp_auth, G_TYPE_OBJECT);

static void
gst_rtsp_auth_class_init (GstRTSPAuthClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_auth_get_property;
  gobject_class->set_property = gst_rtsp_auth_set_property;
  gobject_class->finalize = gst_rtsp_auth_finalize;

  klass->authenticate = default_authenticate;
  klass->check = default_check;
  klass->generate_authenticate_header = default_generate_authenticate_header;

  GST_DEBUG_CATEGORY_INIT (rtsp_auth_debug, "rtspauth", 0, "GstRTSPAuth");

  /**
   * GstRTSPAuth::accept-certificate:
   * @auth: a #GstRTSPAuth
   * @connection: a #GTlsConnection
   * @peer_cert: the peer's #GTlsCertificate
   * @errors: the problems with @peer_cert.
   *
   * Emitted during the TLS handshake after the client certificate has
   * been received. See also gst_rtsp_auth_set_tls_authentication_mode().
   *
   * Returns: %TRUE to accept @peer_cert (which will also
   * immediately end the signal emission). %FALSE to allow the signal
   * emission to continue, which will cause the handshake to fail if
   * no one else overrides it.
   *
   * Since: 1.6
   */
  signals[SIGNAL_ACCEPT_CERTIFICATE] = g_signal_new ("accept-certificate",
      G_TYPE_FROM_CLASS (gobject_class),
      G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPAuthClass, accept_certificate),
      g_signal_accumulator_true_handled, NULL, NULL,
      G_TYPE_BOOLEAN, 3, G_TYPE_TLS_CONNECTION, G_TYPE_TLS_CERTIFICATE,
      G_TYPE_TLS_CERTIFICATE_FLAGS);
}

static void
gst_rtsp_auth_init (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;

  auth->priv = priv = gst_rtsp_auth_get_instance_private (auth);

  g_mutex_init (&priv->lock);

  priv->basic = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
      (GDestroyNotify) gst_rtsp_token_unref);
  priv->digest = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
      (GDestroyNotify) gst_rtsp_digest_entry_free);
  priv->nonces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
      (GDestroyNotify) gst_rtsp_digest_nonce_free);

  /* bitwise or of all methods that need authentication */
  priv->methods = 0;
  priv->auth_methods = GST_RTSP_AUTH_BASIC;
  priv->realm = g_strdup ("GStreamer RTSP Server");
}

static void
gst_rtsp_auth_finalize (GObject * obj)
{
  GstRTSPAuth *auth = GST_RTSP_AUTH (obj);
  GstRTSPAuthPrivate *priv = auth->priv;

  GST_INFO ("finalize auth %p", auth);

  if (priv->certificate)
    g_object_unref (priv->certificate);
  if (priv->database)
    g_object_unref (priv->database);
  g_hash_table_unref (priv->basic);
  g_hash_table_unref (priv->digest);
  g_hash_table_unref (priv->nonces);
  if (priv->default_token)
    gst_rtsp_token_unref (priv->default_token);
  g_mutex_clear (&priv->lock);
  g_free (priv->realm);

  G_OBJECT_CLASS (gst_rtsp_auth_parent_class)->finalize (obj);
}

static void
gst_rtsp_auth_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  switch (propid) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_auth_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  switch (propid) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_auth_new:
 *
 * Create a new #GstRTSPAuth instance.
 *
 * Returns: (transfer full): a new #GstRTSPAuth
 */
GstRTSPAuth *
gst_rtsp_auth_new (void)
{
  GstRTSPAuth *result;

  result = g_object_new (GST_TYPE_RTSP_AUTH, NULL);

  return result;
}

/**
 * gst_rtsp_auth_set_tls_certificate:
 * @auth: a #GstRTSPAuth
 * @cert: (transfer none) (allow-none): a #GTlsCertificate
 *
 * Set the TLS certificate for the auth. Client connections will only
 * be accepted when TLS is negotiated.
 */
void
gst_rtsp_auth_set_tls_certificate (GstRTSPAuth * auth, GTlsCertificate * cert)
{
  GstRTSPAuthPrivate *priv;
  GTlsCertificate *old;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));

  priv = auth->priv;

  if (cert)
    g_object_ref (cert);

  g_mutex_lock (&priv->lock);
  old = priv->certificate;
  priv->certificate = cert;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_auth_get_tls_certificate:
 * @auth: a #GstRTSPAuth
 *
 * Get the #GTlsCertificate used for negotiating TLS @auth.
 *
 * Returns: (transfer full) (nullable): the #GTlsCertificate of @auth. g_object_unref() after
 * usage.
 */
GTlsCertificate *
gst_rtsp_auth_get_tls_certificate (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;
  GTlsCertificate *result;

  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->certificate))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_auth_set_tls_database:
 * @auth: a #GstRTSPAuth
 * @database: (transfer none) (allow-none): a #GTlsDatabase
 *
 * Sets the certificate database that is used to verify peer certificates.
 * If set to %NULL (the default), then peer certificate validation will always
 * set the %G_TLS_CERTIFICATE_UNKNOWN_CA error.
 *
 * Since: 1.6
 */
void
gst_rtsp_auth_set_tls_database (GstRTSPAuth * auth, GTlsDatabase * database)
{
  GstRTSPAuthPrivate *priv;
  GTlsDatabase *old;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));

  priv = auth->priv;

  if (database)
    g_object_ref (database);

  g_mutex_lock (&priv->lock);
  old = priv->database;
  priv->database = database;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_auth_get_tls_database:
 * @auth: a #GstRTSPAuth
 *
 * Get the #GTlsDatabase used for verifying client certificate.
 *
 * Returns: (transfer full) (nullable): the #GTlsDatabase of @auth. g_object_unref() after
 * usage.
 * Since: 1.6
 */
GTlsDatabase *
gst_rtsp_auth_get_tls_database (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;
  GTlsDatabase *result;

  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->database))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_auth_set_tls_authentication_mode:
 * @auth: a #GstRTSPAuth
 * @mode: a #GTlsAuthenticationMode
 *
 * The #GTlsAuthenticationMode to set on the underlying GTlsServerConnection.
 * When set to another value than %G_TLS_AUTHENTICATION_NONE,
 * #GstRTSPAuth::accept-certificate signal will be emitted and must be handled.
 *
 * Since: 1.6
 */
void
gst_rtsp_auth_set_tls_authentication_mode (GstRTSPAuth * auth,
    GTlsAuthenticationMode mode)
{
  GstRTSPAuthPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  priv->mode = mode;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_auth_get_tls_authentication_mode:
 * @auth: a #GstRTSPAuth
 *
 * Get the #GTlsAuthenticationMode.
 *
 * Returns: the #GTlsAuthenticationMode.
 */
GTlsAuthenticationMode
gst_rtsp_auth_get_tls_authentication_mode (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;
  GTlsAuthenticationMode result;

  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), G_TLS_AUTHENTICATION_NONE);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  result = priv->mode;
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_auth_set_default_token:
 * @auth: a #GstRTSPAuth
 * @token: (transfer none) (allow-none): a #GstRTSPToken
 *
 * Set the default #GstRTSPToken to @token in @auth. The default token will
 * be used for unauthenticated users.
 */
void
gst_rtsp_auth_set_default_token (GstRTSPAuth * auth, GstRTSPToken * token)
{
  GstRTSPAuthPrivate *priv;
  GstRTSPToken *old;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));

  priv = auth->priv;

  if (token)
    gst_rtsp_token_ref (token);

  g_mutex_lock (&priv->lock);
  old = priv->default_token;
  priv->default_token = token;
  g_mutex_unlock (&priv->lock);

  if (old)
    gst_rtsp_token_unref (old);
}

/**
 * gst_rtsp_auth_get_default_token:
 * @auth: a #GstRTSPAuth
 *
 * Get the default token for @auth. This token will be used for unauthenticated
 * users.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPToken of @auth. gst_rtsp_token_unref() after
 * usage.
 */
GstRTSPToken *
gst_rtsp_auth_get_default_token (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;
  GstRTSPToken *result;

  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->default_token))
    gst_rtsp_token_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_auth_add_basic:
 * @auth: a #GstRTSPAuth
 * @basic: the basic token
 * @token: (transfer none): authorisation token
 *
 * Add a basic token for the default authentication algorithm that
 * enables the client with privileges listed in @token.
 */
void
gst_rtsp_auth_add_basic (GstRTSPAuth * auth, const gchar * basic,
    GstRTSPToken * token)
{
  GstRTSPAuthPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));
  g_return_if_fail (basic != NULL);
  g_return_if_fail (GST_IS_RTSP_TOKEN (token));

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  g_hash_table_replace (priv->basic, g_strdup (basic),
      gst_rtsp_token_ref (token));
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_auth_remove_basic:
 * @auth: a #GstRTSPAuth
 * @basic: (transfer none): the basic token
 *
 * Removes @basic authentication token.
 */
void
gst_rtsp_auth_remove_basic (GstRTSPAuth * auth, const gchar * basic)
{
  GstRTSPAuthPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));
  g_return_if_fail (basic != NULL);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  g_hash_table_remove (priv->basic, basic);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_auth_add_digest:
 * @auth: a #GstRTSPAuth
 * @user: the digest user name
 * @pass: the digest password
 * @token: (transfer none): authorisation token
 *
 * Add a digest @user and @pass for the default authentication algorithm that
 * enables the client with privileges listed in @token.
 *
 * Since: 1.12
 */
void
gst_rtsp_auth_add_digest (GstRTSPAuth * auth, const gchar * user,
    const gchar * pass, GstRTSPToken * token)
{
  GstRTSPAuthPrivate *priv;
  GstRTSPDigestEntry *entry;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));
  g_return_if_fail (user != NULL);
  g_return_if_fail (pass != NULL);
  g_return_if_fail (GST_IS_RTSP_TOKEN (token));

  priv = auth->priv;

  entry = g_new0 (GstRTSPDigestEntry, 1);
  entry->token = gst_rtsp_token_ref (token);
  entry->pass = g_strdup (pass);

  g_mutex_lock (&priv->lock);
  g_hash_table_replace (priv->digest, g_strdup (user), entry);
  g_mutex_unlock (&priv->lock);
}

/* With auth lock taken */
static gboolean
update_digest_cb (gchar * key, GstRTSPDigestEntry * entry, GHashTable * digest)
{
  g_hash_table_replace (digest, key, entry);

  return TRUE;
}

/**
 * gst_rtsp_auth_parse_htdigest:
 * @path: (type filename): Path to the htdigest file
 * @token: (transfer none): authorisation token
 *
 * Parse the contents of the file at @path and enable the privileges
 * listed in @token for the users it describes.
 *
 * The format of the file is expected to match the format described by
 * <https://en.wikipedia.org/wiki/Digest_access_authentication#The_.htdigest_file>,
 * as output by the `htdigest` command.
 *
 * Returns: %TRUE if the file was successfully parsed, %FALSE otherwise.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_auth_parse_htdigest (GstRTSPAuth * auth, const gchar * path,
    GstRTSPToken * token)
{
  GstRTSPAuthPrivate *priv;
  gboolean ret = FALSE;
  gchar *line = NULL;
  gchar *eol = NULL;
  gchar *contents = NULL;
  GError *error = NULL;
  GHashTable *new_entries =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
      (GDestroyNotify) gst_rtsp_digest_entry_free);


  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), FALSE);
  g_return_val_if_fail (path != NULL, FALSE);
  g_return_val_if_fail (GST_IS_RTSP_TOKEN (token), FALSE);

  priv = auth->priv;
  if (!g_file_get_contents (path, &contents, NULL, &error)) {
    GST_ERROR_OBJECT (auth, "Could not parse htdigest: %s", error->message);
    goto done;
  }

  for (line = contents; line && *line; line = eol ? eol + 1 : NULL) {
    GstRTSPDigestEntry *entry;
    gchar **strv;
    eol = strchr (line, '\n');

    if (eol)
      *eol = '\0';

    strv = g_strsplit (line, ":", -1);

    if (!(strv[0] && strv[1] && strv[2] && !strv[3])) {
      GST_ERROR_OBJECT (auth, "Invalid htdigest format");
      g_strfreev (strv);
      goto done;
    }

    if (strlen (strv[2]) != 32) {
      GST_ERROR_OBJECT (auth,
          "Invalid htdigest format, hash is expected to be 32 characters long");
      g_strfreev (strv);
      goto done;
    }

    entry = g_new0 (GstRTSPDigestEntry, 1);
    entry->token = gst_rtsp_token_ref (token);
    entry->md5_pass = g_strdup (strv[2]);
    g_hash_table_replace (new_entries, g_strdup (strv[0]), entry);
    g_strfreev (strv);
  }

  ret = TRUE;

  /* We only update digest if the file was entirely valid */
  g_mutex_lock (&priv->lock);
  g_hash_table_foreach_steal (new_entries, (GHRFunc) update_digest_cb,
      priv->digest);
  g_mutex_unlock (&priv->lock);

done:
  if (error)
    g_clear_error (&error);
  g_free (contents);
  g_hash_table_unref (new_entries);
  return ret;
}

/**
 * gst_rtsp_auth_remove_digest:
 * @auth: a #GstRTSPAuth
 * @user: (transfer none): the digest user name
 *
 * Removes a digest user.
 *
 * Since: 1.12
 */
void
gst_rtsp_auth_remove_digest (GstRTSPAuth * auth, const gchar * user)
{
  GstRTSPAuthPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));
  g_return_if_fail (user != NULL);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  g_hash_table_remove (priv->digest, user);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_auth_set_supported_methods:
 * @auth: a #GstRTSPAuth
 * @methods: supported methods
 *
 * Sets the supported authentication @methods for @auth.
 *
 * Since: 1.12
 */
void
gst_rtsp_auth_set_supported_methods (GstRTSPAuth * auth,
    GstRTSPAuthMethod methods)
{
  GstRTSPAuthPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_AUTH (auth));

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  priv->auth_methods = methods;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_auth_get_supported_methods:
 * @auth: a #GstRTSPAuth
 *
 * Gets the supported authentication methods of @auth.
 *
 * Returns: The supported authentication methods
 *
 * Since: 1.12
 */
GstRTSPAuthMethod
gst_rtsp_auth_get_supported_methods (GstRTSPAuth * auth)
{
  GstRTSPAuthPrivate *priv;
  GstRTSPAuthMethod methods;

  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), 0);

  priv = auth->priv;

  g_mutex_lock (&priv->lock);
  methods = priv->auth_methods;
  g_mutex_unlock (&priv->lock);

  return methods;
}

typedef struct
{
  GstRTSPAuth *auth;
  GstRTSPDigestNonce *nonce;
} RemoveNonceData;

static void
remove_nonce (gpointer data, GObject * object)
{
  RemoveNonceData *remove_nonce_data = data;

  g_mutex_lock (&remove_nonce_data->auth->priv->lock);
  g_hash_table_remove (remove_nonce_data->auth->priv->nonces,
      remove_nonce_data->nonce->nonce);
  g_mutex_unlock (&remove_nonce_data->auth->priv->lock);

  g_object_unref (remove_nonce_data->auth);
  g_free (remove_nonce_data);
}

static gboolean
default_digest_auth (GstRTSPAuth * auth, GstRTSPContext * ctx,
    GstRTSPAuthParam ** param)
{
  const gchar *realm = NULL, *user = NULL, *nonce = NULL;
  const gchar *response = NULL, *uri = NULL;
  GstRTSPDigestNonce *nonce_entry = NULL;
  GstRTSPDigestEntry *digest_entry;
  gchar *expected_response = NULL;
  gboolean ret = FALSE;

  GST_DEBUG_OBJECT (auth, "check Digest auth");

  if (!param)
    return ret;

  while (*param) {
    if (!realm && strcmp ((*param)->name, "realm") == 0 && (*param)->value)
      realm = (*param)->value;
    else if (!user && strcmp ((*param)->name, "username") == 0
        && (*param)->value)
      user = (*param)->value;
    else if (!nonce && strcmp ((*param)->name, "nonce") == 0 && (*param)->value)
      nonce = (*param)->value;
    else if (!response && strcmp ((*param)->name, "response") == 0
        && (*param)->value)
      response = (*param)->value;
    else if (!uri && strcmp ((*param)->name, "uri") == 0 && (*param)->value)
      uri = (*param)->value;

    param++;
  }

  if (!realm || !user || !nonce || !response || !uri)
    return FALSE;

  g_mutex_lock (&auth->priv->lock);
  digest_entry = g_hash_table_lookup (auth->priv->digest, user);
  if (!digest_entry)
    goto out;
  nonce_entry = g_hash_table_lookup (auth->priv->nonces, nonce);
  if (!nonce_entry)
    goto out;

  if (strcmp (nonce_entry->ip, gst_rtsp_connection_get_ip (ctx->conn)) != 0)
    goto out;
  if (nonce_entry->client && nonce_entry->client != ctx->client)
    goto out;

  if (digest_entry->md5_pass) {
    expected_response = gst_rtsp_generate_digest_auth_response_from_md5 (NULL,
        gst_rtsp_method_as_text (ctx->method), digest_entry->md5_pass,
        uri, nonce);
  } else {
    expected_response =
        gst_rtsp_generate_digest_auth_response (NULL,
        gst_rtsp_method_as_text (ctx->method), realm, user,
        digest_entry->pass, uri, nonce);
  }

  if (!expected_response || strcmp (response, expected_response) != 0)
    goto out;

  ctx->token = digest_entry->token;
  ret = TRUE;

out:
  if (nonce_entry && !nonce_entry->client) {
    RemoveNonceData *remove_nonce_data = g_new (RemoveNonceData, 1);

    nonce_entry->client = ctx->client;
    remove_nonce_data->nonce = nonce_entry;
    remove_nonce_data->auth = g_object_ref (auth);
    g_object_weak_ref (G_OBJECT (ctx->client), remove_nonce, remove_nonce_data);
  }
  g_mutex_unlock (&auth->priv->lock);

  g_free (expected_response);

  return ret;
}

static gboolean
default_authenticate (GstRTSPAuth * auth, GstRTSPContext * ctx)
{
  GstRTSPAuthPrivate *priv = auth->priv;
  GstRTSPAuthCredential **credentials, **credential;

  GST_DEBUG_OBJECT (auth, "authenticate");

  g_mutex_lock (&priv->lock);
  /* FIXME, need to ref but we have no way to unref when the ctx is
   * popped */
  ctx->token = priv->default_token;
  g_mutex_unlock (&priv->lock);

  credentials =
      gst_rtsp_message_parse_auth_credentials (ctx->request,
      GST_RTSP_HDR_AUTHORIZATION);
  if (!credentials)
    goto no_auth;

  /* parse type */
  credential = credentials;
  while (*credential) {
    if ((*credential)->scheme == GST_RTSP_AUTH_BASIC) {
      GstRTSPToken *token;

      GST_DEBUG_OBJECT (auth, "check Basic auth");
      g_mutex_lock (&priv->lock);
      if ((*credential)->authorization && (token =
              g_hash_table_lookup (priv->basic,
                  (*credential)->authorization))) {
        GST_DEBUG_OBJECT (auth, "setting token %p", token);
        ctx->token = token;
        g_mutex_unlock (&priv->lock);
        break;
      }
      g_mutex_unlock (&priv->lock);
    } else if ((*credential)->scheme == GST_RTSP_AUTH_DIGEST) {
      if (default_digest_auth (auth, ctx, (*credential)->params))
        break;
    }

    credential++;
  }

  gst_rtsp_auth_credentials_free (credentials);
  return TRUE;

no_auth:
  {
    GST_DEBUG_OBJECT (auth, "no authorization header found");
    return TRUE;
  }
}

static void
default_generate_authenticate_header (GstRTSPAuth * auth, GstRTSPContext * ctx)
{
  if (auth->priv->auth_methods & GST_RTSP_AUTH_BASIC) {
    gchar *auth_header =
        g_strdup_printf ("Basic realm=\"%s\"", auth->priv->realm);
    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
        auth_header);
    g_free (auth_header);
  }

  if (auth->priv->auth_methods & GST_RTSP_AUTH_DIGEST) {
    GstRTSPDigestNonce *nonce;
    gchar *nonce_value, *auth_header;

    nonce_value =
        g_strdup_printf ("%08x%08x", g_random_int (), g_random_int ());

    auth_header =
        g_strdup_printf
        ("Digest realm=\"%s\", nonce=\"%s\"", auth->priv->realm, nonce_value);
    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
        auth_header);
    g_free (auth_header);

    nonce = g_new0 (GstRTSPDigestNonce, 1);
    nonce->nonce = g_strdup (nonce_value);
    nonce->timestamp = g_get_monotonic_time ();
    nonce->ip = g_strdup (gst_rtsp_connection_get_ip (ctx->conn));
    g_mutex_lock (&auth->priv->lock);
    g_hash_table_replace (auth->priv->nonces, nonce_value, nonce);

    if (auth->priv->last_nonce_check == 0)
      auth->priv->last_nonce_check = nonce->timestamp;

    /* 30 second nonce timeout */
    if (nonce->timestamp - auth->priv->last_nonce_check >= 30 * G_USEC_PER_SEC) {
      GHashTableIter iter;
      gpointer key, value;

      g_hash_table_iter_init (&iter, auth->priv->nonces);
      while (g_hash_table_iter_next (&iter, &key, &value)) {
        GstRTSPDigestNonce *tmp = value;

        if (!tmp->client
            && nonce->timestamp - tmp->timestamp >= 30 * G_USEC_PER_SEC)
          g_hash_table_iter_remove (&iter);
      }
      auth->priv->last_nonce_check = nonce->timestamp;
    }

    g_mutex_unlock (&auth->priv->lock);
  }
}

static void
send_response (GstRTSPAuth * auth, GstRTSPStatusCode code, GstRTSPContext * ctx)
{
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  if (code == GST_RTSP_STS_UNAUTHORIZED) {
    GstRTSPAuthClass *klass;

    klass = GST_RTSP_AUTH_GET_CLASS (auth);

    if (klass->generate_authenticate_header)
      klass->generate_authenticate_header (auth, ctx);
  }
  gst_rtsp_client_send_message (ctx->client, ctx->session, ctx->response);
}

static gboolean
ensure_authenticated (GstRTSPAuth * auth, GstRTSPContext * ctx)
{
  GstRTSPAuthClass *klass;

  klass = GST_RTSP_AUTH_GET_CLASS (auth);

  /* we need a token to check */
  if (ctx->token == NULL) {
    if (klass->authenticate) {
      if (!klass->authenticate (auth, ctx))
        goto authenticate_failed;
    }
  }
  if (ctx->token == NULL)
    goto no_auth;

  return TRUE;

/* ERRORS */
authenticate_failed:
  {
    GST_DEBUG_OBJECT (auth, "authenticate failed");
    send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
    return FALSE;
  }
no_auth:
  {
    GST_DEBUG_OBJECT (auth, "no authorization token found");
    send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
    return FALSE;
  }
}

static gboolean
accept_certificate_cb (GTlsConnection * conn, GTlsCertificate * peer_cert,
    GTlsCertificateFlags errors, GstRTSPAuth * auth)
{
  gboolean ret = FALSE;

  g_signal_emit (auth, signals[SIGNAL_ACCEPT_CERTIFICATE], 0,
      conn, peer_cert, errors, &ret);

  return ret;
}

/* new connection */
static gboolean
check_connect (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
  GstRTSPAuthPrivate *priv = auth->priv;
  GTlsConnection *tls;

  /* configure the connection */

  if (priv->certificate) {
    tls = gst_rtsp_connection_get_tls (ctx->conn, NULL);
    g_tls_connection_set_certificate (tls, priv->certificate);
  }

  if (priv->mode != G_TLS_AUTHENTICATION_NONE) {
    tls = gst_rtsp_connection_get_tls (ctx->conn, NULL);
    g_tls_connection_set_database (tls, priv->database);
    g_object_set (tls, "authentication-mode", priv->mode, NULL);
    g_signal_connect (tls, "accept-certificate",
        G_CALLBACK (accept_certificate_cb), auth);
  }

  return TRUE;
}

/* check url and methods */
static gboolean
check_url (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
  GstRTSPAuthPrivate *priv = auth->priv;

  if ((ctx->method & priv->methods) != 0)
    if (!ensure_authenticated (auth, ctx))
      goto not_authenticated;

  return TRUE;

  /* ERRORS */
not_authenticated:
  {
    return FALSE;
  }
}

/* check access to media factory */
static gboolean
check_factory (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
  const gchar *role;
  GstRTSPPermissions *perms;

  if (!ensure_authenticated (auth, ctx))
    return FALSE;

  if (!(role = gst_rtsp_token_get_string (ctx->token,
              GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE)))
    goto no_media_role;
  if (!(perms = gst_rtsp_media_factory_get_permissions (ctx->factory)))
    goto no_permissions;

  if (g_str_equal (check, GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS)) {
    if (!gst_rtsp_permissions_is_allowed (perms, role,
            GST_RTSP_PERM_MEDIA_FACTORY_ACCESS))
      goto no_access;
  } else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT)) {
    if (!gst_rtsp_permissions_is_allowed (perms, role,
            GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT))
      goto no_construct;
  }

  gst_rtsp_permissions_unref (perms);

  return TRUE;

  /* ERRORS */
no_media_role:
  {
    GST_DEBUG_OBJECT (auth, "no media factory role found");
    send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
    return FALSE;
  }
no_permissions:
  {
    GST_DEBUG_OBJECT (auth, "no permissions on media factory found");
    send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
    return FALSE;
  }
no_access:
  {
    GST_DEBUG_OBJECT (auth, "no permissions to access media factory");
    gst_rtsp_permissions_unref (perms);
    send_response (auth, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_construct:
  {
    GST_DEBUG_OBJECT (auth, "no permissions to construct media factory");
    gst_rtsp_permissions_unref (perms);
    send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
    return FALSE;
  }
}

static gboolean
check_client_settings (GstRTSPAuth * auth, GstRTSPContext * ctx,
    const gchar * check)
{
  if (!ensure_authenticated (auth, ctx))
    return FALSE;

  return gst_rtsp_token_is_allowed (ctx->token,
      GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS);
}

static gboolean
default_check (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
  gboolean res = FALSE;

  /* FIXME, use hastable or so */
  if (g_str_equal (check, GST_RTSP_AUTH_CHECK_CONNECT)) {
    res = check_connect (auth, ctx, check);
  } else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_URL)) {
    res = check_url (auth, ctx, check);
  } else if (g_str_has_prefix (check, "auth.check.media.factory.")) {
    res = check_factory (auth, ctx, check);
  } else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS)) {
    res = check_client_settings (auth, ctx, check);
  }
  return res;
}

static gboolean
no_auth_check (const gchar * check)
{
  gboolean res;

  if (g_str_equal (check, GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS))
    res = FALSE;
  else
    res = TRUE;

  return res;
}

/**
 * gst_rtsp_auth_check:
 * @check: the item to check
 *
 * Check if @check is allowed in the current context.
 *
 * Returns: FALSE if check failed.
 */
gboolean
gst_rtsp_auth_check (const gchar * check)
{
  gboolean result = FALSE;
  GstRTSPAuthClass *klass;
  GstRTSPContext *ctx;
  GstRTSPAuth *auth;

  g_return_val_if_fail (check != NULL, FALSE);

  if (!(ctx = gst_rtsp_context_get_current ()))
    goto no_context;

  /* no auth, we don't need to check */
  if (!(auth = ctx->auth))
    return no_auth_check (check);

  klass = GST_RTSP_AUTH_GET_CLASS (auth);

  GST_DEBUG_OBJECT (auth, "check authorization '%s'", check);

  if (klass->check)
    result = klass->check (auth, ctx, check);

  return result;

  /* ERRORS */
no_context:
  {
    GST_ERROR ("no context found");
    return FALSE;
  }
}

/**
 * gst_rtsp_auth_make_basic:
 * @user: a userid
 * @pass: a password
 *
 * Construct a Basic authorisation token from @user and @pass.
 *
 * Returns: (transfer full): the base64 encoding of the string @user:@pass.
 * g_free() after usage.
 */
gchar *
gst_rtsp_auth_make_basic (const gchar * user, const gchar * pass)
{
  gchar *user_pass;
  gchar *result;

  g_return_val_if_fail (user != NULL, NULL);
  g_return_val_if_fail (pass != NULL, NULL);

  user_pass = g_strjoin (":", user, pass, NULL);
  result = g_base64_encode ((guchar *) user_pass, strlen (user_pass));
  g_free (user_pass);

  return result;
}

/**
 * gst_rtsp_auth_set_realm:
 * @realm: (nullable): The realm to set
 *
 * Set the @realm of @auth
 *
 * Since: 1.16
 */
void
gst_rtsp_auth_set_realm (GstRTSPAuth * auth, const gchar * realm)
{
  g_return_if_fail (GST_IS_RTSP_AUTH (auth));
  g_return_if_fail (realm != NULL);

  if (auth->priv->realm)
    g_free (auth->priv->realm);

  auth->priv->realm = g_strdup (realm);
}

/**
 * gst_rtsp_auth_get_realm:
 *
 * Returns: (transfer full) (nullable): the @realm of @auth
 *
 * Since: 1.16
 */
gchar *
gst_rtsp_auth_get_realm (GstRTSPAuth * auth)
{
  g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);

  return g_strdup (auth->priv->realm);
}
  07070100000037000081A400000000000000000000000169D554BF000020C7000000000000000000000000000000000000003300000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-auth.h    /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#ifndef __GST_RTSP_AUTH_H__
#define __GST_RTSP_AUTH_H__

typedef struct _GstRTSPAuth GstRTSPAuth;
typedef struct _GstRTSPAuthClass GstRTSPAuthClass;
typedef struct _GstRTSPAuthPrivate GstRTSPAuthPrivate;

#include "rtsp-server-prelude.h"
#include "rtsp-client.h"
#include "rtsp-token.h"

G_BEGIN_DECLS

#define GST_TYPE_RTSP_AUTH              (gst_rtsp_auth_get_type ())
#define GST_IS_RTSP_AUTH(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_AUTH))
#define GST_IS_RTSP_AUTH_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_AUTH))
#define GST_RTSP_AUTH_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_AUTH, GstRTSPAuthClass))
#define GST_RTSP_AUTH(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_AUTH, GstRTSPAuth))
#define GST_RTSP_AUTH_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_AUTH, GstRTSPAuthClass))
#define GST_RTSP_AUTH_CAST(obj)         ((GstRTSPAuth*)(obj))
#define GST_RTSP_AUTH_CLASS_CAST(klass) ((GstRTSPAuthClass*)(klass))

/**
 * GstRTSPAuth:
 *
 * The authentication structure.
 */
struct _GstRTSPAuth {
  GObject       parent;

  /*< private >*/
  GstRTSPAuthPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPAuthClass:
 * @authenticate: check the authentication of a client. The default implementation
 *         checks if the authentication in the header matches one of the basic
 *         authentication tokens. This function should set the authgroup field
 *         in the context.
 * @check: check if a resource can be accessed. this function should
 *         call authenticate to authenticate the client when needed. The method
 *         should also construct and send an appropriate response message on
 *         error.
 *
 * The authentication class.
 */
struct _GstRTSPAuthClass {
  GObjectClass  parent_class;

  gboolean           (*authenticate) (GstRTSPAuth *auth, GstRTSPContext *ctx);
  gboolean           (*check)        (GstRTSPAuth *auth, GstRTSPContext *ctx,
                                      const gchar *check);
  void               (*generate_authenticate_header) (GstRTSPAuth *auth, GstRTSPContext *ctx);
  gboolean           (*accept_certificate) (GstRTSPAuth *auth,
                                            GTlsConnection *connection,
                                            GTlsCertificate *peer_cert,
                                            GTlsCertificateFlags errors);
  /*< private >*/
  gpointer            _gst_reserved[GST_PADDING - 1];
};

GST_RTSP_SERVER_API
GType               gst_rtsp_auth_get_type          (void);

GST_RTSP_SERVER_API
GstRTSPAuth *       gst_rtsp_auth_new               (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_tls_certificate (GstRTSPAuth *auth, GTlsCertificate *cert);

GST_RTSP_SERVER_API
GTlsCertificate *   gst_rtsp_auth_get_tls_certificate (GstRTSPAuth *auth) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_tls_database (GstRTSPAuth *auth, GTlsDatabase *database);

GST_RTSP_SERVER_API
GTlsDatabase *      gst_rtsp_auth_get_tls_database (GstRTSPAuth *auth) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_tls_authentication_mode (GstRTSPAuth *auth, GTlsAuthenticationMode mode);

GST_RTSP_SERVER_API
GTlsAuthenticationMode gst_rtsp_auth_get_tls_authentication_mode (GstRTSPAuth *auth);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_default_token (GstRTSPAuth *auth, GstRTSPToken *token);

GST_RTSP_SERVER_API
GstRTSPToken *      gst_rtsp_auth_get_default_token (GstRTSPAuth *auth) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_auth_add_basic         (GstRTSPAuth *auth, const gchar * basic,
                                                     GstRTSPToken *token);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_remove_basic      (GstRTSPAuth *auth, const gchar * basic);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_add_digest        (GstRTSPAuth *auth, const gchar *user,
                                                     const gchar *pass, GstRTSPToken *token);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_remove_digest     (GstRTSPAuth *auth, const gchar *user);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_supported_methods (GstRTSPAuth *auth, GstRTSPAuthMethod methods);

GST_RTSP_SERVER_API
GstRTSPAuthMethod   gst_rtsp_auth_get_supported_methods (GstRTSPAuth *auth);

GST_RTSP_SERVER_API
gboolean            gst_rtsp_auth_check             (const gchar *check);

GST_RTSP_SERVER_API
gboolean            gst_rtsp_auth_parse_htdigest    (GstRTSPAuth *auth, const gchar *path, GstRTSPToken *token);

GST_RTSP_SERVER_API
void                gst_rtsp_auth_set_realm         (GstRTSPAuth *auth, const gchar *realm);

GST_RTSP_SERVER_API
gchar *             gst_rtsp_auth_get_realm         (GstRTSPAuth *auth) G_GNUC_WARN_UNUSED_RESULT;

/* helpers */

GST_RTSP_SERVER_API
gchar *             gst_rtsp_auth_make_basic        (const gchar * user, const gchar * pass);

/* checks */
/**
 * GST_RTSP_AUTH_CHECK_CONNECT:
 *
 * Check a new connection
 */
#define GST_RTSP_AUTH_CHECK_CONNECT                  "auth.check.connect"
/**
 * GST_RTSP_AUTH_CHECK_URL:
 *
 * Check the URL and methods
 */
#define GST_RTSP_AUTH_CHECK_URL                      "auth.check.url"
/**
 * GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS:
 *
 * Check if access is allowed to a factory.
 * When access is not allowed an 404 Not Found is sent in the response.
 */
#define GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS     "auth.check.media.factory.access"
/**
 * GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT:
 *
 * Check if media can be constructed from a media factory
 * A response should be sent on error.
 */
#define GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT  "auth.check.media.factory.construct"
/**
 * GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS:
 *
 * Check if the client can specify TTL, destination and
 * port pair in multicast. No response is sent when the check returns
 * %FALSE.
 */
#define GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS  "auth.check.transport.client-settings"


/* tokens */
/**
 * GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE:
 *
 * G_TYPE_STRING, the role to use when dealing with media factories
 *
 * The default #GstRTSPAuth object uses this string in the token to find the
 * role of the media factory. It will then retrieve the #GstRTSPPermissions of
 * the media factory and retrieve the role with the same name.
 */
#define GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE      "media.factory.role"
/**
 * GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS:
 *
 * G_TYPE_BOOLEAN, %TRUE if the client can specify TTL, destination and
 *     port pair in multicast.
 */
#define GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS   "transport.client-settings"

/* permissions */
/**
 * GST_RTSP_PERM_MEDIA_FACTORY_ACCESS:
 *
 * G_TYPE_BOOLEAN, %TRUE if the media can be accessed, %FALSE will
 * return a 404 Not Found error when trying to access the media.
 */
#define GST_RTSP_PERM_MEDIA_FACTORY_ACCESS      "media.factory.access"
/**
 * GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT:
 *
 * G_TYPE_BOOLEAN, %TRUE if the media can be constructed, %FALSE will
 * return a 404 Not Found error when trying to access the media.
 */
#define GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT   "media.factory.construct"

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPAuth, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_AUTH_H__ */
 07070100000038000081A400000000000000000000000169D554BF0002788B000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-client.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-client
 * @short_description: A client connection state
 * @see_also: #GstRTSPServer, #GstRTSPThreadPool
 *
 * The client object handles the connection with a client for as long as a TCP
 * connection is open.
 *
 * A #GstRTSPClient is created by #GstRTSPServer when a new connection is
 * accepted and it inherits the #GstRTSPMountPoints, #GstRTSPSessionPool,
 * #GstRTSPAuth and #GstRTSPThreadPool from the server.
 *
 * The client connection should be configured with the #GstRTSPConnection using
 * gst_rtsp_client_set_connection() before it can be attached to a #GMainContext
 * using gst_rtsp_client_attach(). From then on the client will handle requests
 * on the connection.
 *
 * Use gst_rtsp_client_session_filter() to iterate or modify all the
 * #GstRTSPSession objects managed by the client object.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>

#include <gst/sdp/gstmikey.h>
#include <gst/rtsp/gstrtsp-enumtypes.h>

#include "rtsp-client.h"
#include "rtsp-sdp.h"
#include "rtsp-params.h"
#include "rtsp-server-internal.h"

typedef enum
{
  TUNNEL_STATE_UNKNOWN,
  TUNNEL_STATE_GET,
  TUNNEL_STATE_POST
} GstRTSPTunnelState;

/* locking order:
 * send_lock, lock, tunnels_lock
 */

struct _GstRTSPClientPrivate
{
  GMutex lock;                  /* protects everything else */
  GMutex send_lock;
  GMutex watch_lock;
  GstRTSPConnection *connection;
  GstRTSPWatch *watch;
  GMainContext *watch_context;
  gchar *server_ip;
  gboolean is_ipv6;

  /* protected by send_lock */
  GstRTSPClientSendFunc send_func;
  gpointer send_data;
  GDestroyNotify send_notify;
  GstRTSPClientSendMessagesFunc send_messages_func;
  gpointer send_messages_data;
  GDestroyNotify send_messages_notify;
  GArray *data_seqs;

  GstRTSPSessionPool *session_pool;
  gulong session_removed_id;
  GstRTSPMountPoints *mount_points;
  GstRTSPAuth *auth;
  GstRTSPThreadPool *thread_pool;

  /* used to cache the media in the last requested DESCRIBE so that
   * we can pick it up in the next SETUP immediately */
  gchar *path;
  GstRTSPMedia *media;

  GHashTable *transports;
  GList *sessions;
  guint sessions_cookie;

  gboolean drop_backlog;
  gint post_session_timeout;

  guint content_length_limit;

  gboolean had_session;
  GSource *rtsp_ctrl_timeout;
  guint rtsp_ctrl_timeout_cnt;

  /* The version currently being used */
  GstRTSPVersion version;

  GHashTable *pipelined_requests;       /* pipelined_request_id -> session_id */
  GstRTSPTunnelState tstate;
};

typedef struct
{
  guint8 channel;
  guint seq;
} DataSeq;

static GMutex tunnels_lock;
static GHashTable *tunnels;     /* protected by tunnels_lock */

#define WATCH_BACKLOG_SIZE              100

#define DEFAULT_SESSION_POOL            NULL
#define DEFAULT_MOUNT_POINTS            NULL
#define DEFAULT_DROP_BACKLOG            TRUE
#define DEFAULT_POST_SESSION_TIMEOUT    -1

#define RTSP_CTRL_CB_INTERVAL           1
#define RTSP_CTRL_TIMEOUT_VALUE         60

enum
{
  PROP_0,
  PROP_SESSION_POOL,
  PROP_MOUNT_POINTS,
  PROP_DROP_BACKLOG,
  PROP_POST_SESSION_TIMEOUT,
  PROP_LAST
};

enum
{
  SIGNAL_CLOSED,
  SIGNAL_NEW_SESSION,
  SIGNAL_PRE_OPTIONS_REQUEST,
  SIGNAL_OPTIONS_REQUEST,
  SIGNAL_PRE_DESCRIBE_REQUEST,
  SIGNAL_DESCRIBE_REQUEST,
  SIGNAL_PRE_SETUP_REQUEST,
  SIGNAL_SETUP_REQUEST,
  SIGNAL_PRE_PLAY_REQUEST,
  SIGNAL_PLAY_REQUEST,
  SIGNAL_PRE_PAUSE_REQUEST,
  SIGNAL_PAUSE_REQUEST,
  SIGNAL_PRE_TEARDOWN_REQUEST,
  SIGNAL_TEARDOWN_REQUEST,
  SIGNAL_PRE_SET_PARAMETER_REQUEST,
  SIGNAL_SET_PARAMETER_REQUEST,
  SIGNAL_PRE_GET_PARAMETER_REQUEST,
  SIGNAL_GET_PARAMETER_REQUEST,
  SIGNAL_HANDLE_RESPONSE,
  SIGNAL_SEND_MESSAGE,
  SIGNAL_PRE_ANNOUNCE_REQUEST,
  SIGNAL_ANNOUNCE_REQUEST,
  SIGNAL_PRE_RECORD_REQUEST,
  SIGNAL_RECORD_REQUEST,
  SIGNAL_CHECK_REQUIREMENTS,
  SIGNAL_PRE_CLOSED,
  SIGNAL_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_client_debug);
#define GST_CAT_DEFAULT rtsp_client_debug

static guint gst_rtsp_client_signals[SIGNAL_LAST] = { 0 };

static void gst_rtsp_client_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_client_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_client_finalize (GObject * obj);

static void rtsp_ctrl_timeout_remove (GstRTSPClient * client);

static GstSDPMessage *create_sdp (GstRTSPClient * client, GstRTSPMedia * media);
static gboolean handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPMedia * media, GstSDPMessage * sdp);
static gboolean default_configure_client_media (GstRTSPClient * client,
    GstRTSPMedia * media, GstRTSPStream * stream, GstRTSPContext * ctx);
static gboolean default_configure_client_transport (GstRTSPClient * client,
    GstRTSPContext * ctx, GstRTSPTransport * ct);
static GstRTSPResult default_params_set (GstRTSPClient * client,
    GstRTSPContext * ctx);
static GstRTSPResult default_params_get (GstRTSPClient * client,
    GstRTSPContext * ctx);
static gchar *default_make_path_from_uri (GstRTSPClient * client,
    const GstRTSPUrl * uri);
static void client_session_removed (GstRTSPSessionPool * pool,
    GstRTSPSession * session, GstRTSPClient * client);
static GstRTSPStatusCode default_pre_signal_handler (GstRTSPClient * client,
    GstRTSPContext * ctx);
static gboolean pre_signal_accumulator (GSignalInvocationHint * ihint,
    GValue * return_accu, const GValue * handler_return, gpointer data);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT);

static void
gst_rtsp_client_class_init (GstRTSPClientClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_client_get_property;
  gobject_class->set_property = gst_rtsp_client_set_property;
  gobject_class->finalize = gst_rtsp_client_finalize;

  klass->create_sdp = create_sdp;
  klass->handle_sdp = handle_sdp;
  klass->configure_client_media = default_configure_client_media;
  klass->configure_client_transport = default_configure_client_transport;
  klass->params_set = default_params_set;
  klass->params_get = default_params_get;
  klass->make_path_from_uri = default_make_path_from_uri;

  klass->pre_options_request = default_pre_signal_handler;
  klass->pre_describe_request = default_pre_signal_handler;
  klass->pre_setup_request = default_pre_signal_handler;
  klass->pre_play_request = default_pre_signal_handler;
  klass->pre_pause_request = default_pre_signal_handler;
  klass->pre_teardown_request = default_pre_signal_handler;
  klass->pre_set_parameter_request = default_pre_signal_handler;
  klass->pre_get_parameter_request = default_pre_signal_handler;
  klass->pre_announce_request = default_pre_signal_handler;
  klass->pre_record_request = default_pre_signal_handler;

  g_object_class_install_property (gobject_class, PROP_SESSION_POOL,
      g_param_spec_object ("session-pool", "Session Pool",
          "The session pool to use for client session",
          GST_TYPE_RTSP_SESSION_POOL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_MOUNT_POINTS,
      g_param_spec_object ("mount-points", "Mount Points",
          "The mount points to use for client session",
          GST_TYPE_RTSP_MOUNT_POINTS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_DROP_BACKLOG,
      g_param_spec_boolean ("drop-backlog", "Drop Backlog",
          "Drop data when the backlog queue is full",
          DEFAULT_DROP_BACKLOG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClient::post-session-timeout:
   *
   * An extra tcp timeout ( > 0) after session timeout, in seconds.
   * The tcp connection will be kept alive until this timeout happens to give
   * the client a possibility to reuse the connection.
   * 0 means that the connection will be closed immediately after the session
   * timeout.
   *
   * Default value is -1 seconds, meaning that we let the system close
   * the connection.
   *
   * Since: 1.18
   */
  g_object_class_install_property (gobject_class, PROP_POST_SESSION_TIMEOUT,
      g_param_spec_int ("post-session-timeout", "Post Session Timeout",
          "An extra TCP connection timeout after session timeout", G_MININT,
          G_MAXINT, DEFAULT_POST_SESSION_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClient::pre-closed:
   *
   * Provide a way for an application to be notified when a connection is closed,
   * before the client's sessions are cleaned up.
   *
   * Since: 1.28
   */
  gst_rtsp_client_signals[SIGNAL_PRE_CLOSED] =
      g_signal_new_class_handler ("pre-closed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 0);

  gst_rtsp_client_signals[SIGNAL_CLOSED] =
      g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPClientClass, closed), NULL, NULL, NULL,
      G_TYPE_NONE, 0, G_TYPE_NONE);

  gst_rtsp_client_signals[SIGNAL_NEW_SESSION] =
      g_signal_new ("new-session", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPClientClass, new_session), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_SESSION);

  /**
   * GstRTSPClient::pre-options-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST] =
      g_signal_new ("pre-options-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_options_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::options-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST] =
      g_signal_new ("options-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, options_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-describe-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST] =
      g_signal_new ("pre-describe-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_describe_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::describe-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST] =
      g_signal_new ("describe-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, describe_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-setup-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST] =
      g_signal_new ("pre-setup-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_setup_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::setup-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST] =
      g_signal_new ("setup-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, setup_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-play-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST] =
      g_signal_new ("pre-play-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_play_request), pre_signal_accumulator, NULL,
      NULL, GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::play-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST] =
      g_signal_new ("play-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, play_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-pause-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST] =
      g_signal_new ("pre-pause-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_pause_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pause-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST] =
      g_signal_new ("pause-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, pause_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-teardown-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST] =
      g_signal_new ("pre-teardown-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_teardown_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::teardown-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST] =
      g_signal_new ("teardown-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, teardown_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-set-parameter-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST] =
      g_signal_new ("pre-set-parameter-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_set_parameter_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::set-parameter-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST] =
      g_signal_new ("set-parameter-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          set_parameter_request), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-get-parameter-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST] =
      g_signal_new ("pre-get-parameter-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_get_parameter_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::get-parameter-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST] =
      g_signal_new ("get-parameter-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          get_parameter_request), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::handle-response:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE] =
      g_signal_new ("handle-response", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          handle_response), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::send-message:
   * @client: The RTSP client
   * @session: (type GstRtspServer.RTSPSession): The session
   * @message: (type GstRtsp.RTSPMessage): The message
   */
  gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE] =
      g_signal_new ("send-message", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          send_message), NULL, NULL, NULL,
      G_TYPE_NONE, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_POINTER);

  /**
   * GstRTSPClient::pre-announce-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST] =
      g_signal_new ("pre-announce-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_announce_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::announce-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST] =
      g_signal_new ("announce-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, announce_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::pre-record-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   *
   * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
   *          otherwise an appropriate return code
   *
   * Since: 1.12
   */
  gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST] =
      g_signal_new ("pre-record-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          pre_record_request), pre_signal_accumulator, NULL, NULL,
      GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::record-request:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   */
  gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST] =
      g_signal_new ("record-request", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, record_request),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);

  /**
   * GstRTSPClient::check-requirements:
   * @client: a #GstRTSPClient
   * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
   * @arr: a NULL-terminated array of strings
   *
   * Returns: a newly allocated string with comma-separated list of
   *          unsupported options. An empty string must be returned if
   *          all options are supported.
   *
   * Since: 1.6
   */
  gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS] =
      g_signal_new ("check-requirements", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
          check_requirements), NULL, NULL, NULL,
      G_TYPE_STRING, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_STRV);

  tunnels =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  g_mutex_init (&tunnels_lock);

  GST_DEBUG_CATEGORY_INIT (rtsp_client_debug, "rtspclient", 0, "GstRTSPClient");
}

static void
gst_rtsp_client_init (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = gst_rtsp_client_get_instance_private (client);

  client->priv = priv;

  g_mutex_init (&priv->lock);
  g_mutex_init (&priv->send_lock);
  g_mutex_init (&priv->watch_lock);
  priv->data_seqs = g_array_new (FALSE, FALSE, sizeof (DataSeq));
  priv->drop_backlog = DEFAULT_DROP_BACKLOG;
  priv->post_session_timeout = DEFAULT_POST_SESSION_TIMEOUT;
  priv->transports =
      g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
      g_object_unref);
  priv->pipelined_requests = g_hash_table_new_full (g_str_hash,
      g_str_equal, g_free, g_free);
  priv->tstate = TUNNEL_STATE_UNKNOWN;
  priv->content_length_limit = G_MAXUINT;
}

static GstRTSPFilterResult
filter_session_media (GstRTSPSession * sess, GstRTSPSessionMedia * sessmedia,
    gpointer user_data)
{
  gboolean *closed = user_data;
  GstRTSPMedia *media;
  guint i, n_streams;
  gboolean is_all_udp = TRUE;

  media = gst_rtsp_session_media_get_media (sessmedia);
  n_streams = gst_rtsp_media_n_streams (media);

  /* Lock the media to synchronize with concurrent request handlers (e.g.
   * handle_play_request) that hold the media lock while operating on session
   * resources and emitting signals. Without this, a session timeout could
   * tear down the media while a play request is still in progress. */
  gst_rtsp_media_lock (media);

  for (i = 0; i < n_streams; i++) {
    GstRTSPStreamTransport *transport =
        gst_rtsp_session_media_get_transport (sessmedia, i);
    const GstRTSPTransport *rtsp_transport;

    if (!transport)
      continue;

    rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
    if (rtsp_transport
        && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP
        && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP_MCAST) {
      is_all_udp = FALSE;
      break;
    }
  }

  if (!is_all_udp || gst_rtsp_media_is_stop_on_disconnect (media)) {
    gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
    gst_rtsp_media_unlock (media);
    return GST_RTSP_FILTER_REMOVE;
  } else {
    gst_rtsp_media_unlock (media);
    *closed = FALSE;
    return GST_RTSP_FILTER_KEEP;
  }
}

static void
client_watch_session (GstRTSPClient * client, GstRTSPSession * session)
{
  GstRTSPClientPrivate *priv = client->priv;

  g_mutex_lock (&priv->lock);
  /* check if we already know about this session */
  if (g_list_find (priv->sessions, session) == NULL) {
    GST_INFO ("watching session %p", session);

    priv->sessions = g_list_prepend (priv->sessions, g_object_ref (session));
    priv->sessions_cookie++;

    /* connect removed session handler, it will be disconnected when the last
     * session gets removed  */
    if (priv->session_removed_id == 0)
      priv->session_removed_id = g_signal_connect_data (priv->session_pool,
          "session-removed", G_CALLBACK (client_session_removed),
          g_object_ref (client), (GClosureNotify) g_object_unref, 0);
  }
  g_mutex_unlock (&priv->lock);

  return;
}

/* should be called with lock */
static void
client_unwatch_session (GstRTSPClient * client, GstRTSPSession * session,
    GList * link)
{
  GstRTSPClientPrivate *priv = client->priv;

  GST_INFO ("client %p: unwatch session %p", client, session);

  if (link == NULL) {
    link = g_list_find (priv->sessions, session);
    if (link == NULL)
      return;
  }

  priv->sessions = g_list_delete_link (priv->sessions, link);
  priv->sessions_cookie++;

  /* if this was the last session, disconnect the handler.
   * This will also drop the extra client ref */
  if (!priv->sessions) {
    g_signal_handler_disconnect (priv->session_pool, priv->session_removed_id);
    priv->session_removed_id = 0;
  }

  if (!priv->drop_backlog) {
    GList *sessions G_GNUC_UNUSED;
    /* unlink all media managed in this session */
    sessions = gst_rtsp_session_filter (session, filter_session_media, client);
    g_assert (sessions == NULL);
  }

  /* remove the session */
  g_object_unref (session);
}

static GstRTSPFilterResult
cleanup_session (GstRTSPClient * client, GstRTSPSession * sess,
    gpointer user_data)
{
  gboolean *closed = user_data;
  GstRTSPClientPrivate *priv = client->priv;

  if (priv->drop_backlog) {
    GList *sessions G_GNUC_UNUSED;
    /* unlink all media managed in this session. This needs to happen
     * without the client lock, so we really want to do it here. */
    sessions = gst_rtsp_session_filter (sess, filter_session_media, user_data);
    g_assert (sessions == NULL);
  }

  if (*closed)
    return GST_RTSP_FILTER_REMOVE;
  else
    return GST_RTSP_FILTER_KEEP;
}

static void
clean_cached_media (GstRTSPClient * client, gboolean unprepare)
{
  GstRTSPClientPrivate *priv = client->priv;

  if (priv->path) {
    g_free (priv->path);
    priv->path = NULL;
  }
  if (priv->media) {
    if (unprepare)
      gst_rtsp_media_unprepare (priv->media);
    g_object_unref (priv->media);
    priv->media = NULL;
  }
}

/* A client is finalized when the connection is broken */
static void
gst_rtsp_client_finalize (GObject * obj)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (obj);
  GstRTSPClientPrivate *priv = client->priv;

  GST_INFO ("finalize client %p", client);

  /* the watch and related state should be cleared before finalize
   * as the watch actually holds a strong reference to the client */
  g_assert (priv->watch == NULL);
  g_assert (priv->rtsp_ctrl_timeout == NULL);

  if (priv->watch_context) {
    g_main_context_unref (priv->watch_context);
    priv->watch_context = NULL;
  }

  gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
  gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);

  /* all sessions should have been removed by now. We keep a ref to
   * the client object for the session removed handler. The ref is
   * dropped when the last session is removed from the list. */
  g_assert (priv->sessions == NULL);
  g_assert (priv->session_removed_id == 0);

  g_array_unref (priv->data_seqs);
  g_hash_table_unref (priv->transports);
  g_hash_table_unref (priv->pipelined_requests);

  if (priv->connection)
    gst_rtsp_connection_free (priv->connection);
  if (priv->session_pool) {
    g_object_unref (priv->session_pool);
  }
  if (priv->mount_points)
    g_object_unref (priv->mount_points);
  if (priv->auth)
    g_object_unref (priv->auth);
  if (priv->thread_pool)
    g_object_unref (priv->thread_pool);

  clean_cached_media (client, TRUE);

  g_free (priv->server_ip);
  g_mutex_clear (&priv->lock);
  g_mutex_clear (&priv->send_lock);
  g_mutex_clear (&priv->watch_lock);

  G_OBJECT_CLASS (gst_rtsp_client_parent_class)->finalize (obj);
}

static void
gst_rtsp_client_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (object);
  GstRTSPClientPrivate *priv = client->priv;

  switch (propid) {
    case PROP_SESSION_POOL:
      g_value_take_object (value, gst_rtsp_client_get_session_pool (client));
      break;
    case PROP_MOUNT_POINTS:
      g_value_take_object (value, gst_rtsp_client_get_mount_points (client));
      break;
    case PROP_DROP_BACKLOG:
      g_value_set_boolean (value, priv->drop_backlog);
      break;
    case PROP_POST_SESSION_TIMEOUT:
      g_value_set_int (value, priv->post_session_timeout);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_client_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (object);
  GstRTSPClientPrivate *priv = client->priv;

  switch (propid) {
    case PROP_SESSION_POOL:
      gst_rtsp_client_set_session_pool (client, g_value_get_object (value));
      break;
    case PROP_MOUNT_POINTS:
      gst_rtsp_client_set_mount_points (client, g_value_get_object (value));
      break;
    case PROP_DROP_BACKLOG:
      g_mutex_lock (&priv->lock);
      priv->drop_backlog = g_value_get_boolean (value);
      g_mutex_unlock (&priv->lock);
      break;
    case PROP_POST_SESSION_TIMEOUT:
      g_mutex_lock (&priv->lock);
      priv->post_session_timeout = g_value_get_int (value);
      g_mutex_unlock (&priv->lock);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_client_new:
 *
 * Create a new #GstRTSPClient instance.
 *
 * Returns: (transfer full): a new #GstRTSPClient
 */
GstRTSPClient *
gst_rtsp_client_new (void)
{
  GstRTSPClient *result;

  result = g_object_new (GST_TYPE_RTSP_CLIENT, NULL);

  return result;
}

static void
send_message (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPMessage * message, gboolean close)
{
  GstRTSPClientPrivate *priv = client->priv;

  gst_rtsp_message_add_header (message, GST_RTSP_HDR_SERVER,
      "GStreamer RTSP server");

  /* remove any previous header */
  gst_rtsp_message_remove_header (message, GST_RTSP_HDR_SESSION, -1);

  /* add the new session header for new session ids */
  if (ctx->session) {
    gst_rtsp_message_take_header (message, GST_RTSP_HDR_SESSION,
        gst_rtsp_session_get_header (ctx->session));
  }

  if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
    gst_rtsp_message_dump (message);
  }

  if (close)
    gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close");

  if (ctx->request)
    message->type_data.response.version =
        ctx->request->type_data.request.version;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE],
      0, ctx, message);

  g_mutex_lock (&priv->send_lock);
  if (priv->send_messages_func) {
    priv->send_messages_func (client, message, 1, close, priv->send_data);
  } else if (priv->send_func) {
    priv->send_func (client, message, close, priv->send_data);
  }
  g_mutex_unlock (&priv->send_lock);

  gst_rtsp_message_unset (message);
}

static void
send_generic_response (GstRTSPClient * client, GstRTSPStatusCode code,
    GstRTSPContext * ctx)
{
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  ctx->session = NULL;

  send_message (client, ctx, ctx->response, FALSE);
}

static void
send_generic_error_response (GstRTSPClient * client, GstRTSPStatusCode code,
    GstRTSPContext * ctx)
{
  GstRTSPClientClass *klass = GST_RTSP_CLIENT_GET_CLASS (client);
  GstRTSPStatusCode adjusted_code = code;

  if (klass->adjust_error_code != NULL) {
    adjusted_code = klass->adjust_error_code (client, ctx, code);
    if (adjusted_code != code) {
      GST_DEBUG ("adjusted response error code from %d to %d", code,
          adjusted_code);
    }
  }
  send_generic_response (client, adjusted_code, ctx);
}

static void
send_option_not_supported_response (GstRTSPClient * client,
    GstRTSPContext * ctx, const gchar * unsupported_options)
{
  GstRTSPStatusCode code = GST_RTSP_STS_OPTION_NOT_SUPPORTED;

  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  if (unsupported_options != NULL) {
    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_UNSUPPORTED,
        unsupported_options);
  }

  ctx->session = NULL;

  send_message (client, ctx, ctx->response, FALSE);
}

static gboolean
paths_are_equal (const gchar * path1, const gchar * path2, gint len2)
{
  if (path1 == NULL || path2 == NULL)
    return FALSE;

  if (strlen (path1) != len2)
    return FALSE;

  if (strncmp (path1, path2, len2))
    return FALSE;

  return TRUE;
}

/* this function is called to initially find the media for the DESCRIBE request
 * but is cached for when the same client (without breaking the connection) is
 * doing a setup for the exact same url. */
static GstRTSPMedia *
find_media (GstRTSPClient * client, GstRTSPContext * ctx, gchar * path,
    gint * matched)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  gint path_len;

  /* find the longest matching factory for the uri first */
  if (!(factory = gst_rtsp_mount_points_match (priv->mount_points,
              path, matched)))
    goto no_factory;

  ctx->factory = factory;

  if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS))
    goto no_factory_access;

  if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT))
    goto not_authorized;

  if (matched)
    path_len = *matched;
  else
    path_len = strlen (path);

  url = gst_rtsp_url_copy (ctx->uri);
  /* normalize rtsp://<IP>:<PORT> to rtsp://<IP>:<PORT>/ */
  if (url->abspath[0] == 0) {
    g_free (url->abspath);
    url->abspath = g_strdup ("/");
  }

  if (!paths_are_equal (priv->path, path, path_len)) {
    /* remove any previously cached values before we try to construct a new
     * media for uri */
    clean_cached_media (client, TRUE);

    /* prepare the media and add it to the pipeline */
    if (!(media = gst_rtsp_media_factory_construct (factory, url)))
      goto no_media;

    ctx->media = media;

    if (!(gst_rtsp_media_get_transport_mode (media) &
            GST_RTSP_TRANSPORT_MODE_RECORD)) {
      GstRTSPThread *thread;

      thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
          GST_RTSP_THREAD_TYPE_MEDIA, ctx);
      if (thread == NULL)
        goto no_thread;

      /* prepare the media */
      if (!gst_rtsp_media_prepare (media, thread))
        goto no_prepare;
    }

    /* now keep track of the uri and the media */
    priv->path = g_strndup (path, path_len);
    priv->media = media;
  } else {
    /* we have seen this path before, used cached media */
    media = priv->media;
    gst_rtsp_media_lock (media);
    ctx->media = media;
    GST_INFO ("reusing cached media %p for path %s", media, priv->path);
  }

  gst_rtsp_url_free (url);
  g_object_unref (factory);
  ctx->factory = NULL;

  if (media)
    g_object_ref (media);

  return media;

  /* ERRORS */
no_factory:
  {
    GST_ERROR ("client %p: no factory for path %s", client, path);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return NULL;
  }
no_factory_access:
  {
    g_object_unref (factory);
    ctx->factory = NULL;
    GST_ERROR ("client %p: not authorized to see factory path %s", client,
        path);
    /* error reply is already sent */
    return NULL;
  }
not_authorized:
  {
    g_object_unref (factory);
    ctx->factory = NULL;
    GST_ERROR ("client %p: not authorized for factory path %s", client, path);
    /* error reply is already sent */
    return NULL;
  }
no_media:
  {
    GST_ERROR ("client %p: can't create media", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    gst_rtsp_url_free (url);
    g_object_unref (factory);
    ctx->factory = NULL;
    return NULL;
  }
no_thread:
  {
    GST_ERROR ("client %p: can't create thread", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    gst_rtsp_url_free (url);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    ctx->media = NULL;
    g_object_unref (factory);
    ctx->factory = NULL;
    return NULL;
  }
no_prepare:
  {
    GST_ERROR ("client %p: can't prepare media", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    gst_rtsp_url_free (url);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    ctx->media = NULL;
    g_object_unref (factory);
    ctx->factory = NULL;
    return NULL;
  }
}

static inline DataSeq *
get_data_seq_element (GstRTSPClient * client, guint8 channel)
{
  GstRTSPClientPrivate *priv = client->priv;
  GArray *data_seqs = priv->data_seqs;
  gint i = 0;

  while (i < data_seqs->len) {
    DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
    if (data_seq->channel == channel)
      return data_seq;
    i++;
  }

  return NULL;
}

static void
add_data_seq (GstRTSPClient * client, guint8 channel)
{
  GstRTSPClientPrivate *priv = client->priv;
  DataSeq data_seq = {.channel = channel,.seq = 0 };

  if (get_data_seq_element (client, channel) == NULL)
    g_array_append_val (priv->data_seqs, data_seq);
}

static void
set_data_seq (GstRTSPClient * client, guint8 channel, guint seq)
{
  DataSeq *data_seq;

  data_seq = get_data_seq_element (client, channel);
  g_assert_nonnull (data_seq);
  data_seq->seq = seq;
}

static guint
get_data_seq (GstRTSPClient * client, guint8 channel)
{
  DataSeq *data_seq;

  data_seq = get_data_seq_element (client, channel);
  g_assert_nonnull (data_seq);
  return data_seq->seq;
}

static gboolean
get_data_channel (GstRTSPClient * client, guint seq, guint8 * channel)
{
  GstRTSPClientPrivate *priv = client->priv;
  GArray *data_seqs = priv->data_seqs;
  gint i = 0;

  while (i < data_seqs->len) {
    DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
    if (data_seq->seq == seq) {
      *channel = data_seq->channel;
      return TRUE;
    }
    i++;
  }

  return FALSE;
}

static gboolean
do_close (gpointer user_data)
{
  GstRTSPClient *client = user_data;

  gst_rtsp_client_close (client);

  return G_SOURCE_REMOVE;
}

static gboolean
do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPMessage message = { 0 };
  gboolean ret = TRUE;

  gst_rtsp_message_init_data (&message, channel);

  gst_rtsp_message_set_body_buffer (&message, buffer);

  g_mutex_lock (&priv->send_lock);
  if (get_data_seq (client, channel) != 0) {
    GST_WARNING ("already a queued data message for channel %d", channel);
    g_mutex_unlock (&priv->send_lock);
    return FALSE;
  }
  if (priv->send_messages_func) {
    ret =
        priv->send_messages_func (client, &message, 1, FALSE, priv->send_data);
  } else if (priv->send_func) {
    ret = priv->send_func (client, &message, FALSE, priv->send_data);
  }
  g_mutex_unlock (&priv->send_lock);

  gst_rtsp_message_unset (&message);

  if (!ret) {
    GSource *idle_src;

    /* close in watch context */
    idle_src = g_idle_source_new ();
    g_source_set_callback (idle_src, do_close, client, NULL);
    g_source_attach (idle_src, priv->watch_context);
    g_source_unref (idle_src);
  }

  return ret;
}

static gboolean
do_check_back_pressure (guint8 channel, GstRTSPClient * client)
{
  return get_data_seq (client, channel) != 0;
}

static gboolean
do_send_data_list (GstBufferList * buffer_list, guint8 channel,
    GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  gboolean ret = TRUE;
  guint i, n = gst_buffer_list_length (buffer_list);
  GstRTSPMessage *messages;

  g_mutex_lock (&priv->send_lock);
  if (get_data_seq (client, channel) != 0) {
    GST_WARNING ("already a queued data message for channel %d", channel);
    g_mutex_unlock (&priv->send_lock);
    return FALSE;
  }

  messages = g_newa (GstRTSPMessage, n);
  memset (messages, 0, sizeof (GstRTSPMessage) * n);
  for (i = 0; i < n; i++) {
    GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);
    gst_rtsp_message_init_data (&messages[i], channel);
    gst_rtsp_message_set_body_buffer (&messages[i], buffer);
  }

  if (priv->send_messages_func) {
    ret =
        priv->send_messages_func (client, messages, n, FALSE, priv->send_data);
  } else if (priv->send_func) {
    for (i = 0; i < n; i++) {
      ret = priv->send_func (client, &messages[i], FALSE, priv->send_data);
      if (!ret)
        break;
    }
  }
  g_mutex_unlock (&priv->send_lock);

  for (i = 0; i < n; i++) {
    gst_rtsp_message_unset (&messages[i]);
  }

  if (!ret) {
    GSource *idle_src;

    /* close in watch context */
    idle_src = g_idle_source_new ();
    g_source_set_callback (idle_src, do_close, client, NULL);
    g_source_attach (idle_src, priv->watch_context);
    g_source_unref (idle_src);
  }

  return ret;
}

/**
 * gst_rtsp_client_close:
 * @client: a #GstRTSPClient
 *
 * Close the connection of @client and remove all media it was managing.
 *
 * Since: 1.4
 */
void
gst_rtsp_client_close (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  const gchar *tunnelid;

  GST_DEBUG ("client %p: closing connection", client);

  g_mutex_lock (&priv->watch_lock);

  /* Work around the lack of thread safety of gst_rtsp_connection_close */
  if (priv->watch) {
    gst_rtsp_watch_set_flushing (priv->watch, TRUE);
  }

  if (priv->connection) {
    if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) {
      g_mutex_lock (&tunnels_lock);
      /* remove from tunnelids */
      g_hash_table_remove (tunnels, tunnelid);
      g_mutex_unlock (&tunnels_lock);
    }
    gst_rtsp_connection_flush (priv->connection, TRUE);
    gst_rtsp_connection_close (priv->connection);
  }

  if (priv->watch) {
    GST_DEBUG ("client %p: destroying watch", client);
    g_source_destroy ((GSource *) priv->watch);
    priv->watch = NULL;
    gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
    gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
    rtsp_ctrl_timeout_remove (client);
  }

  g_mutex_unlock (&priv->watch_lock);
}

static gchar *
default_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri)
{
  gchar *path;

  if (uri->query) {
    path = g_strconcat (uri->abspath, "?", uri->query, NULL);
  } else {
    /* normalize rtsp://<IP>:<PORT> to rtsp://<IP>:<PORT>/ */
    path = g_strdup (uri->abspath[0] ? uri->abspath : "/");
  }

  return path;
}

/* Default signal handler function for all "pre-command" signals, like
 * pre-options-request. It just returns the RTSP return code 200.
 * Subclasses can override this to get another default behaviour.
 */
static GstRTSPStatusCode
default_pre_signal_handler (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GST_LOG_OBJECT (client, "returning GST_RTSP_STS_OK");
  return GST_RTSP_STS_OK;
}

/* The pre-signal accumulator function checks the return value of the signal
 * handlers. If any of them returns an RTSP status code that does not start
 * with 2 it will return FALSE, no more signal handlers will be called, and
 * this last RTSP status code will be the result of the signal emission.
 */
static gboolean
pre_signal_accumulator (GSignalInvocationHint * ihint, GValue * return_accu,
    const GValue * handler_return, gpointer data)
{
  GstRTSPStatusCode handler_value = g_value_get_enum (handler_return);
  GstRTSPStatusCode accumulated_value = g_value_get_enum (return_accu);

  if (handler_value < 200 || handler_value > 299) {
    GST_DEBUG ("handler_value : %d, returning FALSE", handler_value);
    g_value_set_enum (return_accu, handler_value);
    return FALSE;
  }

  /* the accumulated value is initiated to 0 by GLib. if current handler value is
   * bigger then use that instead
   *
   * FIXME: Should we prioritize the 2xx codes in a smarter way?
   *        Like, "201 Created" > "250 Low On Storage Space" > "200 OK"?
   */
  if (handler_value > accumulated_value)
    g_value_set_enum (return_accu, handler_value);

  return TRUE;
}

/* The cleanup_transports function is called from handle_teardown_request() to
 * remove any stream transports from the newly closed session that were added to
 * priv->transports in handle_setup_request(). This is done to avoid forwarding
 * data from the client on a channel that we just closed.
 */
static void
cleanup_transports (GstRTSPClient * client, GPtrArray * transports)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPStreamTransport *stream_transport;
  const GstRTSPTransport *rtsp_transport;
  guint i;

  GST_LOG_OBJECT (client, "potentially removing %u transports",
      transports->len);

  for (i = 0; i < transports->len; i++) {
    stream_transport = g_ptr_array_index (transports, i);
    if (stream_transport == NULL) {
      GST_LOG_OBJECT (client, "stream transport %u is NULL, continue", i);
      continue;
    }

    rtsp_transport = gst_rtsp_stream_transport_get_transport (stream_transport);
    if (rtsp_transport == NULL) {
      GST_LOG_OBJECT (client, "RTSP transport %u is NULL, continue", i);
      continue;
    }

    /* priv->transport only stores transports where RTP is tunneled over RTSP */
    if (rtsp_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
      if (!g_hash_table_remove (priv->transports,
              GINT_TO_POINTER (rtsp_transport->interleaved.min))) {
        GST_WARNING_OBJECT (client,
            "failed removing transport with key '%d' from priv->transports",
            rtsp_transport->interleaved.min);
      }
      if (!g_hash_table_remove (priv->transports,
              GINT_TO_POINTER (rtsp_transport->interleaved.max))) {
        GST_WARNING_OBJECT (client,
            "failed removing transport with key '%d' from priv->transports",
            rtsp_transport->interleaved.max);
      }
    } else {
      GST_LOG_OBJECT (client, "transport %u not RTP/RTSP, skip it", i);
    }
  }
}

static gboolean
handle_teardown_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPClientClass *klass;
  GstRTSPSession *session;
  GstRTSPSessionMedia *sessmedia;
  GstRTSPMedia *media;
  GstRTSPStatusCode code;
  gchar *path;
  gint matched;
  gboolean keep_session;
  GstRTSPStatusCode sig_result;
  GPtrArray *session_media_transports;

  if (!ctx->session)
    goto no_session;

  session = ctx->session;

  if (!ctx->uri)
    goto no_uri;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  path = klass->make_path_from_uri (client, ctx->uri);

  /* get a handle to the configuration of the media in the session */
  sessmedia = gst_rtsp_session_dup_media (session, path, &matched);
  if (!sessmedia)
    goto not_found;

  /* only aggregate control for now.. */
  if (path[matched] != '\0')
    goto no_aggregate;

  g_free (path);

  ctx->sessmedia = sessmedia;

  media = gst_rtsp_session_media_get_media (sessmedia);
  g_object_ref (media);
  gst_rtsp_media_lock (media);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST],
      0, ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  /* get a reference to the transports in the session media so we can clean up
   * our priv->transports before returning */
  session_media_transports = gst_rtsp_session_media_get_transports (sessmedia);

  /* we emit the signal before closing the connection */
  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST],
      0, ctx);

  gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);

  /* unmanage the media in the session, returns false if all media session
   * are torn down. */
  keep_session = gst_rtsp_session_release_media (session, sessmedia);
  g_object_unref (sessmedia);

  /* construct the response now */
  code = GST_RTSP_STS_OK;
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  send_message (client, ctx, ctx->response, TRUE);

  if (!keep_session) {
    /* remove the session */
    gst_rtsp_session_pool_remove (priv->session_pool, session);
  }

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  /* remove all transports that were present in the session media which we just
   * unmanaged from the priv->transports array, so we do not try to handle data
   * on channels that were just closed */
  cleanup_transports (client, session_media_transports);
  g_ptr_array_unref (session_media_transports);

  return TRUE;

  /* ERRORS */
no_session:
  {
    GST_ERROR ("client %p: no session", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    return FALSE;
  }
no_uri:
  {
    GST_ERROR ("client %p: no uri supplied", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
not_found:
  {
    GST_ERROR ("client %p: no media for uri", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    g_free (path);
    return FALSE;
  }
no_aggregate:
  {
    GST_ERROR ("client %p: no aggregate path %s", client, path);
    send_generic_error_response (client,
        GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
    g_free (path);
    g_object_unref (sessmedia);
    return FALSE;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
}

static GstRTSPResult
default_params_set (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPResult res;

  res = gst_rtsp_params_set (client, ctx);

  return res;
}

static GstRTSPResult
default_params_get (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPResult res;

  res = gst_rtsp_params_get (client, ctx);

  return res;
}

static gboolean
handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPResult res;
  guint8 *data;
  guint size;
  GstRTSPStatusCode sig_result;

  g_signal_emit (client,
      gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST], 0, ctx,
      &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  res = gst_rtsp_message_get_body (ctx->request, &data, &size);
  if (res != GST_RTSP_OK)
    goto bad_request;

  if (size == 0 || !data || strlen ((char *) data) == 0) {
    if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
      GST_ERROR_OBJECT (client, "Using PLAY request for keep-alive is forbidden"
          " in RTSP 2.0");
      goto bad_request;
    }

    /* no body (or only '\0'), keep-alive request */
    send_generic_response (client, GST_RTSP_STS_OK, ctx);
  } else {
    /* there is a body, handle the params */
    res = GST_RTSP_CLIENT_GET_CLASS (client)->params_get (client, ctx);
    if (res != GST_RTSP_OK)
      goto bad_request;

    send_message (client, ctx, ctx->response, FALSE);
  }

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST],
      0, ctx);

  return TRUE;

  /* ERRORS */
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    return FALSE;
  }
bad_request:
  {
    GST_ERROR ("client %p: bad request", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
}

static gboolean
handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPResult res;
  guint8 *data;
  guint size;
  GstRTSPStatusCode sig_result;

  g_signal_emit (client,
      gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST], 0, ctx,
      &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  res = gst_rtsp_message_get_body (ctx->request, &data, &size);
  if (res != GST_RTSP_OK)
    goto bad_request;

  if (size == 0 || !data || strlen ((char *) data) == 0) {
    /* no body (or only '\0'), keep-alive request */
    send_generic_response (client, GST_RTSP_STS_OK, ctx);
  } else {
    /* there is a body, handle the params */
    res = GST_RTSP_CLIENT_GET_CLASS (client)->params_set (client, ctx);
    if (res != GST_RTSP_OK)
      goto bad_request;

    send_message (client, ctx, ctx->response, FALSE);
  }

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST],
      0, ctx);

  return TRUE;

  /* ERRORS */
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    return FALSE;
  }
bad_request:
  {
    GST_ERROR ("client %p: bad request", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
}

static gboolean
handle_pause_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPSession *session;
  GstRTSPClientClass *klass;
  GstRTSPSessionMedia *sessmedia;
  GstRTSPMedia *media;
  GstRTSPStatusCode code;
  GstRTSPState rtspstate;
  gchar *path;
  gint matched;
  GstRTSPStatusCode sig_result;
  guint i, n;

  if (!(session = ctx->session))
    goto no_session;

  if (!ctx->uri)
    goto no_uri;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  path = klass->make_path_from_uri (client, ctx->uri);

  /* get a handle to the configuration of the media in the session */
  sessmedia = gst_rtsp_session_dup_media (session, path, &matched);
  if (!sessmedia)
    goto not_found;

  if (path[matched] != '\0')
    goto no_aggregate;

  g_free (path);

  media = gst_rtsp_session_media_get_media (sessmedia);
  g_object_ref (media);
  gst_rtsp_media_lock (media);
  n = gst_rtsp_media_n_streams (media);
  for (i = 0; i < n; i++) {
    GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);

    if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
        GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET)
      goto not_supported;
  }

  ctx->sessmedia = sessmedia;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST], 0,
      ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
  /* the session state must be playing or recording */
  if (rtspstate != GST_RTSP_STATE_PLAYING &&
      rtspstate != GST_RTSP_STATE_RECORDING)
    goto invalid_state;

  /* then pause sending */
  gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PAUSED);

  /* construct the response now */
  code = GST_RTSP_STS_OK;
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  send_message (client, ctx, ctx->response, FALSE);

  /* the state is now READY */
  gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY);
  g_object_unref (sessmedia);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST], 0, ctx);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  return TRUE;

  /* ERRORS */
no_session:
  {
    GST_ERROR ("client %p: no session", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    return FALSE;
  }
no_uri:
  {
    GST_ERROR ("client %p: no uri supplied", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
not_found:
  {
    GST_ERROR ("client %p: no media for uri", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    g_free (path);
    return FALSE;
  }
no_aggregate:
  {
    GST_ERROR ("client %p: no aggregate path %s", client, path);
    send_generic_error_response (client,
        GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
    g_object_unref (sessmedia);
    g_free (path);
    return FALSE;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (sessmedia);
    g_object_unref (media);
    return FALSE;
  }
invalid_state:
  {
    GST_ERROR ("client %p: not PLAYING or RECORDING", client);
    send_generic_error_response (client,
        GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (sessmedia);
    g_object_unref (media);
    return FALSE;
  }
not_supported:
  {
    GST_ERROR ("client %p: pausing not supported", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (sessmedia);
    g_object_unref (media);
    return FALSE;
  }
}

/* convert @url and @path to a URL used as a content base for the factory
 * located at @path */
static gchar *
make_base_url (GstRTSPClient * client, GstRTSPUrl * url, const gchar * path)
{
  GstRTSPUrl tmp;
  gchar *result;
  const gchar *trail;

  /* check for trailing '/' and append one */
  trail = (path[strlen (path) - 1] != '/' ? "/" : "");

  tmp = *url;
  tmp.user = NULL;
  tmp.passwd = NULL;
  tmp.abspath = g_strdup_printf ("%s%s", path, trail);
  tmp.query = NULL;
  result = gst_rtsp_url_get_request_uri (&tmp);
  g_free (tmp.abspath);

  return result;
}

/* Check if the given header of type double is present and, if so,
 * put it's value in the supplied variable.
 */
static GstRTSPStatusCode
parse_header_value_double (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPHeaderField header, gboolean * present, gdouble * value)
{
  GstRTSPResult res;
  gchar *str;
  gchar *end;

  res = gst_rtsp_message_get_header (ctx->request, header, &str, 0);
  if (res == GST_RTSP_OK) {
    *value = g_ascii_strtod (str, &end);
    if (end == str)
      goto parse_header_failed;

    GST_DEBUG ("client %p: got '%s', value %f", client,
        gst_rtsp_header_as_text (header), *value);
    *present = TRUE;
  } else {
    *present = FALSE;
  }

  return GST_RTSP_STS_OK;

parse_header_failed:
  {
    GST_ERROR ("client %p: failed parsing '%s' header", client,
        gst_rtsp_header_as_text (header));
    return GST_RTSP_STS_BAD_REQUEST;
  }
}

/* Parse scale and speed headers, if present, and set the rate to
 * (rate * scale * speed) */
static GstRTSPStatusCode
parse_scale_and_speed (GstRTSPClient * client, GstRTSPContext * ctx,
    gboolean * scale_present, gboolean * speed_present, gdouble * rate,
    GstSeekFlags * flags)
{
  gdouble scale = 1.0;
  gdouble speed = 1.0;
  GstRTSPStatusCode status;

  GST_DEBUG ("got rate %f", *rate);

  status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SCALE,
      scale_present, &scale);
  if (status != GST_RTSP_STS_OK)
    return status;

  if (*scale_present) {
    GST_DEBUG ("got Scale %f", scale);
    if (scale == 0)
      goto bad_scale_value;
    *rate *= scale;

    if (ABS (scale) != 1.0)
      *flags |= GST_SEEK_FLAG_TRICKMODE;
  }

  GST_DEBUG ("rate after parsing Scale %f", *rate);

  status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SPEED,
      speed_present, &speed);
  if (status != GST_RTSP_STS_OK)
    return status;

  if (*speed_present) {
    GST_DEBUG ("got Speed %f", speed);
    if (speed <= 0)
      goto bad_speed_value;
    *rate *= speed;
  }

  GST_DEBUG ("rate after parsing Speed %f", *rate);

  return status;

bad_scale_value:
  {
    GST_ERROR ("client %p: bad 'Scale' header value (%f)", client, scale);
    return GST_RTSP_STS_BAD_REQUEST;
  }
bad_speed_value:
  {
    GST_ERROR ("client %p: bad 'Speed' header value (%f)", client, speed);
    return GST_RTSP_STS_BAD_REQUEST;
  }
}

static GstRTSPStatusCode
setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPRangeUnit * unit, gboolean * scale_present, gboolean * speed_present)
{
  gchar *str;
  GstRTSPResult res;
  GstRTSPTimeRange *range = NULL;
  gdouble rate = 1.0;
  GstSeekFlags flags = GST_SEEK_FLAG_NONE;
  GstRTSPClientClass *klass = GST_RTSP_CLIENT_GET_CLASS (client);
  GstRTSPStatusCode rtsp_status_code;
  GstClockTime trickmode_interval = 0;
  gboolean enable_rate_control = TRUE;

  /* parse the range header if we have one */
  res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0);
  if (res == GST_RTSP_OK) {
    gchar *seek_style = NULL;

    res = gst_rtsp_range_parse (str, &range);
    if (res != GST_RTSP_OK)
      goto parse_range_failed;

    *unit = range->unit;

    /* parse seek style header, if present */
    res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_SEEK_STYLE,
        &seek_style, 0);

    if (res == GST_RTSP_OK) {
      if (g_strcmp0 (seek_style, "RAP") == 0)
        flags = GST_SEEK_FLAG_ACCURATE;
      else if (g_strcmp0 (seek_style, "CoRAP") == 0)
        flags = GST_SEEK_FLAG_KEY_UNIT;
      else if (g_strcmp0 (seek_style, "First-Prior") == 0)
        flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_BEFORE;
      else if (g_strcmp0 (seek_style, "Next") == 0)
        flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_AFTER;
      else
        GST_FIXME_OBJECT (client, "Add support for seek style %s", seek_style);
    } else if (range->min.type == GST_RTSP_TIME_END) {
      flags = GST_SEEK_FLAG_ACCURATE;
    } else {
      flags = GST_SEEK_FLAG_KEY_UNIT;
    }

    if (seek_style)
      gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_SEEK_STYLE,
          seek_style);
  } else {
    flags = GST_SEEK_FLAG_ACCURATE;
  }

  /* check for scale and/or speed headers
   * we will set the seek rate to (speed * scale) and let the media decide
   * the resulting scale and speed. in the response we will use rate and applied
   * rate from the resulting segment as values for the speed and scale headers
   * respectively */
  rtsp_status_code = parse_scale_and_speed (client, ctx, scale_present,
      speed_present, &rate, &flags);
  if (rtsp_status_code != GST_RTSP_STS_OK)
    goto scale_speed_failed;

  /* give the application a chance to tweak range, flags, or rate */
  if (klass->adjust_play_mode != NULL) {
    rtsp_status_code =
        klass->adjust_play_mode (client, ctx, &range, &flags, &rate,
        &trickmode_interval, &enable_rate_control);
    if (rtsp_status_code != GST_RTSP_STS_OK)
      goto adjust_play_mode_failed;
  }

  gst_rtsp_media_set_rate_control (ctx->media, enable_rate_control);

  /* now do the seek with the seek options */
  gst_rtsp_media_seek_trickmode (ctx->media, range, flags, rate,
      trickmode_interval);
  if (range != NULL)
    gst_rtsp_range_free (range);

  if (gst_rtsp_media_get_status (ctx->media) == GST_RTSP_MEDIA_STATUS_ERROR)
    goto seek_failed;

  return GST_RTSP_STS_OK;

parse_range_failed:
  {
    GST_ERROR ("client %p: failed parsing range header", client);
    return GST_RTSP_STS_BAD_REQUEST;
  }
scale_speed_failed:
  {
    if (range != NULL)
      gst_rtsp_range_free (range);
    GST_ERROR ("client %p: failed parsing Scale or Speed headers", client);
    return rtsp_status_code;
  }
adjust_play_mode_failed:
  {
    GST_ERROR ("client %p: sub class returned bad code (%d)", client,
        rtsp_status_code);
    if (range != NULL)
      gst_rtsp_range_free (range);
    return rtsp_status_code;
  }
seek_failed:
  {
    GST_ERROR ("client %p: seek failed", client);
    return GST_RTSP_STS_SERVICE_UNAVAILABLE;
  }
}

static gboolean
handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPSession *session;
  GstRTSPClientClass *klass;
  GstRTSPSessionMedia *sessmedia;
  GstRTSPMedia *media;
  GstRTSPStatusCode code;
  GstRTSPUrl *uri;
  gchar *str;
  GstRTSPState rtspstate;
  GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT;
  gchar *path, *rtpinfo = NULL;
  gint matched;
  GstRTSPStatusCode sig_result;
  GPtrArray *transports;
  gboolean scale_present;
  gboolean speed_present;
  gdouble rate;
  gdouble applied_rate;

  if (!(session = ctx->session))
    goto no_session;

  if (!(uri = ctx->uri))
    goto no_uri;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  path = klass->make_path_from_uri (client, uri);

  /* get a handle to the configuration of the media in the session */
  sessmedia = gst_rtsp_session_dup_media (session, path, &matched);
  if (!sessmedia)
    goto not_found;

  if (path[matched] != '\0')
    goto no_aggregate;

  g_free (path);

  ctx->sessmedia = sessmedia;
  ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);

  g_object_ref (media);
  gst_rtsp_media_lock (media);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST], 0,
      ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  if (!(gst_rtsp_media_get_transport_mode (media) &
          GST_RTSP_TRANSPORT_MODE_PLAY))
    goto unsupported_mode;

  /* the session state must be playing or ready */
  rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
  if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
    goto invalid_state;

  /* update the pipeline */
  transports = gst_rtsp_session_media_get_transports (sessmedia);
  if (!gst_rtsp_media_complete_pipeline (media, transports)) {
    g_ptr_array_unref (transports);
    goto pipeline_error;
  }
  g_ptr_array_unref (transports);

  /* in play we first unsuspend, media could be suspended from SDP or PAUSED */
  if (!gst_rtsp_media_unsuspend (media))
    goto unsuspend_failed;

  code = setup_play_mode (client, ctx, &unit, &scale_present, &speed_present);
  if (code != GST_RTSP_STS_OK)
    goto invalid_mode;

  /* grab RTPInfo from the media now */
  if (gst_rtsp_media_has_completed_sender (media) &&
      !(rtpinfo = gst_rtsp_session_media_get_rtpinfo (sessmedia)))
    goto rtp_info_error;

  /* construct the response now */
  code = GST_RTSP_STS_OK;
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  /* add the RTP-Info header */
  if (rtpinfo)
    gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO,
        rtpinfo);

  /* add the range */
  str = gst_rtsp_media_get_range_string (media, TRUE, unit);
  if (str)
    gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RANGE, str);

  if (gst_rtsp_media_has_completed_sender (media)) {
    /* the scale and speed headers must always be added if they were present in
     * the request. however, even if they were not, we still add them if
     * applied_rate or rate deviate from the "normal", i.e. 1.0 */
    if (!gst_rtsp_media_get_rates (media, &rate, &applied_rate))
      goto get_rates_error;
    g_assert (rate != 0 && applied_rate != 0);

    if (scale_present || applied_rate != 1.0)
      gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SCALE,
          g_strdup_printf ("%1.3f", applied_rate));

    if (speed_present || rate != 1.0)
      gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SPEED,
          g_strdup_printf ("%1.3f", rate));
  }

  if (klass->adjust_play_response) {
    code = klass->adjust_play_response (client, ctx);
    if (code != GST_RTSP_STS_OK)
      goto adjust_play_response_failed;
  }

  send_message (client, ctx, ctx->response, FALSE);

  /* start playing after sending the response */
  gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);

  gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);
  g_object_unref (sessmedia);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST], 0, ctx);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  return TRUE;

  /* ERRORS */
no_session:
  {
    GST_ERROR ("client %p: no session", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    return FALSE;
  }
no_uri:
  {
    GST_ERROR ("client %p: no uri supplied", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
not_found:
  {
    GST_ERROR ("client %p: media not found", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_aggregate:
  {
    GST_ERROR ("client %p: no aggregate path %s", client, path);
    send_generic_error_response (client,
        GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
    g_object_unref (sessmedia);
    g_free (path);
    return FALSE;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
invalid_state:
  {
    GST_ERROR ("client %p: not PLAYING or READY", client);
    send_generic_error_response (client,
        GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
pipeline_error:
  {
    GST_ERROR ("client %p: failed to configure the pipeline", client);
    send_generic_error_response (client,
        GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
unsuspend_failed:
  {
    GST_ERROR ("client %p: unsuspend failed", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
invalid_mode:
  {
    GST_ERROR ("client %p: seek failed", client);
    send_generic_error_response (client, code, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
unsupported_mode:
  {
    GST_ERROR ("client %p: media does not support PLAY", client);
    send_generic_error_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
get_rates_error:
  {
    GST_ERROR ("client %p: failed obtaining rate and applied_rate", client);
    send_generic_error_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR,
        ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
adjust_play_response_failed:
  {
    GST_ERROR ("client %p: failed to adjust play response", client);
    send_generic_error_response (client, code, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
rtp_info_error:
  {
    GST_ERROR ("client %p: failed to add RTP-Info", client);
    send_generic_error_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR,
        ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    g_object_unref (sessmedia);
    return FALSE;
  }
}

static void
do_keepalive (GstRTSPSession * session)
{
  GST_INFO ("keep session %p alive", session);
  gst_rtsp_session_touch (session);
}

/* parse @transport and return a valid transport in @tr. only transports
 * supported by @stream are returned. Returns FALSE if no valid transport
 * was found. */
static gboolean
parse_transport (const char *transport, GstRTSPStream * stream,
    GstRTSPTransport * tr)
{
  gint i;
  gboolean res;
  gchar **transports;

  res = FALSE;
  gst_rtsp_transport_init (tr);

  GST_DEBUG ("parsing transports %s", transport);

  transports = g_strsplit (transport, ",", 0);

  /* loop through the transports, try to parse */
  for (i = 0; transports[i]; i++) {
    g_strstrip (transports[i]);
    res = gst_rtsp_transport_parse (transports[i], tr);
    if (res != GST_RTSP_OK) {
      /* no valid transport, search some more */
      GST_WARNING ("could not parse transport %s", transports[i]);
      goto next;
    }

    /* we have a transport, see if it's supported */
    if (!gst_rtsp_stream_is_transport_supported (stream, tr)) {
      GST_WARNING ("unsupported transport %s", transports[i]);
      goto next;
    }

    /* we have a valid transport */
    GST_INFO ("found valid transport %s", transports[i]);
    res = TRUE;
    break;

  next:
    gst_rtsp_transport_init (tr);
  }
  g_strfreev (transports);

  return res;
}

static gboolean
default_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media,
    GstRTSPStream * stream, GstRTSPContext * ctx)
{
  GstRTSPMessage *request = ctx->request;
  gchar *blocksize_str;

  if (!gst_rtsp_stream_is_sender (stream))
    return TRUE;

  if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_BLOCKSIZE,
          &blocksize_str, 0) == GST_RTSP_OK) {
    guint64 blocksize;
    gchar *end;

    blocksize = g_ascii_strtoull (blocksize_str, &end, 10);
    if (end == blocksize_str)
      goto parse_failed;

    /* we don't want to change the mtu when this media
     * can be shared because it impacts other clients */
    if (gst_rtsp_media_is_shared (media))
      goto done;

    if (blocksize > G_MAXUINT)
      blocksize = G_MAXUINT;

    gst_rtsp_stream_set_mtu (stream, blocksize);
  }
done:
  return TRUE;

  /* ERRORS */
parse_failed:
  {
    GST_ERROR_OBJECT (client, "failed to parse blocksize");
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
}

static gboolean
default_configure_client_transport (GstRTSPClient * client,
    GstRTSPContext * ctx, GstRTSPTransport * ct)
{
  GstRTSPClientPrivate *priv = client->priv;

  /* we have a valid transport now, set the destination of the client. */
  if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST ||
      ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP) {
    /* allocate UDP ports */
    GSocketFamily family;
    gboolean use_client_settings = FALSE;

    family = priv->is_ipv6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4;

    if ((ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) &&
        gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS) &&
        (ct->destination != NULL)) {

      if (!gst_rtsp_stream_verify_mcast_ttl (ctx->stream, ct->ttl))
        goto error_ttl;

      use_client_settings = TRUE;
    }

    /* We need to allocate the sockets for both families before starting
     * multiudpsink, otherwise multiudpsink won't accept new clients with
     * a different family.
     */
    /* FIXME: could be more adequately solved by making it possible
     * to set a socket on multiudpsink after it has already been started */
    if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
            G_SOCKET_FAMILY_IPV4, ct, use_client_settings)
        && family == G_SOCKET_FAMILY_IPV4)
      goto error_allocating_ports;

    if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
            G_SOCKET_FAMILY_IPV6, ct, use_client_settings)
        && family == G_SOCKET_FAMILY_IPV6)
      goto error_allocating_ports;

    if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
      if (use_client_settings) {
        /* FIXME: the address has been successfully allocated, however, in
         * the use_client_settings case we need to verify that the allocated
         * address is the one requested by the client and if this address is
         * an allowed destination. Verifying this via the address pool in not
         * the proper way as the address pool should only be used for choosing
         * the server-selected address/port pairs. */
        GSocket *rtp_socket;
        guint ttl;

        rtp_socket =
            gst_rtsp_stream_get_rtp_multicast_socket (ctx->stream, family);
        if (rtp_socket == NULL)
          goto no_socket;
        ttl = g_socket_get_multicast_ttl (rtp_socket);
        g_object_unref (rtp_socket);
        if (ct->ttl < ttl) {
          /* use the maximum ttl that is requested by multicast clients */
          GST_DEBUG ("requested ttl %u, but keeping ttl %u", ct->ttl, ttl);
          ct->ttl = ttl;
        }

      } else {
        GstRTSPAddress *addr = NULL;

        g_free (ct->destination);
        addr = gst_rtsp_stream_get_multicast_address (ctx->stream, family);
        if (addr == NULL)
          goto no_address;
        ct->destination = g_strdup (addr->address);
        ct->port.min = addr->port;
        ct->port.max = addr->port + addr->n_ports - 1;
        ct->ttl = addr->ttl;
        gst_rtsp_address_free (addr);
      }

      if (!gst_rtsp_stream_add_multicast_client_address (ctx->stream,
              ct->destination, ct->port.min, ct->port.max, family))
        goto error_mcast_transport;

    } else {
      GstRTSPUrl *url;

      url = gst_rtsp_connection_get_url (priv->connection);
      g_free (ct->destination);
      ct->destination = g_strdup (url->host);
    }
  } else {
    GstRTSPUrl *url;

    url = gst_rtsp_connection_get_url (priv->connection);
    g_free (ct->destination);
    ct->destination = g_strdup (url->host);

    if (ct->lower_transport & GST_RTSP_LOWER_TRANS_TCP) {
      GSocket *sock;
      GSocketAddress *addr;

      sock = gst_rtsp_connection_get_read_socket (priv->connection);
      if ((addr = g_socket_get_remote_address (sock, NULL))) {
        /* our read port is the sender port of client */
        ct->client_port.min =
            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
        g_object_unref (addr);
      }
      if ((addr = g_socket_get_local_address (sock, NULL))) {
        ct->server_port.max =
            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
        g_object_unref (addr);
      }
      sock = gst_rtsp_connection_get_write_socket (priv->connection);
      if ((addr = g_socket_get_remote_address (sock, NULL))) {
        /* our write port is the receiver port of client */
        ct->client_port.max =
            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
        g_object_unref (addr);
      }
      if ((addr = g_socket_get_local_address (sock, NULL))) {
        ct->server_port.min =
            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
        g_object_unref (addr);
      }
      /* check if the client selected channels for TCP */
      if (ct->interleaved.min == -1 || ct->interleaved.max == -1) {
        gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
            &ct->interleaved);
      }
      /* alloc new channels if they are already taken */
      while (g_hash_table_contains (priv->transports,
              GINT_TO_POINTER (ct->interleaved.min))
          || g_hash_table_contains (priv->transports,
              GINT_TO_POINTER (ct->interleaved.max))) {
        gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
            &ct->interleaved);
        if (ct->interleaved.max > 255)
          goto error_allocating_channels;
      }
    }
  }
  return TRUE;

  /* ERRORS */
error_ttl:
  {
    GST_ERROR_OBJECT (client,
        "Failed to allocate UDP ports: invalid ttl value");
    return FALSE;
  }
error_allocating_ports:
  {
    GST_ERROR_OBJECT (client, "Failed to allocate UDP ports");
    return FALSE;
  }
no_address:
  {
    GST_ERROR_OBJECT (client, "Failed to acquire address for stream");
    return FALSE;
  }
no_socket:
  {
    GST_ERROR_OBJECT (client, "Failed to get UDP socket");
    return FALSE;
  }
error_mcast_transport:
  {
    GST_ERROR_OBJECT (client, "Failed to add multicast client transport");
    return FALSE;
  }
error_allocating_channels:
  {
    GST_ERROR_OBJECT (client, "Failed to allocate interleaved channels");
    return FALSE;
  }
}

static GstRTSPTransport *
make_server_transport (GstRTSPClient * client, GstRTSPMedia * media,
    GstRTSPContext * ctx, GstRTSPTransport * ct)
{
  GstRTSPTransport *st;
  GInetAddress *addr;
  GSocketFamily family;

  /* prepare the server transport */
  gst_rtsp_transport_new (&st);

  st->trans = ct->trans;
  st->profile = ct->profile;
  st->lower_transport = ct->lower_transport;
  st->mode_play = ct->mode_play;
  st->mode_record = ct->mode_record;

  addr = g_inet_address_new_from_string (ct->destination);

  if (!addr) {
    GST_ERROR ("failed to get inet addr from client destination");
    family = G_SOCKET_FAMILY_IPV4;
  } else {
    family = g_inet_address_get_family (addr);
    g_object_unref (addr);
    addr = NULL;
  }

  switch (st->lower_transport) {
    case GST_RTSP_LOWER_TRANS_UDP:
      st->client_port = ct->client_port;
      gst_rtsp_stream_get_server_port (ctx->stream, &st->server_port, family);
      break;
    case GST_RTSP_LOWER_TRANS_UDP_MCAST:
      st->port = ct->port;
      st->destination = g_strdup (ct->destination);
      st->ttl = ct->ttl;
      break;
    case GST_RTSP_LOWER_TRANS_TCP:
      st->interleaved = ct->interleaved;
      st->client_port = ct->client_port;
      st->server_port = ct->server_port;
    default:
      break;
  }

  if ((gst_rtsp_media_get_transport_mode (media) &
          GST_RTSP_TRANSPORT_MODE_PLAY))
    gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);

  return st;
}

static void
rtsp_ctrl_timeout_remove_unlocked (GstRTSPClientPrivate * priv)
{
  if (priv->rtsp_ctrl_timeout != NULL) {
    GST_DEBUG ("rtsp control session removed timeout %p.",
        priv->rtsp_ctrl_timeout);
    g_source_destroy (priv->rtsp_ctrl_timeout);
    g_source_unref (priv->rtsp_ctrl_timeout);
    priv->rtsp_ctrl_timeout = NULL;
    priv->rtsp_ctrl_timeout_cnt = 0;
  }
}

static void
rtsp_ctrl_timeout_remove (GstRTSPClient * client)
{
  g_mutex_lock (&client->priv->lock);
  rtsp_ctrl_timeout_remove_unlocked (client->priv);
  g_mutex_unlock (&client->priv->lock);
}

static void
rtsp_ctrl_timeout_destroy_notify (gpointer user_data)
{
  GWeakRef *client_weak_ref = (GWeakRef *) user_data;

  g_weak_ref_clear (client_weak_ref);
  g_free (client_weak_ref);
}

static gboolean
rtsp_ctrl_timeout_cb (gpointer user_data)
{
  gboolean res = G_SOURCE_CONTINUE;
  GstRTSPClientPrivate *priv;
  GWeakRef *client_weak_ref = (GWeakRef *) user_data;
  GstRTSPClient *client = (GstRTSPClient *) g_weak_ref_get (client_weak_ref);

  if (client == NULL) {
    return G_SOURCE_REMOVE;
  }

  priv = client->priv;
  g_mutex_lock (&priv->lock);
  priv->rtsp_ctrl_timeout_cnt += RTSP_CTRL_CB_INTERVAL;

  if ((priv->rtsp_ctrl_timeout_cnt > RTSP_CTRL_TIMEOUT_VALUE)
      || (priv->had_session
          && priv->rtsp_ctrl_timeout_cnt > priv->post_session_timeout)) {
    GST_DEBUG ("rtsp control session timeout %p expired, closing client.",
        priv->rtsp_ctrl_timeout);
    rtsp_ctrl_timeout_remove_unlocked (client->priv);

    res = G_SOURCE_REMOVE;
  }

  g_mutex_unlock (&priv->lock);

  if (res == G_SOURCE_REMOVE) {
    gst_rtsp_client_close (client);
  }

  g_object_unref (client);

  return res;
}

static gchar *
stream_make_keymgmt (GstRTSPClient * client, const gchar * location,
    GstRTSPStream * stream)
{
  gchar *base64, *result = NULL;
  GstMIKEYMessage *mikey_msg;
  GstCaps *srtcpparams;
  GstElement *rtcp_encoder;
  gint srtcp_cipher, srtp_cipher;
  gint srtcp_auth, srtp_auth;
  GstBuffer *key;
  GType ciphertype, authtype;
  GEnumClass *cipher_enum, *auth_enum;
  GEnumValue *srtcp_cipher_value, *srtp_cipher_value, *srtcp_auth_value,
      *srtp_auth_value;

  rtcp_encoder = gst_rtsp_stream_get_srtp_encoder (stream);

  if (!rtcp_encoder)
    goto done;

  ciphertype = g_type_from_name ("GstSrtpCipherType");
  authtype = g_type_from_name ("GstSrtpAuthType");

  cipher_enum = g_type_class_ref (ciphertype);
  auth_enum = g_type_class_ref (authtype);

  /* We need to bring the encoder to READY so that it generates its key */
  gst_element_set_state (rtcp_encoder, GST_STATE_READY);

  g_object_get (rtcp_encoder, "rtcp-cipher", &srtcp_cipher, "rtcp-auth",
      &srtcp_auth, "rtp-cipher", &srtp_cipher, "rtp-auth", &srtp_auth, "key",
      &key, NULL);
  g_object_unref (rtcp_encoder);

  srtcp_cipher_value = g_enum_get_value (cipher_enum, srtcp_cipher);
  srtp_cipher_value = g_enum_get_value (cipher_enum, srtp_cipher);
  srtcp_auth_value = g_enum_get_value (auth_enum, srtcp_auth);
  srtp_auth_value = g_enum_get_value (auth_enum, srtp_auth);

  g_type_class_unref (cipher_enum);
  g_type_class_unref (auth_enum);

  srtcpparams = gst_caps_new_simple ("application/x-srtcp",
      "srtcp-cipher", G_TYPE_STRING, srtcp_cipher_value->value_nick,
      "srtcp-auth", G_TYPE_STRING, srtcp_auth_value->value_nick,
      "srtp-cipher", G_TYPE_STRING, srtp_cipher_value->value_nick,
      "srtp-auth", G_TYPE_STRING, srtp_auth_value->value_nick,
      "srtp-key", GST_TYPE_BUFFER, key, NULL);

  mikey_msg = gst_mikey_message_new_from_caps (srtcpparams);
  if (mikey_msg) {
    guint send_ssrc;

    gst_rtsp_stream_get_ssrc (stream, &send_ssrc);
    gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_ssrc, 0);

    base64 = gst_mikey_message_base64_encode (mikey_msg);
    gst_mikey_message_unref (mikey_msg);

    if (base64) {
      result = gst_sdp_make_keymgmt (location, base64);
      g_free (base64);
    }
  }

done:
  return result;
}

static gboolean
handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPResult res;
  GstRTSPUrl *uri;
  gchar *transport, *keymgmt;
  GstRTSPTransport *ct, *st;
  GstRTSPStatusCode code;
  GstRTSPSession *session;
  GstRTSPStreamTransport *trans;
  gchar *trans_str;
  GstRTSPSessionMedia *sessmedia;
  GstRTSPMedia *media;
  GstRTSPStream *stream;
  GstRTSPState rtspstate;
  GstRTSPClientClass *klass;
  gchar *path, *control = NULL;
  gint matched;
  gboolean new_session = FALSE;
  GstRTSPStatusCode sig_result;
  gchar *pipelined_request_id = NULL, *accept_range = NULL;

  if (!ctx->uri)
    goto no_uri;

  uri = ctx->uri;
  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  path = klass->make_path_from_uri (client, uri);

  /* parse the transport */
  res =
      gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_TRANSPORT,
      &transport, 0);
  if (res != GST_RTSP_OK)
    goto no_transport;

  /* Handle Pipelined-requests if using >= 2.0 */
  if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0)
    gst_rtsp_message_get_header (ctx->request,
        GST_RTSP_HDR_PIPELINED_REQUESTS, &pipelined_request_id, 0);

  /* we create the session after parsing stuff so that we don't make
   * a session for malformed requests */
  if (priv->session_pool == NULL)
    goto no_pool;

  session = ctx->session;

  if (session) {
    g_object_ref (session);
    /* get a handle to the configuration of the media in the session, this can
     * return NULL if this is a new url to manage in this session. */
    sessmedia = gst_rtsp_session_get_media (session, path, &matched);
  } else {
    /* we need a new media configuration in this session */
    sessmedia = NULL;
  }

  /* we have no session media, find one and manage it */
  if (sessmedia == NULL) {
    /* get a handle to the configuration of the media in the session */
    media = find_media (client, ctx, path, &matched);
    /* need to suspend the media, if the protocol has changed */
    if (media != NULL) {
      gst_rtsp_media_suspend (media);
    }
  } else {
    if ((media = gst_rtsp_session_media_get_media (sessmedia))) {
      g_object_ref (media);
      gst_rtsp_media_lock (media);
    } else {
      goto media_not_found;
    }
  }
  /* no media, not found then */
  if (media == NULL)
    goto media_not_found_no_reply;

  if (path[matched] == '\0') {
    if (gst_rtsp_media_n_streams (media) == 1) {
      stream = gst_rtsp_media_get_stream (media, 0);
    } else {
      goto control_not_found;
    }
  } else {
    /* path is what matched. */
    gchar *newpath = g_strndup (path, matched);
    /* control is remainder */
    if (matched == 1 && path[0] == '/')
      control = g_strdup (&path[1]);
    else
      control = g_strdup (&path[matched + 1]);

    g_free (path);
    path = newpath;

    /* find the stream now using the control part */
    stream = gst_rtsp_media_find_stream (media, control);
  }

  if (stream == NULL)
    goto stream_not_found;

  /* now we have a uri identifying a valid media and stream */
  ctx->stream = stream;
  ctx->media = media;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST], 0,
      ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  if (session == NULL) {
    /* create a session if this fails we probably reached our session limit or
     * something. */
    if (!(session = gst_rtsp_session_pool_create (priv->session_pool)))
      goto service_unavailable;

    /* Pipelined requests should be cleared between sessions */
    g_hash_table_remove_all (priv->pipelined_requests);

    /* make sure this client is closed when the session is closed */
    client_watch_session (client, session);

    new_session = TRUE;
    /* signal new session */
    g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_NEW_SESSION], 0,
        session);

    ctx->session = session;
  }

  if (pipelined_request_id) {
    g_hash_table_insert (client->priv->pipelined_requests,
        g_strdup (pipelined_request_id),
        g_strdup (gst_rtsp_session_get_sessionid (session)));
  }
  /* Remember that we had at least one session in the past */
  priv->had_session = TRUE;
  rtsp_ctrl_timeout_remove (client);

  if (!klass->configure_client_media (client, media, stream, ctx))
    goto configure_media_failed_no_reply;

  gst_rtsp_transport_new (&ct);

  /* parse and find a usable supported transport */
  if (!parse_transport (transport, stream, ct))
    goto unsupported_transports;

  if ((ct->mode_play
          && !(gst_rtsp_media_get_transport_mode (media) &
              GST_RTSP_TRANSPORT_MODE_PLAY)) || (ct->mode_record
          && !(gst_rtsp_media_get_transport_mode (media) &
              GST_RTSP_TRANSPORT_MODE_RECORD)))
    goto unsupported_mode;

  /* parse the keymgmt */
  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_KEYMGMT,
          &keymgmt, 0) == GST_RTSP_OK) {
    if (!gst_rtsp_stream_handle_keymgmt (ctx->stream, keymgmt))
      goto keymgmt_error;
  }

  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
          &accept_range, 0) == GST_RTSP_OK) {
    GEnumValue *runit = NULL;
    gint i;
    gchar **valid_ranges;
    GEnumClass *runit_class = g_type_class_ref (GST_TYPE_RTSP_RANGE_UNIT);

    gst_rtsp_message_dump (ctx->request);
    valid_ranges = g_strsplit (accept_range, ",", -1);

    for (i = 0; valid_ranges[i]; i++) {
      gchar *range = valid_ranges[i];

      while (*range == ' ')
        range++;

      runit = g_enum_get_value_by_nick (runit_class, range);
      if (runit)
        break;
    }
    g_strfreev (valid_ranges);
    g_type_class_unref (runit_class);

    if (!runit)
      goto unsupported_range_unit;
  }

  if (sessmedia == NULL) {
    /* manage the media in our session now, if not done already  */
    sessmedia =
        gst_rtsp_session_manage_media (session, path, g_object_ref (media));
    /* if we stil have no media, error */
    if (sessmedia == NULL)
      goto sessmedia_unavailable;

    /* don't cache media anymore */
    clean_cached_media (client, FALSE);
  }

  ctx->sessmedia = sessmedia;

  /* update the client transport */
  if (!klass->configure_client_transport (client, ctx, ct))
    goto unsupported_client_transport;

  /* set in the session media transport */
  trans = gst_rtsp_session_media_set_transport (sessmedia, stream, ct);

  ctx->trans = trans;

  /* configure the url used to set this transport, this we will use when
   * generating the response for the PLAY request */
  gst_rtsp_stream_transport_set_url (trans, uri);
  /* configure keepalive for this transport */
  gst_rtsp_stream_transport_set_keepalive (trans,
      (GstRTSPKeepAliveFunc) do_keepalive, session, NULL);

  if (ct->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
    /* our callbacks to send data on this TCP connection */
    gst_rtsp_stream_transport_set_callbacks (trans,
        (GstRTSPSendFunc) do_send_data,
        (GstRTSPSendFunc) do_send_data, client, NULL);
    gst_rtsp_stream_transport_set_list_callbacks (trans,
        (GstRTSPSendListFunc) do_send_data_list,
        (GstRTSPSendListFunc) do_send_data_list, client, NULL);

    gst_rtsp_stream_transport_set_back_pressure_callback (trans,
        (GstRTSPBackPressureFunc) do_check_back_pressure, client, NULL);

    g_hash_table_insert (priv->transports,
        GINT_TO_POINTER (ct->interleaved.min), trans);
    g_object_ref (trans);
    g_hash_table_insert (priv->transports,
        GINT_TO_POINTER (ct->interleaved.max), trans);
    g_object_ref (trans);
    add_data_seq (client, ct->interleaved.min);
    add_data_seq (client, ct->interleaved.max);
  }

  /* create and serialize the server transport */
  st = make_server_transport (client, media, ctx, ct);
  trans_str = gst_rtsp_transport_as_text (st);
  gst_rtsp_transport_free (st);

  /* construct the response now */
  code = GST_RTSP_STS_OK;
  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_TRANSPORT,
      trans_str);
  g_free (trans_str);

  if (pipelined_request_id)
    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PIPELINED_REQUESTS,
        pipelined_request_id);

  if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
    GstClockTimeDiff seekable = gst_rtsp_media_seekable (media);
    GString *media_properties = g_string_new (NULL);

    if (seekable == -1)
      g_string_append (media_properties,
          "No-Seeking,Time-Progressing,Time-Duration=0.0");
    else if (seekable == 0)
      g_string_append (media_properties, "Beginning-Only");
    else if (seekable == G_MAXINT64)
      g_string_append (media_properties, "Random-Access");
    else
      g_string_append_printf (media_properties,
          "Random-Access=%f, Unlimited, Immutable",
          (gdouble) seekable / GST_SECOND);

    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_MEDIA_PROPERTIES,
        media_properties->str);
    g_string_free (media_properties, TRUE);
    /* TODO Check how Accept-Ranges should be filled */
    gst_rtsp_message_add_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
        "npt, clock, smpte, clock");
  }

  send_message (client, ctx, ctx->response, FALSE);

  /* update the state */
  rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
  switch (rtspstate) {
    case GST_RTSP_STATE_PLAYING:
    case GST_RTSP_STATE_RECORDING:
    case GST_RTSP_STATE_READY:
      /* no state change */
      break;
    default:
      gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY);
      break;
  }

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST], 0, ctx);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  g_object_unref (session);
  g_free (path);
  g_free (control);

  return TRUE;

  /* ERRORS */
no_uri:
  {
    GST_ERROR ("client %p: no uri", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
no_transport:
  {
    GST_ERROR ("client %p: no transport", client);
    send_generic_error_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT,
        ctx);
    goto cleanup_path;
  }
no_pool:
  {
    GST_ERROR ("client %p: no session pool configured", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    goto cleanup_path;
  }
media_not_found_no_reply:
  {
    GST_ERROR ("client %p: media '%s' not found", client, path);
    /* error reply is already sent */
    goto cleanup_session;
  }
media_not_found:
  {
    GST_ERROR ("client %p: media '%s' not found", client, path);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    goto cleanup_session;
  }
control_not_found:
  {
    GST_ERROR ("client %p: no control in path '%s'", client, path);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    goto cleanup_session;
  }
stream_not_found:
  {
    GST_ERROR ("client %p: stream '%s' not found", client,
        GST_STR_NULL (control));
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    goto cleanup_session;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    goto cleanup_path;
  }
service_unavailable:
  {
    GST_ERROR ("client %p: can't create session", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    goto cleanup_session;
  }
sessmedia_unavailable:
  {
    GST_ERROR ("client %p: can't create session media", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    goto cleanup_transport;
  }
configure_media_failed_no_reply:
  {
    GST_ERROR ("client %p: configure_media failed", client);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    /* error reply is already sent */
    goto cleanup_session;
  }
unsupported_transports:
  {
    GST_ERROR ("client %p: unsupported transports", client);
    send_generic_error_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT,
        ctx);
    goto cleanup_transport;
  }
unsupported_client_transport:
  {
    GST_ERROR ("client %p: unsupported client transport", client);
    send_generic_error_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT,
        ctx);
    goto cleanup_transport;
  }
unsupported_mode:
  {
    GST_ERROR ("client %p: unsupported mode (media play: %d, media record: %d, "
        "mode play: %d, mode record: %d)", client,
        !!(gst_rtsp_media_get_transport_mode (media) &
            GST_RTSP_TRANSPORT_MODE_PLAY),
        !!(gst_rtsp_media_get_transport_mode (media) &
            GST_RTSP_TRANSPORT_MODE_RECORD), ct->mode_play, ct->mode_record);
    send_generic_error_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT,
        ctx);
    goto cleanup_transport;
  }
unsupported_range_unit:
  {
    GST_ERROR ("Client %p: does not support any range format we support",
        client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
    goto cleanup_transport;
  }
keymgmt_error:
  {
    GST_ERROR ("client %p: keymgmt error", client);
    send_generic_error_response (client, GST_RTSP_STS_KEY_MANAGEMENT_FAILURE,
        ctx);
    goto cleanup_transport;
  }
  {
  cleanup_transport:
    gst_rtsp_transport_free (ct);
    if (media) {
      gst_rtsp_media_unlock (media);
      g_object_unref (media);
    }
  cleanup_session:
    if (new_session)
      gst_rtsp_session_pool_remove (priv->session_pool, session);
    if (session)
      g_object_unref (session);
  cleanup_path:
    g_free (path);
    g_free (control);
    return FALSE;
  }
}

static GstSDPMessage *
create_sdp (GstRTSPClient * client, GstRTSPMedia * media)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstSDPMessage *sdp;
  GstSDPInfo info;
  const gchar *proto;
  guint64 session_id_tmp;
  gchar session_id[21];

  gst_sdp_message_new (&sdp);

  /* some standard things first */
  gst_sdp_message_set_version (sdp, "0");

  if (priv->is_ipv6)
    proto = "IP6";
  else
    proto = "IP4";

  session_id_tmp = (((guint64) g_random_int ()) << 32) | g_random_int ();
  g_snprintf (session_id, sizeof (session_id), "%" G_GUINT64_FORMAT,
      session_id_tmp);

  gst_sdp_message_set_origin (sdp, "-", session_id, "1", "IN", proto,
      priv->server_ip);

  gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
  gst_sdp_message_set_information (sdp, "rtsp-server");
  gst_sdp_message_add_time (sdp, "0", "0", NULL);
  gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");
  gst_sdp_message_add_attribute (sdp, "type", "broadcast");
  gst_sdp_message_add_attribute (sdp, "control", "*");

  info.is_ipv6 = priv->is_ipv6;
  info.server_ip = priv->server_ip;

  /* create an SDP for the media object */
  if (!gst_rtsp_media_setup_sdp (media, sdp, &info))
    goto no_sdp;

  return sdp;

  /* ERRORS */
no_sdp:
  {
    GST_ERROR ("client %p: could not create SDP", client);
    gst_sdp_message_free (sdp);
    return NULL;
  }
}

/* for the describe we must generate an SDP */
static gboolean
handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPResult res;
  GstSDPMessage *sdp;
  guint i;
  gchar *path, *str;
  GstRTSPMedia *media;
  GstRTSPClientClass *klass;
  GstRTSPStatusCode sig_result;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);

  if (!ctx->uri)
    goto no_uri;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST],
      0, ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  /* check what kind of format is accepted, we don't really do anything with it
   * and always return SDP for now. */
  for (i = 0;; i++) {
    gchar *accept;

    res =
        gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT,
        &accept, i);
    if (res == GST_RTSP_ENOTIMPL)
      break;

    if (g_ascii_strcasecmp (accept, "application/sdp") == 0)
      break;
  }

  if (!priv->mount_points)
    goto no_mount_points;

  if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
    goto no_path;

  /* find the media object for the uri */
  if (!(media = find_media (client, ctx, path, NULL)))
    goto no_media;

  if (!(gst_rtsp_media_get_transport_mode (media) &
          GST_RTSP_TRANSPORT_MODE_PLAY))
    goto unsupported_mode;

  /* create an SDP for the media object on this client */
  if (!(sdp = klass->create_sdp (client, media)))
    goto no_sdp;

  /* we suspend after the describe */
  gst_rtsp_media_suspend (media);

  gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
      gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);

  gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  /* content base for some clients that might screw up creating the setup uri */
  str = make_base_url (client, ctx->uri, path);
  g_free (path);

  GST_INFO ("adding content-base: %s", str);
  gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_CONTENT_BASE, str);

  /* add SDP to the response body */
  str = gst_sdp_message_as_text (sdp);
  gst_rtsp_message_take_body (ctx->response, (guint8 *) str, strlen (str));
  gst_sdp_message_free (sdp);

  send_message (client, ctx, ctx->response, FALSE);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST],
      0, ctx);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  return TRUE;

  /* ERRORS */
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    return FALSE;
  }
no_uri:
  {
    GST_ERROR ("client %p: no uri", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
no_mount_points:
  {
    GST_ERROR ("client %p: no mount points configured", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_path:
  {
    GST_ERROR ("client %p: can't find path for url", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_media:
  {
    GST_ERROR ("client %p: no media", client);
    g_free (path);
    /* error reply is already sent */
    return FALSE;
  }
unsupported_mode:
  {
    GST_ERROR ("client %p: media does not support DESCRIBE", client);
    send_generic_error_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
    g_free (path);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    return FALSE;
  }
no_sdp:
  {
    GST_ERROR ("client %p: can't create SDP", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    g_free (path);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    return FALSE;
  }
}

static gboolean
handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx, GstRTSPMedia * media,
    GstSDPMessage * sdp)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPThread *thread;

  /* create an SDP for the media object */
  if (!gst_rtsp_media_handle_sdp (media, sdp))
    goto unhandled_sdp;

  thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
      GST_RTSP_THREAD_TYPE_MEDIA, ctx);
  if (thread == NULL)
    goto no_thread;

  /* prepare the media */
  if (!gst_rtsp_media_prepare (media, thread))
    goto no_prepare;

  return TRUE;

  /* ERRORS */
unhandled_sdp:
  {
    GST_ERROR ("client %p: could not handle SDP", client);
    return FALSE;
  }
no_thread:
  {
    GST_ERROR ("client %p: can't create thread", client);
    return FALSE;
  }
no_prepare:
  {
    GST_ERROR ("client %p: can't prepare media", client);
    return FALSE;
  }
}

static gboolean
handle_announce_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPClientClass *klass;
  GstSDPResult sres;
  GstSDPMessage *sdp;
  GstRTSPMedia *media;
  gchar *path, *cont = NULL;
  guint8 *data;
  guint size;
  GstRTSPStatusCode sig_result;
  guint i, n_streams;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);

  if (!ctx->uri)
    goto no_uri;

  if (!priv->mount_points)
    goto no_mount_points;

  /* check if reply is SDP */
  gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_CONTENT_TYPE, &cont,
      0);
  /* could not be set but since the request returned OK, we assume it
   * was SDP, else check it. */
  if (cont) {
    if (g_ascii_strcasecmp (cont, "application/sdp") != 0)
      goto wrong_content_type;
  }

  /* get message body and parse as SDP */
  gst_rtsp_message_get_body (ctx->request, &data, &size);
  if (data == NULL || size == 0)
    goto no_message;

  GST_DEBUG ("client %p: parse SDP...", client);
  gst_sdp_message_new (&sdp);
  sres = gst_sdp_message_parse_buffer (data, size, sdp);
  if (sres != GST_SDP_OK)
    goto sdp_parse_failed;

  if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
    goto no_path;

  /* find the media object for the uri */
  if (!(media = find_media (client, ctx, path, NULL)))
    goto no_media;

  ctx->media = media;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST],
      0, ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  if (!(gst_rtsp_media_get_transport_mode (media) &
          GST_RTSP_TRANSPORT_MODE_RECORD))
    goto unsupported_mode;

  /* Tell client subclass about the media */
  if (!klass->handle_sdp (client, ctx, media, sdp))
    goto unhandled_sdp;

  gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
      gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);

  n_streams = gst_rtsp_media_n_streams (media);
  for (i = 0; i < n_streams; i++) {
    GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
    gchar *uri, *location, *keymgmt;

    uri = gst_rtsp_url_get_request_uri (ctx->uri);
    location = g_strdup_printf ("%s/stream=%d", uri, i);
    keymgmt = stream_make_keymgmt (client, location, stream);

    if (keymgmt)
      gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_KEYMGMT,
          keymgmt);

    g_free (location);
    g_free (uri);
  }

  /* we suspend after the announce */
  gst_rtsp_media_suspend (media);

  send_message (client, ctx, ctx->response, FALSE);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST],
      0, ctx);

  gst_sdp_message_free (sdp);
  g_free (path);
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  return TRUE;

no_uri:
  {
    GST_ERROR ("client %p: no uri", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
no_mount_points:
  {
    GST_ERROR ("client %p: no mount points configured", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_path:
  {
    GST_ERROR ("client %p: can't find path for url", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    gst_sdp_message_free (sdp);
    return FALSE;
  }
wrong_content_type:
  {
    GST_ERROR ("client %p: unknown content type", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
no_message:
  {
    GST_ERROR ("client %p: can't find SDP message", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
sdp_parse_failed:
  {
    GST_ERROR ("client %p: failed to parse SDP message", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    gst_sdp_message_free (sdp);
    return FALSE;
  }
no_media:
  {
    GST_ERROR ("client %p: no media", client);
    g_free (path);
    /* error reply is already sent */
    gst_sdp_message_free (sdp);
    return FALSE;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_sdp_message_free (sdp);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    return FALSE;
  }
unsupported_mode:
  {
    GST_ERROR ("client %p: media does not support ANNOUNCE", client);
    send_generic_error_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
    g_free (path);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    gst_sdp_message_free (sdp);
    return FALSE;
  }
unhandled_sdp:
  {
    GST_ERROR ("client %p: can't handle SDP", client);
    send_generic_error_response (client, GST_RTSP_STS_UNSUPPORTED_MEDIA_TYPE,
        ctx);
    g_free (path);
    gst_rtsp_media_unlock (media);
    g_object_unref (media);
    gst_sdp_message_free (sdp);
    return FALSE;
  }
}

static gboolean
handle_record_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPSession *session;
  GstRTSPClientClass *klass;
  GstRTSPSessionMedia *sessmedia;
  GstRTSPMedia *media;
  GstRTSPUrl *uri;
  GstRTSPState rtspstate;
  gchar *path;
  gint matched;
  GstRTSPStatusCode sig_result;
  GPtrArray *transports;

  if (!(session = ctx->session))
    goto no_session;

  if (!(uri = ctx->uri))
    goto no_uri;

  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  path = klass->make_path_from_uri (client, uri);

  /* get a handle to the configuration of the media in the session */
  sessmedia = gst_rtsp_session_get_media (session, path, &matched);
  if (!sessmedia)
    goto not_found;

  if (path[matched] != '\0')
    goto no_aggregate;

  g_free (path);

  ctx->sessmedia = sessmedia;
  ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST], 0,
      ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  if (!(gst_rtsp_media_get_transport_mode (media) &
          GST_RTSP_TRANSPORT_MODE_RECORD))
    goto unsupported_mode;

  /* the session state must be playing or ready */
  rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
  if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
    goto invalid_state;

  /* update the pipeline */
  transports = gst_rtsp_session_media_get_transports (sessmedia);
  if (!gst_rtsp_media_complete_pipeline (media, transports)) {
    g_ptr_array_unref (transports);
    goto pipeline_error;
  }
  g_ptr_array_unref (transports);

  /* in record we first unsuspend, media could be suspended from SDP or PAUSED */
  if (!gst_rtsp_media_unsuspend (media))
    goto unsuspend_failed;

  gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
      gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);

  send_message (client, ctx, ctx->response, FALSE);

  /* start playing after sending the response */
  gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);

  gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST], 0,
      ctx);

  return TRUE;

  /* ERRORS */
no_session:
  {
    GST_ERROR ("client %p: no session", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    return FALSE;
  }
no_uri:
  {
    GST_ERROR ("client %p: no uri supplied", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    return FALSE;
  }
not_found:
  {
    GST_ERROR ("client %p: media not found", client);
    send_generic_error_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
    return FALSE;
  }
no_aggregate:
  {
    GST_ERROR ("client %p: no aggregate path %s", client, path);
    send_generic_error_response (client,
        GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
    g_free (path);
    return FALSE;
  }
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    return FALSE;
  }
unsupported_mode:
  {
    GST_ERROR ("client %p: media does not support RECORD", client);
    send_generic_error_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
    return FALSE;
  }
invalid_state:
  {
    GST_ERROR ("client %p: not PLAYING or READY", client);
    send_generic_error_response (client,
        GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, ctx);
    return FALSE;
  }
pipeline_error:
  {
    GST_ERROR ("client %p: failed to configure the pipeline", client);
    send_generic_error_response (client,
        GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, ctx);
    return FALSE;
  }
unsuspend_failed:
  {
    GST_ERROR ("client %p: unsuspend failed", client);
    send_generic_error_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
    return FALSE;
  }
}

static gboolean
handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPVersion version)
{
  GstRTSPMethod options;
  gchar *str;
  GstRTSPStatusCode sig_result;

  options = GST_RTSP_DESCRIBE |
      GST_RTSP_OPTIONS |
      GST_RTSP_PAUSE |
      GST_RTSP_PLAY |
      GST_RTSP_SETUP |
      GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN;

  if (version < GST_RTSP_VERSION_2_0) {
    options |= GST_RTSP_RECORD;
    options |= GST_RTSP_ANNOUNCE;
  }

  str = gst_rtsp_options_as_text (options);

  gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
      gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);

  gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PUBLIC, str);
  g_free (str);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST], 0,
      ctx, &sig_result);
  if (sig_result != GST_RTSP_STS_OK) {
    goto sig_failed;
  }

  send_message (client, ctx, ctx->response, FALSE);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST],
      0, ctx);

  return TRUE;

/* ERRORS */
sig_failed:
  {
    GST_ERROR ("client %p: pre signal returned error: %s", client,
        gst_rtsp_status_as_text (sig_result));
    send_generic_error_response (client, sig_result, ctx);
    gst_rtsp_message_free (ctx->response);
    return FALSE;
  }
}

/* remove duplicate and trailing '/' */
static void
sanitize_uri (GstRTSPUrl * uri)
{
  gint i, len;
  gchar *s, *d;
  gboolean have_slash, prev_slash;

  s = d = uri->abspath;
  len = strlen (uri->abspath);

  prev_slash = FALSE;

  for (i = 0; i < len; i++) {
    have_slash = s[i] == '/';
    *d = s[i];
    if (!have_slash || !prev_slash)
      d++;
    prev_slash = have_slash;
  }
  len = d - uri->abspath;
  /* don't remove the first slash if that's the only thing left */
  if (len > 1 && *(d - 1) == '/')
    d--;
  *d = '\0';
}

/* is called when the session is removed from its session pool. */
static void
client_session_removed (GstRTSPSessionPool * pool, GstRTSPSession * session,
    GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  GSource *timer_src;

  GST_INFO ("client %p: session %p removed", client, session);

  g_mutex_lock (&priv->lock);
  client_unwatch_session (client, session, NULL);

  if (!priv->sessions && priv->rtsp_ctrl_timeout == NULL) {
    if (priv->post_session_timeout > 0) {
      GWeakRef *client_weak_ref = g_new (GWeakRef, 1);
      timer_src = g_timeout_source_new_seconds (RTSP_CTRL_CB_INTERVAL);

      g_weak_ref_init (client_weak_ref, client);
      g_source_set_callback (timer_src, rtsp_ctrl_timeout_cb, client_weak_ref,
          rtsp_ctrl_timeout_destroy_notify);
      priv->rtsp_ctrl_timeout_cnt = 0;
      g_source_attach (timer_src, priv->watch_context);
      priv->rtsp_ctrl_timeout = timer_src;
      GST_DEBUG ("rtsp control setting up connection timeout %p.",
          priv->rtsp_ctrl_timeout);
      g_mutex_unlock (&priv->lock);
    } else if (priv->post_session_timeout == 0) {
      g_mutex_unlock (&priv->lock);
      gst_rtsp_client_close (client);
    } else {
      g_mutex_unlock (&priv->lock);
    }
  } else {
    g_mutex_unlock (&priv->lock);
  }
}

/* Check for Require headers. Returns TRUE if there are no Require headers,
 * otherwise lets the application decide which headers are supported.
 * By default all headers are unsupported.
 * If there are unsupported options, FALSE will be returned together with
 * a newly-allocated string of (comma-separated) unsupported options in
 * the unsupported_reqs variable.
 *
 * There may be multiple Require headers, but we must send one single
 * Unsupported header with all the unsupported options as response. If
 * an incoming Require header contained a comma-separated list of options
 * GstRtspConnection will already have split that list up into multiple
 * headers.
 */
static gboolean
check_request_requirements (GstRTSPContext * ctx, gchar ** unsupported_reqs)
{
  GstRTSPResult res;
  GPtrArray *arr = NULL;
  GstRTSPMessage *msg = ctx->request;
  gchar *reqs = NULL;
  gint i;
  gchar *sig_result = NULL;
  gboolean result = TRUE;

  i = 0;
  do {
    res = gst_rtsp_message_get_header (msg, GST_RTSP_HDR_REQUIRE, &reqs, i++);

    if (res == GST_RTSP_ENOTIMPL)
      break;

    if (arr == NULL)
      arr = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);

    g_ptr_array_add (arr, g_strdup (reqs));
  }
  while (TRUE);

  /* if we don't have any Require headers at all, all is fine */
  if (i == 1)
    return TRUE;

  /* otherwise we've now processed at all the Require headers */
  g_ptr_array_add (arr, NULL);

  g_signal_emit (ctx->client,
      gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS], 0, ctx,
      (gchar **) arr->pdata, &sig_result);

  if (sig_result == NULL) {
    /* no supported options, just report all of the required ones as
     * unsupported */
    *unsupported_reqs = g_strjoinv (", ", (gchar **) arr->pdata);
    result = FALSE;
    goto done;
  }

  if (strlen (sig_result) == 0)
    g_free (sig_result);
  else {
    *unsupported_reqs = sig_result;
    result = FALSE;
  }

done:
  g_ptr_array_unref (arr);
  return result;
}

static void
handle_request (GstRTSPClient * client, GstRTSPMessage * request)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPMethod method;
  const gchar *uristr;
  GstRTSPUrl *uri = NULL;
  GstRTSPVersion version;
  GstRTSPResult res;
  GstRTSPSession *session = NULL;
  GstRTSPContext sctx = { NULL }, *ctx;
  GstRTSPMessage response = { 0 };
  gchar *unsupported_reqs = NULL;
  gchar *sessid = NULL, *pipelined_request_id = NULL;

  if (!(ctx = gst_rtsp_context_get_current ())) {
    ctx = &sctx;
    ctx->auth = priv->auth;
    gst_rtsp_context_push_current (ctx);
  }

  ctx->conn = priv->connection;
  ctx->client = client;
  ctx->request = request;
  ctx->response = &response;

  if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
    gst_rtsp_message_dump (request);
  }

  gst_rtsp_message_parse_request (request, &method, &uristr, &version);

  GST_INFO ("client %p: received a request %s %s %s", client,
      gst_rtsp_method_as_text (method), uristr,
      gst_rtsp_version_as_text (version));

  /* we can only handle 1.0 requests */
  if (version != GST_RTSP_VERSION_1_0 && version != GST_RTSP_VERSION_2_0)
    goto not_supported;

  ctx->method = method;

  /* we always try to parse the url first */
  if (strcmp (uristr, "*") == 0) {
    /* special case where we have * as uri, keep uri = NULL */
  } else if (gst_rtsp_url_parse (uristr, &uri) != GST_RTSP_OK) {
    /* check if the uristr is an absolute path <=> scheme and host information
     * is missing */
    gchar *scheme;

    scheme = g_uri_parse_scheme (uristr);
    if (scheme == NULL && g_str_has_prefix (uristr, "/")) {
      gchar *absolute_uristr = NULL;

      GST_WARNING_OBJECT (client, "request doesn't contain absolute url");
      if (priv->server_ip == NULL) {
        GST_WARNING_OBJECT (client, "host information missing");
        goto bad_request;
      }

      absolute_uristr =
          g_strdup_printf ("rtsp://%s%s", priv->server_ip, uristr);

      GST_DEBUG_OBJECT (client, "absolute url: %s", absolute_uristr);
      if (gst_rtsp_url_parse (absolute_uristr, &uri) != GST_RTSP_OK) {
        g_free (absolute_uristr);
        goto bad_request;
      }
      g_free (absolute_uristr);
    } else {
      g_free (scheme);
      goto bad_request;
    }
  }

  /* get the session if there is any */
  res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_PIPELINED_REQUESTS,
      &pipelined_request_id, 0);
  if (res == GST_RTSP_OK) {
    sessid = g_hash_table_lookup (client->priv->pipelined_requests,
        pipelined_request_id);

    if (!sessid)
      res = GST_RTSP_ERROR;
  }

  if (res != GST_RTSP_OK)
    res =
        gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);

  if (res == GST_RTSP_OK) {
    if (priv->session_pool == NULL)
      goto no_pool;

    /* we had a session in the request, find it again */
    if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid)))
      goto session_not_found;

    /* we add the session to the client list of watched sessions. When a session
     * disappears because it times out, we will be notified. If all sessions are
     * gone, we will close the connection */
    client_watch_session (client, session);
  }

  /* sanitize the uri */
  if (uri)
    sanitize_uri (uri);
  ctx->uri = uri;
  ctx->session = session;

  if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_URL))
    goto not_authorized;

  /* handle any 'Require' headers */
  if (!check_request_requirements (ctx, &unsupported_reqs))
    goto unsupported_requirement;

  /* now see what is asked and dispatch to a dedicated handler */
  switch (method) {
    case GST_RTSP_OPTIONS:
      priv->version = version;
      handle_options_request (client, ctx, version);
      break;
    case GST_RTSP_DESCRIBE:
      handle_describe_request (client, ctx);
      break;
    case GST_RTSP_SETUP:
      handle_setup_request (client, ctx);
      break;
    case GST_RTSP_PLAY:
      handle_play_request (client, ctx);
      break;
    case GST_RTSP_PAUSE:
      handle_pause_request (client, ctx);
      break;
    case GST_RTSP_TEARDOWN:
      handle_teardown_request (client, ctx);
      break;
    case GST_RTSP_SET_PARAMETER:
      handle_set_param_request (client, ctx);
      break;
    case GST_RTSP_GET_PARAMETER:
      handle_get_param_request (client, ctx);
      break;
    case GST_RTSP_ANNOUNCE:
      if (version >= GST_RTSP_VERSION_2_0)
        goto invalid_command_for_version;
      handle_announce_request (client, ctx);
      break;
    case GST_RTSP_RECORD:
      if (version >= GST_RTSP_VERSION_2_0)
        goto invalid_command_for_version;
      handle_record_request (client, ctx);
      break;
    case GST_RTSP_REDIRECT:
      goto not_implemented;
    case GST_RTSP_INVALID:
    default:
      goto bad_request;
  }

done:
  if (ctx == &sctx)
    gst_rtsp_context_pop_current (ctx);
  if (session)
    g_object_unref (session);
  if (uri)
    gst_rtsp_url_free (uri);
  return;

  /* ERRORS */
not_supported:
  {
    GST_ERROR ("client %p: version %d not supported", client, version);
    send_generic_error_response (client,
        GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED, ctx);
    goto done;
  }
invalid_command_for_version:
  {
    GST_ERROR ("client %p: invalid command for version", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    goto done;
  }
bad_request:
  {
    GST_ERROR ("client %p: bad request", client);
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    goto done;
  }
no_pool:
  {
    GST_ERROR ("client %p: no pool configured", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    goto done;
  }
session_not_found:
  {
    GST_ERROR ("client %p: session not found", client);
    send_generic_error_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
    goto done;
  }
not_authorized:
  {
    GST_ERROR ("client %p: not allowed", client);
    /* error reply is already sent */
    goto done;
  }
unsupported_requirement:
  {
    GST_ERROR ("client %p: Required option is not supported (%s)", client,
        unsupported_reqs);
    send_option_not_supported_response (client, ctx, unsupported_reqs);
    g_free (unsupported_reqs);
    goto done;
  }
not_implemented:
  {
    GST_ERROR ("client %p: method %d not implemented", client, method);
    send_generic_error_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
    goto done;
  }
}


static void
handle_response (GstRTSPClient * client, GstRTSPMessage * response)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPResult res;
  GstRTSPSession *session = NULL;
  GstRTSPContext sctx = { NULL }, *ctx;
  gchar *sessid;

  if (!(ctx = gst_rtsp_context_get_current ())) {
    ctx = &sctx;
    ctx->auth = priv->auth;
    gst_rtsp_context_push_current (ctx);
  }

  ctx->conn = priv->connection;
  ctx->client = client;
  ctx->request = NULL;
  ctx->uri = NULL;
  ctx->method = GST_RTSP_INVALID;
  ctx->response = response;

  if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
    gst_rtsp_message_dump (response);
  }

  GST_INFO ("client %p: received a response", client);

  /* get the session if there is any */
  res =
      gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &sessid, 0);
  if (res == GST_RTSP_OK) {
    if (priv->session_pool == NULL)
      goto no_pool;

    /* we had a session in the request, find it again */
    if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid)))
      goto session_not_found;

    /* we add the session to the client list of watched sessions. When a session
     * disappears because it times out, we will be notified. If all sessions are
     * gone, we will close the connection */
    client_watch_session (client, session);
  }

  ctx->session = session;

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE],
      0, ctx);

done:
  if (ctx == &sctx)
    gst_rtsp_context_pop_current (ctx);
  if (session)
    g_object_unref (session);
  return;

no_pool:
  {
    GST_ERROR ("client %p: no pool configured", client);
    goto done;
  }
session_not_found:
  {
    GST_ERROR ("client %p: session not found", client);
    goto done;
  }
}

static void
handle_data (GstRTSPClient * client, GstRTSPMessage * message)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPResult res;
  guint8 channel;
  guint8 *data;
  guint size;
  GstBuffer *buffer;
  GstRTSPStreamTransport *trans;

  /* find the stream for this message */
  res = gst_rtsp_message_parse_data (message, &channel);
  if (res != GST_RTSP_OK)
    return;

  gst_rtsp_message_get_body (message, &data, &size);
  if (size < 2)
    goto invalid_length;

  gst_rtsp_message_steal_body (message, &data, &size);

  /* Strip trailing \0 (which GstRTSPConnection adds) */
  --size;

  buffer = gst_buffer_new_wrapped (data, size);

  trans =
      g_hash_table_lookup (priv->transports, GINT_TO_POINTER ((gint) channel));
  if (trans) {
    GSocketAddress *addr;

    /* Only create the socket address once for the transport, we don't really
     * want to do that for every single packet.
     *
     * The netaddress meta is later used by the RTP stack to know where
     * packets came from and allows us to match it again to a stream transport
     *
     * In theory we could use the remote socket address of the RTSP connection
     * here, but this would fail with a custom configure_client_transport()
     * implementation.
     */
    if (!(addr =
            g_object_get_data (G_OBJECT (trans), "rtsp-client.remote-addr"))) {
      const GstRTSPTransport *tr;
      GInetAddress *iaddr;

      tr = gst_rtsp_stream_transport_get_transport (trans);
      iaddr = g_inet_address_new_from_string (tr->destination);
      if (iaddr) {
        addr = g_inet_socket_address_new (iaddr, tr->client_port.min);
        g_object_unref (iaddr);
        g_object_set_data_full (G_OBJECT (trans), "rtsp-client.remote-addr",
            addr, (GDestroyNotify) g_object_unref);
      }
    }

    if (addr) {
      gst_buffer_add_net_address_meta (buffer, addr);
    }

    /* dispatch to the stream based on the channel number */
    GST_LOG_OBJECT (client, "%u bytes of data on channel %u", size, channel);
    gst_rtsp_stream_transport_recv_data (trans, channel, buffer);
  } else {
    GST_DEBUG_OBJECT (client, "received %u bytes of data for "
        "unknown channel %u", size, channel);
    gst_buffer_unref (buffer);
  }

  return;

/* ERRORS */
invalid_length:
  {
    GST_DEBUG ("client %p: Short message received, ignoring", client);
    return;
  }
}

/**
 * gst_rtsp_client_set_session_pool:
 * @client: a #GstRTSPClient
 * @pool: (transfer none) (nullable): a #GstRTSPSessionPool
 *
 * Set @pool as the sessionpool for @client which it will use to find
 * or allocate sessions. the sessionpool is usually inherited from the server
 * that created the client but can be overridden later.
 */
void
gst_rtsp_client_set_session_pool (GstRTSPClient * client,
    GstRTSPSessionPool * pool)
{
  GstRTSPSessionPool *old;
  GstRTSPClientPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  if (pool)
    g_object_ref (pool);

  g_mutex_lock (&priv->lock);
  old = priv->session_pool;
  priv->session_pool = pool;

  if (priv->session_removed_id) {
    g_signal_handler_disconnect (old, priv->session_removed_id);
    priv->session_removed_id = 0;
  }
  g_mutex_unlock (&priv->lock);

  /* FIXME, should remove all sessions from the old pool for this client */
  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_client_get_session_pool:
 * @client: a #GstRTSPClient
 *
 * Get the #GstRTSPSessionPool object that @client uses to manage its sessions.
 *
 * Returns: (transfer full) (nullable): a #GstRTSPSessionPool, unref after usage.
 */
GstRTSPSessionPool *
gst_rtsp_client_get_session_pool (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv;
  GstRTSPSessionPool *result;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  priv = client->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->session_pool))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_client_set_mount_points:
 * @client: a #GstRTSPClient
 * @mounts: (transfer none) (nullable): a #GstRTSPMountPoints
 *
 * Set @mounts as the mount points for @client which it will use to map urls
 * to media streams. These mount points are usually inherited from the server that
 * created the client but can be overriden later.
 */
void
gst_rtsp_client_set_mount_points (GstRTSPClient * client,
    GstRTSPMountPoints * mounts)
{
  GstRTSPClientPrivate *priv;
  GstRTSPMountPoints *old;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  if (mounts)
    g_object_ref (mounts);

  g_mutex_lock (&priv->lock);
  old = priv->mount_points;
  priv->mount_points = mounts;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_client_get_mount_points:
 * @client: a #GstRTSPClient
 *
 * Get the #GstRTSPMountPoints object that @client uses to manage its sessions.
 *
 * Returns: (transfer full) (nullable): a #GstRTSPMountPoints, unref after usage.
 */
GstRTSPMountPoints *
gst_rtsp_client_get_mount_points (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv;
  GstRTSPMountPoints *result;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  priv = client->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->mount_points))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_client_set_content_length_limit:
 * @client: a #GstRTSPClient
 * @limit: Content-Length limit
 *
 * Configure @client to use the specified Content-Length limit.
 *
 * Define an appropriate request size limit and reject requests exceeding the
 * limit with response status 413 Request Entity Too Large
 *
 * Since: 1.18
 */
void
gst_rtsp_client_set_content_length_limit (GstRTSPClient * client, guint limit)
{
  GstRTSPClientPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;
  g_mutex_lock (&priv->lock);
  priv->content_length_limit = limit;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_client_get_content_length_limit:
 * @client: a #GstRTSPClient
 *
 * Get the Content-Length limit of @client.
 *
 * Returns: the Content-Length limit.
 *
 * Since: 1.18
 */
guint
gst_rtsp_client_get_content_length_limit (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv;
  glong content_length_limit;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), -1);
  priv = client->priv;

  g_mutex_lock (&priv->lock);
  content_length_limit = priv->content_length_limit;
  g_mutex_unlock (&priv->lock);

  return content_length_limit;
}

/**
 * gst_rtsp_client_set_auth:
 * @client: a #GstRTSPClient
 * @auth: (transfer none) (nullable): a #GstRTSPAuth
 *
 * configure @auth to be used as the authentication manager of @client.
 */
void
gst_rtsp_client_set_auth (GstRTSPClient * client, GstRTSPAuth * auth)
{
  GstRTSPClientPrivate *priv;
  GstRTSPAuth *old;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  if (auth)
    g_object_ref (auth);

  g_mutex_lock (&priv->lock);
  old = priv->auth;
  priv->auth = auth;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}


/**
 * gst_rtsp_client_get_auth:
 * @client: a #GstRTSPClient
 *
 * Get the #GstRTSPAuth used as the authentication manager of @client.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAuth of @client.
 * g_object_unref() after usage.
 */
GstRTSPAuth *
gst_rtsp_client_get_auth (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv;
  GstRTSPAuth *result;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  priv = client->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->auth))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_client_set_thread_pool:
 * @client: a #GstRTSPClient
 * @pool: (transfer none) (nullable): a #GstRTSPThreadPool
 *
 * configure @pool to be used as the thread pool of @client.
 */
void
gst_rtsp_client_set_thread_pool (GstRTSPClient * client,
    GstRTSPThreadPool * pool)
{
  GstRTSPClientPrivate *priv;
  GstRTSPThreadPool *old;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  if (pool)
    g_object_ref (pool);

  g_mutex_lock (&priv->lock);
  old = priv->thread_pool;
  priv->thread_pool = pool;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_client_get_thread_pool:
 * @client: a #GstRTSPClient
 *
 * Get the #GstRTSPThreadPool used as the thread pool of @client.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPThreadPool of @client. g_object_unref() after
 * usage.
 */
GstRTSPThreadPool *
gst_rtsp_client_get_thread_pool (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv;
  GstRTSPThreadPool *result;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  priv = client->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->thread_pool))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_client_set_connection:
 * @client: a #GstRTSPClient
 * @conn: (transfer full): a #GstRTSPConnection
 *
 * Set the #GstRTSPConnection of @client. This function takes ownership of
 * @conn.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_client_set_connection (GstRTSPClient * client,
    GstRTSPConnection * conn)
{
  GstRTSPClientPrivate *priv;
  GSocket *read_socket;
  GSocketAddress *address;
  GstRTSPUrl *url;
  GError *error = NULL;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE);
  g_return_val_if_fail (conn != NULL, FALSE);

  priv = client->priv;

  gst_rtsp_connection_set_content_length_limit (conn,
      priv->content_length_limit);
  read_socket = gst_rtsp_connection_get_read_socket (conn);

  if (!(address = g_socket_get_local_address (read_socket, &error)))
    goto no_address;

  g_free (priv->server_ip);
  /* keep the original ip that the client connected to */
  if (G_IS_INET_SOCKET_ADDRESS (address)) {
    GInetAddress *iaddr;

    iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address));

    /* socket might be ipv6 but adress still ipv4 */
    priv->is_ipv6 = g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV6;
    priv->server_ip = g_inet_address_to_string (iaddr);
    g_object_unref (address);
  } else {
    priv->is_ipv6 = g_socket_get_family (read_socket) == G_SOCKET_FAMILY_IPV6;
    priv->server_ip = g_strdup ("unknown");
  }

  GST_INFO ("client %p connected to server ip %s, ipv6 = %d", client,
      priv->server_ip, priv->is_ipv6);

  url = gst_rtsp_connection_get_url (conn);
  GST_INFO ("added new client %p ip %s:%d", client, url->host, url->port);

  priv->connection = conn;

  return TRUE;

  /* ERRORS */
no_address:
  {
    GST_ERROR ("could not get local address %s", error->message);
    g_error_free (error);
    return FALSE;
  }
}

/**
 * gst_rtsp_client_get_connection:
 * @client: a #GstRTSPClient
 *
 * Get the #GstRTSPConnection of @client.
 *
 * Returns: (transfer none) (nullable): the #GstRTSPConnection of @client.
 * The connection object returned remains valid until the client is freed.
 */
GstRTSPConnection *
gst_rtsp_client_get_connection (GstRTSPClient * client)
{
  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  return client->priv->connection;
}

/**
 * gst_rtsp_client_set_send_func:
 * @client: a #GstRTSPClient
 * @func: (scope notified) (closure user_data): a #GstRTSPClientSendFunc
 * @user_data: user data passed to @func
 * @notify: (allow-none): called when @user_data is no longer in use
 *
 * Set @func as the callback that will be called when a new message needs to be
 * sent to the client. @user_data is passed to @func and @notify is called when
 * @user_data is no longer in use.
 *
 * By default, the client will send the messages on the #GstRTSPConnection that
 * was configured with gst_rtsp_client_attach() was called.
 *
 * It is only allowed to set either a `send_func` or a `send_messages_func`
 * but not both at the same time.
 */
void
gst_rtsp_client_set_send_func (GstRTSPClient * client,
    GstRTSPClientSendFunc func, gpointer user_data, GDestroyNotify notify)
{
  GstRTSPClientPrivate *priv;
  GDestroyNotify old_notify;
  gpointer old_data;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  g_mutex_lock (&priv->send_lock);
  g_assert (func == NULL || priv->send_messages_func == NULL);
  priv->send_func = func;
  old_notify = priv->send_notify;
  old_data = priv->send_data;
  priv->send_notify = notify;
  priv->send_data = user_data;
  g_mutex_unlock (&priv->send_lock);

  if (old_notify)
    old_notify (old_data);
}

/**
 * gst_rtsp_client_set_send_messages_func:
 * @client: a #GstRTSPClient
 * @func: (scope notified) (closure user_data): a #GstRTSPClientSendMessagesFunc
 * @user_data: user data passed to @func
 * @notify: (allow-none): called when @user_data is no longer in use
 *
 * Set @func as the callback that will be called when new messages needs to be
 * sent to the client. @user_data is passed to @func and @notify is called when
 * @user_data is no longer in use.
 *
 * By default, the client will send the messages on the #GstRTSPConnection that
 * was configured with gst_rtsp_client_attach() was called.
 *
 * It is only allowed to set either a `send_func` or a `send_messages_func`
 * but not both at the same time.
 *
 * Since: 1.16
 */
void
gst_rtsp_client_set_send_messages_func (GstRTSPClient * client,
    GstRTSPClientSendMessagesFunc func, gpointer user_data,
    GDestroyNotify notify)
{
  GstRTSPClientPrivate *priv;
  GDestroyNotify old_notify;
  gpointer old_data;

  g_return_if_fail (GST_IS_RTSP_CLIENT (client));

  priv = client->priv;

  g_mutex_lock (&priv->send_lock);
  g_assert (func == NULL || priv->send_func == NULL);
  priv->send_messages_func = func;
  old_notify = priv->send_messages_notify;
  old_data = priv->send_messages_data;
  priv->send_messages_notify = notify;
  priv->send_messages_data = user_data;
  g_mutex_unlock (&priv->send_lock);

  if (old_notify)
    old_notify (old_data);
}

/**
 * gst_rtsp_client_handle_message:
 * @client: a #GstRTSPClient
 * @message: (transfer none): an #GstRTSPMessage
 *
 * Let the client handle @message.
 *
 * Returns: a #GstRTSPResult.
 */
GstRTSPResult
gst_rtsp_client_handle_message (GstRTSPClient * client,
    GstRTSPMessage * message)
{
  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL);
  g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);

  switch (message->type) {
    case GST_RTSP_MESSAGE_REQUEST:
      handle_request (client, message);
      break;
    case GST_RTSP_MESSAGE_RESPONSE:
      handle_response (client, message);
      break;
    case GST_RTSP_MESSAGE_DATA:
      handle_data (client, message);
      break;
    default:
      break;
  }
  return GST_RTSP_OK;
}

/**
 * gst_rtsp_client_send_message:
 * @client: a #GstRTSPClient
 * @session: (allow-none) (transfer none): a #GstRTSPSession to send
 *   the message to or %NULL
 * @message: (transfer none): The #GstRTSPMessage to send
 *
 * Send a message message to the remote end. @message must be a
 * #GST_RTSP_MESSAGE_REQUEST or a #GST_RTSP_MESSAGE_RESPONSE.
 */
GstRTSPResult
gst_rtsp_client_send_message (GstRTSPClient * client, GstRTSPSession * session,
    GstRTSPMessage * message)
{
  GstRTSPContext sctx = { NULL }
  , *ctx;
  GstRTSPClientPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL);
  g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (message->type == GST_RTSP_MESSAGE_REQUEST ||
      message->type == GST_RTSP_MESSAGE_RESPONSE, GST_RTSP_EINVAL);

  priv = client->priv;

  if (!(ctx = gst_rtsp_context_get_current ())) {
    ctx = &sctx;
    ctx->auth = priv->auth;
    gst_rtsp_context_push_current (ctx);
  }

  ctx->conn = priv->connection;
  ctx->client = client;
  ctx->session = session;

  send_message (client, ctx, message, FALSE);

  if (ctx == &sctx)
    gst_rtsp_context_pop_current (ctx);

  return GST_RTSP_OK;
}

/**
 * gst_rtsp_client_get_stream_transport:
 *
 * This is useful when providing a send function through
 * gst_rtsp_client_set_send_func() when doing RTSP over TCP:
 * the send function must call gst_rtsp_stream_transport_message_sent ()
 * on the appropriate transport when data has been received for streaming
 * to continue.
 *
 * Returns: (transfer none) (nullable): the #GstRTSPStreamTransport associated with @channel.
 *
 * Since: 1.18
 */
GstRTSPStreamTransport *
gst_rtsp_client_get_stream_transport (GstRTSPClient * self, guint8 channel)
{
  return g_hash_table_lookup (self->priv->transports,
      GINT_TO_POINTER ((gint) channel));
}

static gboolean
do_send_messages (GstRTSPClient * client, GstRTSPMessage * messages,
    guint n_messages, gboolean close, gpointer user_data)
{
  GstRTSPClientPrivate *priv = client->priv;
  guint id = 0;
  GstRTSPResult ret;
  guint i;

  /* send the message */
  if (close)
    GST_INFO ("client %p: sending close message", client);

  ret = gst_rtsp_watch_send_messages (priv->watch, messages, n_messages, &id);
  if (ret != GST_RTSP_OK)
    goto error;

  for (i = 0; i < n_messages; i++) {
    if (gst_rtsp_message_get_type (&messages[i]) == GST_RTSP_MESSAGE_DATA) {
      guint8 channel = 0;
      GstRTSPResult r;

      /* We assume that all data messages in the list are for the
       * same channel */
      r = gst_rtsp_message_parse_data (&messages[i], &channel);
      if (r != GST_RTSP_OK) {
        ret = r;
        goto error;
      }

      /* check if the message has been queued for transmission in watch */
      if (id) {
        /* store the seq number so we can wait until it has been sent */
        GST_DEBUG_OBJECT (client, "wait for message %d, channel %d", id,
            channel);
        set_data_seq (client, channel, id);
      } else {
        GstRTSPStreamTransport *trans;

        trans =
            g_hash_table_lookup (priv->transports,
            GINT_TO_POINTER ((gint) channel));
        if (trans) {
          GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
          g_mutex_unlock (&priv->send_lock);
          gst_rtsp_stream_transport_message_sent (trans);
          g_mutex_lock (&priv->send_lock);
        }
      }
      break;
    }
  }

  return ret == GST_RTSP_OK;

  /* ERRORS */
error:
  {
    GST_DEBUG_OBJECT (client, "got error %d", ret);
    return FALSE;
  }
}

static GstRTSPResult
message_received (GstRTSPWatch * watch, GstRTSPMessage * message,
    gpointer user_data)
{
  return gst_rtsp_client_handle_message (GST_RTSP_CLIENT (user_data), message);
}

static GstRTSPResult
message_sent (GstRTSPWatch * watch, guint cseq, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPStreamTransport *trans = NULL;
  guint8 channel = 0;

  g_mutex_lock (&priv->send_lock);

  if (get_data_channel (client, cseq, &channel)) {
    trans = g_hash_table_lookup (priv->transports, GINT_TO_POINTER (channel));
    set_data_seq (client, channel, 0);
  }
  g_mutex_unlock (&priv->send_lock);

  if (trans) {
    GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
    gst_rtsp_stream_transport_message_sent (trans);
  }

  return GST_RTSP_OK;
}

static GstRTSPResult
closed (GstRTSPWatch * watch, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  GstRTSPClientPrivate *priv = client->priv;
  const gchar *tunnelid;

  GST_INFO ("client %p: connection closed", client);

  g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_CLOSED], 0, NULL);
  if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) {
    g_mutex_lock (&tunnels_lock);
    /* remove from tunnelids */
    g_hash_table_remove (tunnels, tunnelid);
    g_mutex_unlock (&tunnels_lock);
  }

  gst_rtsp_watch_set_flushing (watch, TRUE);
  g_mutex_lock (&priv->watch_lock);
  gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
  gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
  g_mutex_unlock (&priv->watch_lock);

  return GST_RTSP_OK;
}

static GstRTSPResult
error (GstRTSPWatch * watch, GstRTSPResult result, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  gchar *str;

  str = gst_rtsp_strresult (result);
  GST_INFO ("client %p: received an error %s", client, str);
  g_free (str);

  return GST_RTSP_OK;
}

static GstRTSPResult
error_full (GstRTSPWatch * watch, GstRTSPResult result,
    GstRTSPMessage * message, guint id, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  gchar *str;
  GstRTSPContext sctx = { NULL }, *ctx;
  GstRTSPClientPrivate *priv;
  GstRTSPMessage response = { 0 };
  priv = client->priv;

  if (!(ctx = gst_rtsp_context_get_current ())) {
    ctx = &sctx;
    ctx->auth = priv->auth;
    gst_rtsp_context_push_current (ctx);
  }

  ctx->conn = priv->connection;
  ctx->client = client;
  ctx->request = message;
  ctx->method = GST_RTSP_INVALID;
  ctx->response = &response;

  /* only return error response if it is a request */
  if (!message || message->type != GST_RTSP_MESSAGE_REQUEST)
    goto done;

  if (result == GST_RTSP_ENOMEM) {
    send_generic_error_response (client, GST_RTSP_STS_REQUEST_ENTITY_TOO_LARGE,
        ctx);
    goto done;
  }
  if (result == GST_RTSP_EPARSE) {
    send_generic_error_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
    goto done;
  }

done:
  if (ctx == &sctx)
    gst_rtsp_context_pop_current (ctx);
  str = gst_rtsp_strresult (result);
  GST_INFO
      ("client %p: error when handling message %p with id %d: %s",
      client, message, id, str);
  g_free (str);

  return GST_RTSP_OK;
}

static gboolean
remember_tunnel (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  const gchar *tunnelid;

  /* store client in the pending tunnels */
  tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection);
  if (tunnelid == NULL)
    goto no_tunnelid;

  GST_INFO ("client %p: inserting tunnel session %s", client, tunnelid);

  /* we can't have two clients connecting with the same tunnelid */
  g_mutex_lock (&tunnels_lock);
  if (g_hash_table_lookup (tunnels, tunnelid))
    goto tunnel_existed;

  g_hash_table_insert (tunnels, g_strdup (tunnelid), g_object_ref (client));
  g_mutex_unlock (&tunnels_lock);

  return TRUE;

  /* ERRORS */
no_tunnelid:
  {
    GST_ERROR ("client %p: no tunnelid provided", client);
    return FALSE;
  }
tunnel_existed:
  {
    g_mutex_unlock (&tunnels_lock);
    GST_ERROR ("client %p: tunnel session %s already existed", client,
        tunnelid);
    return FALSE;
  }
}

static GstRTSPResult
tunnel_lost (GstRTSPWatch * watch, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  GstRTSPClientPrivate *priv = client->priv;

  GST_WARNING ("client %p: tunnel lost (connection %p)", client,
      priv->connection);

  /* ignore error, it'll only be a problem when the client does a POST again */
  remember_tunnel (client);

  return GST_RTSP_OK;
}

static GstRTSPStatusCode
handle_tunnel (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  GstRTSPClient *oclient;
  GstRTSPClientPrivate *opriv;
  const gchar *tunnelid;

  tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection);
  if (tunnelid == NULL)
    goto no_tunnelid;

  /* check for previous tunnel */
  g_mutex_lock (&tunnels_lock);
  oclient = g_hash_table_lookup (tunnels, tunnelid);

  if (oclient == NULL) {
    /* no previous tunnel, remember tunnel */
    g_hash_table_insert (tunnels, g_strdup (tunnelid), g_object_ref (client));
    g_mutex_unlock (&tunnels_lock);

    GST_INFO ("client %p: no previous tunnel found, remembering tunnel (%p)",
        client, priv->connection);
  } else {
    /* merge both tunnels into the first client */
    /* remove the old client from the table. ref before because removing it will
     * remove the ref to it. */
    g_object_ref (oclient);
    g_hash_table_remove (tunnels, tunnelid);
    g_mutex_unlock (&tunnels_lock);

    opriv = oclient->priv;

    g_mutex_lock (&opriv->watch_lock);
    if (opriv->watch == NULL)
      goto tunnel_closed;
    if (opriv->tstate == priv->tstate)
      goto tunnel_duplicate_id;

    GST_INFO ("client %p: found previous tunnel %p (old %p, new %p)", client,
        oclient, opriv->connection, priv->connection);

    gst_rtsp_connection_do_tunnel (opriv->connection, priv->connection);
    gst_rtsp_watch_reset (priv->watch);
    gst_rtsp_watch_reset (opriv->watch);
    g_mutex_unlock (&opriv->watch_lock);
    g_object_unref (oclient);

    /* the old client owns the tunnel now, the new one will be freed */
    g_source_destroy ((GSource *) priv->watch);
    priv->watch = NULL;
    gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
    gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
    rtsp_ctrl_timeout_remove (client);
  }

  return GST_RTSP_STS_OK;

  /* ERRORS */
no_tunnelid:
  {
    GST_ERROR ("client %p: no tunnelid provided", client);
    return GST_RTSP_STS_SERVICE_UNAVAILABLE;
  }
tunnel_closed:
  {
    GST_ERROR ("client %p: tunnel session %s was closed", client, tunnelid);
    g_mutex_unlock (&opriv->watch_lock);
    g_object_unref (oclient);
    return GST_RTSP_STS_SERVICE_UNAVAILABLE;
  }
tunnel_duplicate_id:
  {
    GST_ERROR ("client %p: tunnel session %s was duplicate", client, tunnelid);
    g_mutex_unlock (&opriv->watch_lock);
    g_object_unref (oclient);
    return GST_RTSP_STS_BAD_REQUEST;
  }
}

static GstRTSPStatusCode
tunnel_get (GstRTSPWatch * watch, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);

  GST_INFO ("client %p: tunnel get (connection %p)", client,
      client->priv->connection);

  g_mutex_lock (&client->priv->lock);
  client->priv->tstate = TUNNEL_STATE_GET;
  g_mutex_unlock (&client->priv->lock);

  return handle_tunnel (client);
}

static GstRTSPResult
tunnel_post (GstRTSPWatch * watch, gpointer user_data)
{
  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);

  GST_INFO ("client %p: tunnel post (connection %p)", client,
      client->priv->connection);

  g_mutex_lock (&client->priv->lock);
  client->priv->tstate = TUNNEL_STATE_POST;
  g_mutex_unlock (&client->priv->lock);

  if (handle_tunnel (client) != GST_RTSP_STS_OK)
    return GST_RTSP_ERROR;

  return GST_RTSP_OK;
}

static GstRTSPResult
tunnel_http_response (GstRTSPWatch * watch, GstRTSPMessage * request,
    GstRTSPMessage * response, gpointer user_data)
{
  GstRTSPClientClass *klass;

  GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
  klass = GST_RTSP_CLIENT_GET_CLASS (client);

  if (klass->tunnel_http_response) {
    klass->tunnel_http_response (client, request, response);
  }

  return GST_RTSP_OK;
}

static GstRTSPWatchFuncs watch_funcs = {
  message_received,
  message_sent,
  closed,
  error,
  tunnel_get,
  tunnel_post,
  error_full,
  tunnel_lost,
  tunnel_http_response
};

static void
client_watch_notify (GstRTSPClient * client)
{
  GstRTSPClientPrivate *priv = client->priv;
  gboolean closed = TRUE;
  GList *sessions G_GNUC_UNUSED;

  GST_INFO ("client %p: watch destroyed", client);
  priv->watch = NULL;
  /* remove all sessions if the media says so and so drop the extra client ref */
  gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
  gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
  rtsp_ctrl_timeout_remove (client);
  sessions = gst_rtsp_client_session_filter (client, cleanup_session, &closed);
  g_assert (sessions == NULL);

  if (closed)
    g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_CLOSED], 0, NULL);
  g_object_unref (client);
}

/**
 * gst_rtsp_client_attach:
 * @client: a #GstRTSPClient
 * @context: (allow-none): a #GMainContext
 *
 * Attaches @client to @context. When the mainloop for @context is run, the
 * client will be dispatched. When @context is %NULL, the default context will be
 * used).
 *
 * This function should be called when the client properties and urls are fully
 * configured and the client is ready to start.
 *
 * Returns: the ID (greater than 0) for the source within the GMainContext.
 */
guint
gst_rtsp_client_attach (GstRTSPClient * client, GMainContext * context)
{
  GstRTSPClientPrivate *priv;
  GSource *timer_src;
  guint res;
  GWeakRef *client_weak_ref = g_new (GWeakRef, 1);

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), 0);
  priv = client->priv;
  g_return_val_if_fail (priv->connection != NULL, 0);
  g_return_val_if_fail (priv->watch == NULL, 0);
  g_return_val_if_fail (priv->watch_context == NULL, 0);

  /* make sure noone will free the context before the watch is destroyed */
  priv->watch_context = g_main_context_ref (context);

  /* create watch for the connection and attach */
  priv->watch = gst_rtsp_watch_new (priv->connection, &watch_funcs,
      g_object_ref (client), (GDestroyNotify) client_watch_notify);
  gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
  gst_rtsp_client_set_send_messages_func (client, do_send_messages, priv->watch,
      (GDestroyNotify) gst_rtsp_watch_unref);

  gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);

  /* take the lock before attaching the client watch, so that the client thread
   * can not access the control channel timer until it's properly in place */
  g_mutex_lock (&priv->lock);

  GST_INFO ("client %p: attaching to context %p", client, context);
  res = gst_rtsp_watch_attach (priv->watch, context);

  /* Setting up a timeout for the RTSP control channel until a session
   * is up where it is handling timeouts. */

  /* remove old timeout if any */
  rtsp_ctrl_timeout_remove_unlocked (client->priv);

  timer_src = g_timeout_source_new_seconds (RTSP_CTRL_CB_INTERVAL);
  g_weak_ref_init (client_weak_ref, client);
  g_source_set_callback (timer_src, rtsp_ctrl_timeout_cb, client_weak_ref,
      rtsp_ctrl_timeout_destroy_notify);
  g_source_attach (timer_src, priv->watch_context);
  priv->rtsp_ctrl_timeout = timer_src;
  GST_DEBUG ("rtsp control setting up session timeout %p.",
      priv->rtsp_ctrl_timeout);

  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_client_session_filter:
 * @client: a #GstRTSPClient
 * @func: (scope call) (allow-none): a callback
 * @user_data: user data passed to @func
 *
 * Call @func for each session managed by @client. The result value of @func
 * determines what happens to the session. @func will be called with @client
 * locked so no further actions on @client can be performed from @func.
 *
 * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
 * @client.
 *
 * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @client.
 *
 * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @client but
 * will also be added with an additional ref to the result #GList of this
 * function..
 *
 * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each session.
 *
 * Returns: (element-type GstRTSPSession) (transfer full): a #GList with all
 * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
 * element in the #GList should be unreffed before the list is freed.
 */
GList *
gst_rtsp_client_session_filter (GstRTSPClient * client,
    GstRTSPClientSessionFilterFunc func, gpointer user_data)
{
  GstRTSPClientPrivate *priv;
  GList *result, *walk, *next;
  GHashTable *visited;
  guint cookie;

  g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);

  priv = client->priv;

  result = NULL;
  if (func)
    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  g_mutex_lock (&priv->lock);
restart:
  cookie = priv->sessions_cookie;
  for (walk = priv->sessions; walk; walk = next) {
    GstRTSPSession *sess = walk->data;
    GstRTSPFilterResult res;
    gboolean changed;

    next = g_list_next (walk);

    if (func) {
      /* only visit each session once */
      if (g_hash_table_contains (visited, sess))
        continue;

      g_hash_table_add (visited, g_object_ref (sess));
      g_mutex_unlock (&priv->lock);

      res = func (client, sess, user_data);

      g_mutex_lock (&priv->lock);
    } else
      res = GST_RTSP_FILTER_REF;

    changed = (cookie != priv->sessions_cookie);

    switch (res) {
      case GST_RTSP_FILTER_REMOVE:
        /* stop watching the session and pretend it went away, if the list was
         * changed, we can't use the current list position, try to see if we
         * still have the session */
        client_unwatch_session (client, sess, changed ? NULL : walk);
        cookie = priv->sessions_cookie;
        break;
      case GST_RTSP_FILTER_REF:
        result = g_list_prepend (result, g_object_ref (sess));
        break;
      case GST_RTSP_FILTER_KEEP:
      default:
        break;
    }
    if (changed)
      goto restart;
  }
  g_mutex_unlock (&priv->lock);

  if (func)
    g_hash_table_unref (visited);

  return result;
}
 07070100000039000081A400000000000000000000000169D554BF00003891000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-client.h  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp/gstrtspconnection.h>

#ifndef __GST_RTSP_CLIENT_H__
#define __GST_RTSP_CLIENT_H__

G_BEGIN_DECLS

typedef struct _GstRTSPClient GstRTSPClient;
typedef struct _GstRTSPClientClass GstRTSPClientClass;
typedef struct _GstRTSPClientPrivate GstRTSPClientPrivate;

#include "rtsp-server-prelude.h"
#include "rtsp-context.h"
#include "rtsp-mount-points.h"
#include "rtsp-sdp.h"
#include "rtsp-auth.h"

#define GST_TYPE_RTSP_CLIENT              (gst_rtsp_client_get_type ())
#define GST_IS_RTSP_CLIENT(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_CLIENT))
#define GST_IS_RTSP_CLIENT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_CLIENT))
#define GST_RTSP_CLIENT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass))
#define GST_RTSP_CLIENT(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClient))
#define GST_RTSP_CLIENT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass))
#define GST_RTSP_CLIENT_CAST(obj)         ((GstRTSPClient*)(obj))
#define GST_RTSP_CLIENT_CLASS_CAST(klass) ((GstRTSPClientClass*)(klass))

/**
 * GstRTSPClientSendFunc:
 * @client: a #GstRTSPClient
 * @message: a #GstRTSPMessage
 * @close: close the connection
 * @user_data: user data when registering the callback
 *
 * This callback is called when @client wants to send @message. When @close is
 * %TRUE, the connection should be closed when the message has been sent.
 *
 * Returns: %TRUE on success.
 */
typedef gboolean (*GstRTSPClientSendFunc)      (GstRTSPClient *client,
                                                GstRTSPMessage *message,
                                                gboolean close,
                                                gpointer user_data);

/**
 * GstRTSPClientSendMessagesFunc:
 * @client: a #GstRTSPClient
 * @messages: #GstRTSPMessage
 * @n_messages: number of messages
 * @close: close the connection
 * @user_data: user data when registering the callback
 *
 * This callback is called when @client wants to send @messages. When @close is
 * %TRUE, the connection should be closed when the message has been sent.
 *
 * Returns: %TRUE on success.
 *
 * Since: 1.16
 */
typedef gboolean (*GstRTSPClientSendMessagesFunc)      (GstRTSPClient *client,
                                                        GstRTSPMessage *messages,
                                                        guint n_messages,
                                                        gboolean close,
                                                        gpointer user_data);

/**
 * GstRTSPClient:
 *
 * The client object represents the connection and its state with a client.
 */
struct _GstRTSPClient {
  GObject       parent;

  /*< private >*/
  GstRTSPClientPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPClientClass:
 * @create_sdp: called when the SDP needs to be created for media.
 * @configure_client_media: called when the stream in media needs to be configured.
 *    The default implementation will configure the blocksize on the payloader when
 *    spcified in the request headers.
 * @configure_client_transport: called when the client transport needs to be
 *    configured.
 * @params_set: set parameters. This function should also initialize the
 *    RTSP response(ctx->response) via a call to gst_rtsp_message_init_response()
 * @params_get: get parameters. This function should also initialize the
 *    RTSP response(ctx->response) via a call to gst_rtsp_message_init_response()
 * @make_path_from_uri: called to create path from uri.
 * @adjust_play_mode: called to give the application the possibility to adjust
 *    the range, seek flags, rate and rate-control. Since 1.18
 * @adjust_play_response: called to give the implementation the possibility to
 *    adjust the response to a play request, for example if extra headers were
 *    parsed when #GstRTSPClientClass.adjust_play_mode was called. Since 1.18
 * @tunnel_http_response: called when a response to the GET request is about to
 *   be sent for a tunneled connection. The response can be modified. Since: 1.4
 *
 * The client class structure.
 */
struct _GstRTSPClientClass {
  GObjectClass  parent_class;

  GstSDPMessage * (*create_sdp) (GstRTSPClient *client, GstRTSPMedia *media);
  gboolean        (*configure_client_media)     (GstRTSPClient * client,
                                                 GstRTSPMedia * media, GstRTSPStream * stream,
                                                 GstRTSPContext * ctx);
  gboolean        (*configure_client_transport) (GstRTSPClient * client,
                                                 GstRTSPContext * ctx,
                                                 GstRTSPTransport * ct);
  GstRTSPResult   (*params_set) (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPResult   (*params_get) (GstRTSPClient *client, GstRTSPContext *ctx);
  gchar *         (*make_path_from_uri) (GstRTSPClient *client, const GstRTSPUrl *uri);
  GstRTSPStatusCode (*adjust_play_mode) (GstRTSPClient * client,
                                         GstRTSPContext * context,
                                         GstRTSPTimeRange ** range,
                                         GstSeekFlags * flags,
                                         gdouble * rate,
                                         GstClockTime * trickmode_interval,
                                         gboolean * enable_rate_control);
  GstRTSPStatusCode (*adjust_play_response) (GstRTSPClient * client,
                                            GstRTSPContext * context);

  /* signals */
  void     (*closed)                  (GstRTSPClient *client);
  void     (*new_session)             (GstRTSPClient *client, GstRTSPSession *session);
  void     (*options_request)         (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*describe_request)        (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*setup_request)           (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*play_request)            (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*pause_request)           (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*teardown_request)        (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*set_parameter_request)   (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*get_parameter_request)   (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*handle_response)         (GstRTSPClient *client, GstRTSPContext *ctx);

  void     (*tunnel_http_response)    (GstRTSPClient * client, GstRTSPMessage * request,
                                       GstRTSPMessage * response);
  void     (*send_message)            (GstRTSPClient * client, GstRTSPContext *ctx,
                                       GstRTSPMessage * response);

  gboolean (*handle_sdp)              (GstRTSPClient *client, GstRTSPContext *ctx, GstRTSPMedia *media, GstSDPMessage *sdp);

  void     (*announce_request)        (GstRTSPClient *client, GstRTSPContext *ctx);
  void     (*record_request)          (GstRTSPClient *client, GstRTSPContext *ctx);
  gchar*   (*check_requirements)      (GstRTSPClient *client, GstRTSPContext *ctx, gchar ** arr);

  GstRTSPStatusCode (*pre_options_request)       (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_describe_request)      (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_setup_request)         (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_play_request)          (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_pause_request)         (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_teardown_request)      (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_set_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_get_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_announce_request)      (GstRTSPClient *client, GstRTSPContext *ctx);
  GstRTSPStatusCode (*pre_record_request)        (GstRTSPClient *client, GstRTSPContext *ctx);

  /**
   * GstRTSPClientClass::adjust_error_code:
   * @client: a #GstRTSPClient
   * @ctx: a #GstRTSPContext
   * @code: a #GstRTSPStatusCode
   *
   * Called before sending error response to give the application the
   * possibility to adjust the error code.
   *
   * Returns: a #GstRTSPStatusCode, containing the adjusted error code.
   *
   * Since: 1.22
   */
  GstRTSPStatusCode (*adjust_error_code)         (GstRTSPClient *client, GstRTSPContext *ctx, GstRTSPStatusCode code);

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE-19];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_client_get_type          (void);

GST_RTSP_SERVER_API
GstRTSPClient *       gst_rtsp_client_new               (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_session_pool  (GstRTSPClient *client,
                                                         GstRTSPSessionPool *pool);

GST_RTSP_SERVER_API
GstRTSPSessionPool *  gst_rtsp_client_get_session_pool  (GstRTSPClient *client) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_mount_points  (GstRTSPClient *client,
                                                         GstRTSPMountPoints *mounts);

GST_RTSP_SERVER_API
GstRTSPMountPoints *  gst_rtsp_client_get_mount_points  (GstRTSPClient *client) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_content_length_limit (GstRTSPClient *client, guint limit);

GST_RTSP_SERVER_API
guint                 gst_rtsp_client_get_content_length_limit (GstRTSPClient *client);

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_auth          (GstRTSPClient *client, GstRTSPAuth *auth);

GST_RTSP_SERVER_API
GstRTSPAuth *         gst_rtsp_client_get_auth          (GstRTSPClient *client) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_thread_pool   (GstRTSPClient *client, GstRTSPThreadPool *pool);

GST_RTSP_SERVER_API
GstRTSPThreadPool *   gst_rtsp_client_get_thread_pool   (GstRTSPClient *client) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean              gst_rtsp_client_set_connection    (GstRTSPClient *client, GstRTSPConnection *conn);

GST_RTSP_SERVER_API
GstRTSPConnection *   gst_rtsp_client_get_connection    (GstRTSPClient *client);

GST_RTSP_SERVER_API
guint                 gst_rtsp_client_attach            (GstRTSPClient *client,
                                                         GMainContext *context);

GST_RTSP_SERVER_API
void                  gst_rtsp_client_close             (GstRTSPClient * client);

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_send_func     (GstRTSPClient *client,
                                                         GstRTSPClientSendFunc func,
                                                         gpointer user_data,
                                                         GDestroyNotify notify);

GST_RTSP_SERVER_API
void                  gst_rtsp_client_set_send_messages_func (GstRTSPClient *client,
                                                              GstRTSPClientSendMessagesFunc func,
                                                              gpointer user_data,
                                                              GDestroyNotify notify);

GST_RTSP_SERVER_API
GstRTSPResult         gst_rtsp_client_handle_message    (GstRTSPClient *client,
                                                         GstRTSPMessage *message);

GST_RTSP_SERVER_API
GstRTSPResult         gst_rtsp_client_send_message      (GstRTSPClient * client,
                                                         GstRTSPSession *session,
                                                         GstRTSPMessage *message);
/**
 * GstRTSPClientSessionFilterFunc:
 * @client: a #GstRTSPClient object
 * @sess: a #GstRTSPSession in @client
 * @user_data: user data that has been given to gst_rtsp_client_session_filter()
 *
 * This function will be called by the gst_rtsp_client_session_filter(). An
 * implementation should return a value of #GstRTSPFilterResult.
 *
 * When this function returns #GST_RTSP_FILTER_REMOVE, @sess will be removed
 * from @client.
 *
 * A return value of #GST_RTSP_FILTER_KEEP will leave @sess untouched in
 * @client.
 *
 * A value of #GST_RTSP_FILTER_REF will add @sess to the result #GList of
 * gst_rtsp_client_session_filter().
 *
 * Returns: a #GstRTSPFilterResult.
 */
typedef GstRTSPFilterResult (*GstRTSPClientSessionFilterFunc)  (GstRTSPClient *client,
                                                                GstRTSPSession *sess,
                                                                gpointer user_data);

GST_RTSP_SERVER_API
GList *                gst_rtsp_client_session_filter    (GstRTSPClient *client,
                                                          GstRTSPClientSessionFilterFunc func,
                                                          gpointer user_data) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPStreamTransport * gst_rtsp_client_get_stream_transport (GstRTSPClient *client,
                                                               guint8 channel);


#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPClient, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_CLIENT_H__ */
   0707010000003A000081A400000000000000000000000169D554BF00000B43000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-context.c /* GStreamer
 * Copyright (C) 2013 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-context
 * @short_description: A client request context
 * @see_also: #GstRTSPServer, #GstRTSPClient
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rtsp-context.h"

G_DEFINE_POINTER_TYPE (GstRTSPContext, gst_rtsp_context);

static GPrivate current_context;

/**
 * gst_rtsp_context_get_current: (skip):
 *
 * Get the current #GstRTSPContext. This object is retrieved from the
 * current thread that is handling the request for a client.
 *
 * Returns: a #GstRTSPContext
 */
GstRTSPContext *
gst_rtsp_context_get_current (void)
{
  GSList *l;

  l = g_private_get (&current_context);
  if (l == NULL)
    return NULL;

  return (GstRTSPContext *) (l->data);

}

/**
 * gst_rtsp_context_push_current:
 * @ctx: a #GstRTSPContext
 *
 * Pushes @ctx onto the context stack. The current
 * context can then be received using gst_rtsp_context_get_current().
 **/
void
gst_rtsp_context_push_current (GstRTSPContext * ctx)
{
  GSList *l;

  g_return_if_fail (ctx != NULL);

  l = g_private_get (&current_context);
  l = g_slist_prepend (l, ctx);
  g_private_set (&current_context, l);
}

/**
 * gst_rtsp_context_pop_current:
 * @ctx: a #GstRTSPContext
 *
 * Pops @ctx off the context stack (verifying that @ctx
 * is on the top of the stack).
 **/
void
gst_rtsp_context_pop_current (GstRTSPContext * ctx)
{
  GSList *l;

  l = g_private_get (&current_context);

  g_return_if_fail (l != NULL);
  g_return_if_fail (l->data == ctx);

  l = g_slist_delete_link (l, l);
  g_private_set (&current_context, l);
}

/**
 * gst_rtsp_context_set_token:
 * @ctx: a #GstRTSPContext
 * @token: a #GstRTSPToken
 *
 * Set the token for @ctx.
 *
 * Since: 1.22
 **/
void
gst_rtsp_context_set_token (GstRTSPContext * ctx, GstRTSPToken * token)
{
  g_return_if_fail (ctx != NULL);
  g_return_if_fail (ctx == gst_rtsp_context_get_current ());
  g_return_if_fail (GST_IS_RTSP_TOKEN (token));

  if (ctx->token != NULL)
    gst_rtsp_token_unref (ctx->token);

  gst_rtsp_token_ref (token);
  ctx->token = token;
}
 0707010000003B000081A400000000000000000000000169D554BF00000C28000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-context.h /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp/gstrtspconnection.h>

#ifndef __GST_RTSP_CONTEXT_H__
#define __GST_RTSP_CONTEXT_H__

G_BEGIN_DECLS

#define GST_TYPE_RTSP_CONTEXT              (gst_rtsp_context_get_type ())

typedef struct _GstRTSPContext GstRTSPContext;

#include "rtsp-server-prelude.h"
#include "rtsp-server-object.h"
#include "rtsp-media.h"
#include "rtsp-media-factory.h"
#include "rtsp-session-media.h"
#include "rtsp-auth.h"
#include "rtsp-thread-pool.h"
#include "rtsp-token.h"

/**
 * GstRTSPContext:
 * @server: the server
 * @conn: the connection
 * @client: the client
 * @request: the complete request
 * @uri: the complete url parsed from @request
 * @method: the parsed method of @uri
 * @auth: the current auth object or %NULL
 * @token: authorisation token
 * @session: the session, can be %NULL
 * @sessmedia: the session media for the url can be %NULL
 * @factory: the media factory for the url, can be %NULL
 * @media: the media for the url can be %NULL
 * @stream: the stream for the url can be %NULL
 * @response: the response
 * @trans: the stream transport, can be %NULL
 *
 * Information passed around containing the context of a request.
 */
struct _GstRTSPContext {
  GstRTSPServer          *server;
  GstRTSPConnection      *conn;
  GstRTSPClient          *client;
  GstRTSPMessage         *request;
  GstRTSPUrl             *uri;
  GstRTSPMethod           method;
  GstRTSPAuth            *auth;
  GstRTSPToken           *token;
  GstRTSPSession         *session;
  GstRTSPSessionMedia    *sessmedia;
  GstRTSPMediaFactory    *factory;
  GstRTSPMedia           *media;
  GstRTSPStream          *stream;
  GstRTSPMessage         *response;
  GstRTSPStreamTransport *trans;

  /*< private >*/
  gpointer            _gst_reserved[GST_PADDING - 1];
};

GST_RTSP_SERVER_API
GType gst_rtsp_context_get_type (void);

GST_RTSP_SERVER_API
GstRTSPContext *     gst_rtsp_context_get_current   (void);

GST_RTSP_SERVER_API
void                 gst_rtsp_context_push_current  (GstRTSPContext * ctx);

GST_RTSP_SERVER_API
void                 gst_rtsp_context_pop_current   (GstRTSPContext * ctx);

GST_RTSP_SERVER_API
void                 gst_rtsp_context_set_token     (GstRTSPContext * ctx, GstRTSPToken * token);

G_END_DECLS

#endif /* __GST_RTSP_CONTEXT_H__ */
0707010000003C000081A400000000000000000000000169D554BF000027EF000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-latency-bin.c /* GStreamer
 * Copyright (C) 2018 Ognyan Tonchev <ognyan@axis.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
#include "rtsp-latency-bin.h"

struct _GstRTSPLatencyBinPrivate
{
  GstPad *sinkpad;
  GstElement *element;
};

enum
{
  PROP_0,
  PROP_ELEMENT,
  PROP_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_latency_bin_debug);
#define GST_CAT_DEFAULT rtsp_latency_bin_debug

static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

static void gst_rtsp_latency_bin_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_latency_bin_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static gboolean gst_rtsp_latency_bin_element_query (GstElement * element,
    GstQuery * query);
static gboolean gst_rtsp_latency_bin_element_event (GstElement * element,
    GstEvent * event);
static void gst_rtsp_latency_bin_message_handler (GstBin * bin,
    GstMessage * message);
static gboolean gst_rtsp_latency_bin_add_element (GstRTSPLatencyBin *
    latency_bin, GstElement * element);
static GstStateChangeReturn gst_rtsp_latency_bin_change_state (GstElement *
    element, GstStateChange transition);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPLatencyBin, gst_rtsp_latency_bin,
    GST_TYPE_BIN);

static void
gst_rtsp_latency_bin_class_init (GstRTSPLatencyBinClass * klass)
{
  GObjectClass *gobject_klass = G_OBJECT_CLASS (klass);
  GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (klass);
  GstBinClass *gstbin_klass = GST_BIN_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (rtsp_latency_bin_debug,
      "rtsplatencybin", 0, "GstRTSPLatencyBin");

  gobject_klass->get_property = gst_rtsp_latency_bin_get_property;
  gobject_klass->set_property = gst_rtsp_latency_bin_set_property;

  g_object_class_install_property (gobject_klass, PROP_ELEMENT,
      g_param_spec_object ("element", "The Element",
          "The GstElement to prevent from affecting piplines latency",
          GST_TYPE_ELEMENT,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gstelement_klass->change_state =
      GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_change_state);
  gstelement_klass->query =
      GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_element_query);
  gstelement_klass->send_event =
      GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_element_event);

  gstbin_klass->handle_message =
      GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_message_handler);
}

static void
gst_rtsp_latency_bin_init (GstRTSPLatencyBin * latency_bin)
{
  GST_OBJECT_FLAG_SET (latency_bin, GST_ELEMENT_FLAG_SINK);
}

static void
gst_rtsp_latency_bin_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (object);
  GstRTSPLatencyBinPrivate *priv =
      gst_rtsp_latency_bin_get_instance_private (latency_bin);

  switch (propid) {
    case PROP_ELEMENT:
      g_value_set_object (value, priv->element);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_latency_bin_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (object);

  switch (propid) {
    case PROP_ELEMENT:
      if (!gst_rtsp_latency_bin_add_element (latency_bin,
              g_value_get_object (value))) {
        GST_WARNING_OBJECT (latency_bin, "Could not add the element");
      }
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static gboolean
gst_rtsp_latency_bin_add_element (GstRTSPLatencyBin * latency_bin,
    GstElement * element)
{
  GstRTSPLatencyBinPrivate *priv =
      gst_rtsp_latency_bin_get_instance_private (latency_bin);
  GstPad *pad;
  GstPadTemplate *templ;

  GST_DEBUG_OBJECT (latency_bin, "Adding element to latencybin : %s",
      GST_ELEMENT_NAME (element));

  if (!element) {
    goto no_element;
  }

  /* add the element to ourself */
  gst_object_ref (element);
  gst_bin_add (GST_BIN (latency_bin), element);
  priv->element = element;

  /* add ghost pad first */
  templ = gst_static_pad_template_get (&sinktemplate);
  priv->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
  gst_object_unref (templ);
  g_assert (priv->sinkpad);

  gst_element_add_pad (GST_ELEMENT (latency_bin), priv->sinkpad);

  /* and link it to our element */
  pad = gst_element_get_static_pad (element, "sink");
  if (!pad) {
    goto no_sink_pad;
  }

  if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (priv->sinkpad), pad)) {
    goto set_target_failed;
  }

  gst_object_unref (pad);

  return TRUE;

  /* ERRORs */
no_element:
  {
    GST_WARNING_OBJECT (latency_bin, "No element, not adding");
    return FALSE;
  }
no_sink_pad:
  {
    GST_WARNING_OBJECT (latency_bin, "The element has no sink pad");
    return FALSE;
  }
set_target_failed:
  {
    GST_WARNING_OBJECT (latency_bin, "Could not set target pad");
    gst_object_unref (pad);
    return FALSE;
  }
}


static gboolean
gst_rtsp_latency_bin_element_query (GstElement * element, GstQuery * query)
{
  gboolean ret = TRUE;

  GST_LOG_OBJECT (element, "got query %s", GST_QUERY_TYPE_NAME (query));

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_LATENCY:
      /* ignoring latency query, we do not want our element to affect latency on
       * the rest of the pipeline */
      GST_DEBUG_OBJECT (element, "ignoring latency query");
      gst_query_set_latency (query, FALSE, 0, -1);
      break;
    default:
      ret =
          GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->query
          (GST_ELEMENT (element), query);
      break;
  }

  return ret;
}

static gboolean
gst_rtsp_latency_bin_element_event (GstElement * element, GstEvent * event)
{
  gboolean ret = TRUE;

  GST_LOG_OBJECT (element, "got event %s", GST_EVENT_TYPE_NAME (event));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_LATENCY:
      /* ignoring latency event, we will configure latency on our element when
       * going to PLAYING */
      GST_DEBUG_OBJECT (element, "ignoring latency event");
      gst_event_unref (event);
      break;
    default:
      ret =
          GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->send_event
          (GST_ELEMENT (element), event);
      break;
  }

  return ret;
}

static gboolean
gst_rtsp_latency_bin_recalculate_latency (GstRTSPLatencyBin * latency_bin)
{
  GstRTSPLatencyBinPrivate *priv =
      gst_rtsp_latency_bin_get_instance_private (latency_bin);
  GstEvent *latency;
  GstQuery *query;
  GstClockTime min_latency;

  GST_DEBUG_OBJECT (latency_bin, "Recalculating latency");

  if (!priv->element) {
    GST_WARNING_OBJECT (latency_bin, "We do not have an element");
    return FALSE;
  }

  query = gst_query_new_latency ();

  if (!gst_element_query (priv->element, query)) {
    GST_WARNING_OBJECT (latency_bin, "Latency query failed");
    gst_query_unref (query);
    return FALSE;
  }

  gst_query_parse_latency (query, NULL, &min_latency, NULL);
  gst_query_unref (query);

  GST_LOG_OBJECT (latency_bin, "Got min_latency from stream: %"
      GST_TIME_FORMAT, GST_TIME_ARGS (min_latency));

  latency = gst_event_new_latency (min_latency);
  if (!gst_element_send_event (priv->element, latency)) {
    GST_WARNING_OBJECT (latency_bin, "Sending latency event to stream failed");
    return FALSE;
  }

  return TRUE;
}

static void
gst_rtsp_latency_bin_message_handler (GstBin * bin, GstMessage * message)
{
  GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (bin);

  GST_LOG_OBJECT (bin, "Got message %s", GST_MESSAGE_TYPE_NAME (message));

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_LATENCY:{
      if (!gst_rtsp_latency_bin_recalculate_latency (latency_bin)) {
        GST_WARNING_OBJECT (latency_bin, "Could not recalculate latency");
      }
      break;
    }
    default:
      GST_BIN_CLASS (gst_rtsp_latency_bin_parent_class)->handle_message (bin,
          message);
      break;
  }
}

static GstStateChangeReturn
gst_rtsp_latency_bin_change_state (GstElement * element, GstStateChange
    transition)
{
  GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (element);
  GstStateChangeReturn ret;

  GST_LOG_OBJECT (latency_bin, "Changing state %s",
      gst_state_change_get_name (transition));

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
    case GST_STATE_CHANGE_PLAYING_TO_PLAYING:
      if (!gst_rtsp_latency_bin_recalculate_latency (latency_bin)) {
        GST_WARNING_OBJECT (latency_bin, "Could not recalculate latency");
      }
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->change_state
      (element, transition);

  return ret;
}

/**
 * gst_rtsp_latency_bin_new:
 * @element: (transfer full): a #GstElement
 *
 * Create a bin that encapsulates an @element and prevents it from affecting
 * latency on the whole pipeline.
 *
 * Returns: (nullable): A newly created #GstRTSPLatencyBin element, or %NULL on failure
 */
GstElement *
gst_rtsp_latency_bin_new (GstElement * element)
{
  GstElement *gst_rtsp_latency_bin;

  g_return_val_if_fail (element, NULL);

  gst_rtsp_latency_bin = g_object_new (GST_RTSP_LATENCY_BIN_TYPE, "element",
      element, NULL);
  gst_object_unref (element);
  return gst_rtsp_latency_bin;
}
 0707010000003D000081A400000000000000000000000169D554BF00000947000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-latency-bin.h /* GStreamer
 * Copyright (C) 2018 Ognyan Tonchev <ognyan@axis.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_LATENCY_BIN_H__
#define __GST_RTSP_LATENCY_BIN_H__

#include <gst/gst.h>
#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

typedef struct _GstRTSPLatencyBin GstRTSPLatencyBin;
typedef struct _GstRTSPLatencyBinClass GstRTSPLatencyBinClass;
typedef struct _GstRTSPLatencyBinPrivate GstRTSPLatencyBinPrivate;

#define GST_RTSP_LATENCY_BIN_TYPE                 (gst_rtsp_latency_bin_get_type ())
#define IS_GST_RTSP_LATENCY_BIN(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_RTSP_LATENCY_BIN_TYPE))
#define IS_GST_RTSP_LATENCY_BIN_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_RTSP_LATENCY_BIN_TYPE))
#define GST_RTSP_LATENCY_BIN_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_RTSP_LATENCY_BIN_TYPE, GstRTSPLatencyBinClass))
#define GST_RTSP_LATENCY_BIN(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_RTSP_LATENCY_BIN_TYPE, GstRTSPLatencyBin))
#define GST_RTSP_LATENCY_BIN_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GST_RTSP_LATENCY_BIN_TYPE, GstRTSPLatencyBinClass))
#define GST_RTSP_LATENCY_BIN_CAST(obj)            ((GstRTSPLatencyBin*)(obj))
#define GST_RTSP_LATENCY_BIN_CLASS_CAST(klass)    ((GstRTSPLatencyBinClass*)(klass))

struct _GstRTSPLatencyBin {
  GstBin parent;

  GstRTSPLatencyBinPrivate *priv;
};

struct _GstRTSPLatencyBinClass {
  GstBinClass parent_class;
};

GST_RTSP_SERVER_API
GType gst_rtsp_latency_bin_get_type (void);

GST_RTSP_SERVER_API
GstElement * gst_rtsp_latency_bin_new (GstElement * element);

G_END_DECLS

#endif /* __GST_RTSP_LATENCY_BIN_H__ */
 0707010000003E000081A400000000000000000000000169D554BF00004808000000000000000000000000000000000000004000000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media-factory-uri.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-media-factory-uri
 * @short_description: A factory for URI sources
 * @see_also: #GstRTSPMediaFactory, #GstRTSPMedia
 *
 * This specialized #GstRTSPMediaFactory constructs media pipelines from a URI,
 * given with gst_rtsp_media_factory_uri_set_uri().
 *
 * It will automatically demux and payload the different streams found in the
 * media at URL.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-media-factory-uri.h"

struct _GstRTSPMediaFactoryURIPrivate
{
  GMutex lock;
  gchar *uri;                   /* protected by lock */
  gboolean use_gstpay;

  GstCaps *raw_vcaps;
  GstCaps *raw_acaps;
  GList *demuxers;
  GList *payloaders;
  GList *decoders;
};

#define DEFAULT_URI         NULL
#define DEFAULT_USE_GSTPAY  FALSE

enum
{
  PROP_0,
  PROP_URI,
  PROP_USE_GSTPAY,
  PROP_LAST
};


#define RAW_VIDEO_CAPS \
    "video/x-raw"

#define RAW_AUDIO_CAPS \
    "audio/x-raw"

static GstStaticCaps raw_video_caps = GST_STATIC_CAPS (RAW_VIDEO_CAPS);
static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS (RAW_AUDIO_CAPS);

typedef struct
{
  GstRTSPMediaFactoryURI *factory;
  guint pt;
} FactoryData;

static void
free_data (FactoryData * data)
{
  g_object_unref (data->factory);
  g_free (data);
}

static const gchar *factory_key = "GstRTSPMediaFactoryURI";

GST_DEBUG_CATEGORY_STATIC (rtsp_media_factory_uri_debug);
#define GST_CAT_DEFAULT rtsp_media_factory_uri_debug

static void gst_rtsp_media_factory_uri_get_property (GObject * object,
    guint propid, GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_uri_set_property (GObject * object,
    guint propid, const GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_uri_finalize (GObject * obj);

static GstElement *rtsp_media_factory_uri_create_element (GstRTSPMediaFactory *
    factory, const GstRTSPUrl * url);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMediaFactoryURI, gst_rtsp_media_factory_uri,
    GST_TYPE_RTSP_MEDIA_FACTORY);

static void
gst_rtsp_media_factory_uri_class_init (GstRTSPMediaFactoryURIClass * klass)
{
  GObjectClass *gobject_class;
  GstRTSPMediaFactoryClass *mediafactory_class;

  gobject_class = G_OBJECT_CLASS (klass);
  mediafactory_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);

  gobject_class->get_property = gst_rtsp_media_factory_uri_get_property;
  gobject_class->set_property = gst_rtsp_media_factory_uri_set_property;
  gobject_class->finalize = gst_rtsp_media_factory_uri_finalize;

  /**
   * GstRTSPMediaFactoryURI::uri:
   *
   * The uri of the resource that will be served by this factory.
   */
  g_object_class_install_property (gobject_class, PROP_URI,
      g_param_spec_string ("uri", "URI",
          "The URI of the resource to stream", DEFAULT_URI,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPMediaFactoryURI::use-gstpay:
   *
   * Allow the usage of gstpay in order to avoid decoding of compressed formats
   * without a payloader.
   */
  g_object_class_install_property (gobject_class, PROP_USE_GSTPAY,
      g_param_spec_boolean ("use-gstpay", "Use gstpay",
          "Use the gstpay payloader to avoid decoding", DEFAULT_USE_GSTPAY,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  mediafactory_class->create_element = rtsp_media_factory_uri_create_element;

  GST_DEBUG_CATEGORY_INIT (rtsp_media_factory_uri_debug, "rtspmediafactoryuri",
      0, "GstRTSPMediaFactoryUri");
}

typedef struct
{
  GList *demux;
  GList *payload;
  GList *decode;
} FilterData;

static gboolean
payloader_filter (GstPluginFeature * feature, FilterData * data)
{
  const gchar *klass;
  GstElementFactory *fact;
  GList **list = NULL;

  /* we only care about element factories */
  if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature)))
    return FALSE;

  if (gst_plugin_feature_get_rank (feature) < GST_RANK_MARGINAL)
    return FALSE;

  fact = GST_ELEMENT_FACTORY_CAST (feature);

  klass = gst_element_factory_get_metadata (fact, GST_ELEMENT_METADATA_KLASS);

  if (strstr (klass, "Decoder"))
    list = &data->decode;
  else if (strstr (klass, "Demux"))
    list = &data->demux;
  else if (strstr (klass, "Parser") && strstr (klass, "Codec"))
    list = &data->demux;
  else if (strstr (klass, "Payloader") && strstr (klass, "RTP"))
    list = &data->payload;

  if (list) {
    GST_DEBUG ("adding %s", GST_OBJECT_NAME (fact));
    *list = g_list_prepend (*list, gst_object_ref (fact));
  }

  return FALSE;
}

static void
gst_rtsp_media_factory_uri_init (GstRTSPMediaFactoryURI * factory)
{
  GstRTSPMediaFactoryURIPrivate *priv =
      gst_rtsp_media_factory_uri_get_instance_private (factory);
  FilterData data = { NULL, NULL, NULL };
  GList *unused;

  GST_DEBUG_OBJECT (factory, "new");

  factory->priv = priv;

  priv->uri = g_strdup (DEFAULT_URI);
  priv->use_gstpay = DEFAULT_USE_GSTPAY;
  g_mutex_init (&priv->lock);

  /* get the feature list using the filter */
  unused =
      gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter)
      payloader_filter, FALSE, &data);
  /* sort */
  priv->demuxers =
      g_list_sort (data.demux, gst_plugin_feature_rank_compare_func);
  priv->payloaders =
      g_list_sort (data.payload, gst_plugin_feature_rank_compare_func);
  priv->decoders =
      g_list_sort (data.decode, gst_plugin_feature_rank_compare_func);

  priv->raw_vcaps = gst_static_caps_get (&raw_video_caps);
  priv->raw_acaps = gst_static_caps_get (&raw_audio_caps);

  gst_plugin_feature_list_free (unused);
}

static void
gst_rtsp_media_factory_uri_finalize (GObject * obj)
{
  GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (obj);
  GstRTSPMediaFactoryURIPrivate *priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "finalize");

  g_free (priv->uri);
  gst_plugin_feature_list_free (priv->demuxers);
  gst_plugin_feature_list_free (priv->payloaders);
  gst_plugin_feature_list_free (priv->decoders);
  gst_caps_unref (priv->raw_vcaps);
  gst_caps_unref (priv->raw_acaps);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_media_factory_uri_parent_class)->finalize (obj);
}

static void
gst_rtsp_media_factory_uri_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
  GstRTSPMediaFactoryURIPrivate *priv = factory->priv;

  switch (propid) {
    case PROP_URI:
      g_value_take_string (value, gst_rtsp_media_factory_uri_get_uri (factory));
      break;
    case PROP_USE_GSTPAY:
      g_value_set_boolean (value, priv->use_gstpay);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_media_factory_uri_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
  GstRTSPMediaFactoryURIPrivate *priv = factory->priv;

  switch (propid) {
    case PROP_URI:
      gst_rtsp_media_factory_uri_set_uri (factory, g_value_get_string (value));
      break;
    case PROP_USE_GSTPAY:
      priv->use_gstpay = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_media_factory_uri_new:
 *
 * Create a new #GstRTSPMediaFactoryURI instance.
 *
 * Returns: (transfer full): a new #GstRTSPMediaFactoryURI object.
 */
GstRTSPMediaFactoryURI *
gst_rtsp_media_factory_uri_new (void)
{
  GstRTSPMediaFactoryURI *result;

  result = g_object_new (GST_TYPE_RTSP_MEDIA_FACTORY_URI, NULL);

  return result;
}

/**
 * gst_rtsp_media_factory_uri_set_uri:
 * @factory: a #GstRTSPMediaFactory
 * @uri: the uri the stream
 *
 * Set the URI of the resource that will be streamed by this factory.
 */
void
gst_rtsp_media_factory_uri_set_uri (GstRTSPMediaFactoryURI * factory,
    const gchar * uri)
{
  GstRTSPMediaFactoryURIPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory));
  g_return_if_fail (uri != NULL);

  priv = factory->priv;

  g_mutex_lock (&priv->lock);
  g_free (priv->uri);
  priv->uri = g_strdup (uri);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_factory_uri_get_uri:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the URI that will provide media for this factory.
 *
 * Returns: (transfer full): the configured URI. g_free() after usage.
 */
gchar *
gst_rtsp_media_factory_uri_get_uri (GstRTSPMediaFactoryURI * factory)
{
  GstRTSPMediaFactoryURIPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory), NULL);

  priv = factory->priv;

  g_mutex_lock (&priv->lock);
  result = g_strdup (priv->uri);
  g_mutex_unlock (&priv->lock);

  return result;
}

static GstElementFactory *
find_payloader (GstRTSPMediaFactoryURI * urifact, GstCaps * caps)
{
  GstRTSPMediaFactoryURIPrivate *priv = urifact->priv;
  GList *list;
  GstElementFactory *factory = NULL;
  gboolean autoplug_more = FALSE;

  /* first find a demuxer that can link */
  list = gst_element_factory_list_filter (priv->demuxers, caps,
      GST_PAD_SINK, FALSE);

  if (list) {
    GstStructure *structure = gst_caps_get_structure (caps, 0);
    gboolean parsed = FALSE;
    gint mpegversion = 0;

    if (!gst_structure_get_boolean (structure, "parsed", &parsed) &&
        gst_structure_has_name (structure, "audio/mpeg") &&
        gst_structure_get_int (structure, "mpegversion", &mpegversion) &&
        (mpegversion == 2 || mpegversion == 4)) {
      /* for AAC it's framed=true instead of parsed=true */
      gst_structure_get_boolean (structure, "framed", &parsed);
    }

    /* Avoid plugging parsers in a loop. This is not 100% correct, as some
     * parsers don't set parsed=true in caps. We should do something like
     * decodebin does and track decode chains and elements plugged in those
     * chains...
     */
    if (parsed) {
      GList *walk;
      const gchar *klass;

      for (walk = list; walk; walk = walk->next) {
        factory = GST_ELEMENT_FACTORY (walk->data);
        klass = gst_element_factory_get_metadata (factory,
            GST_ELEMENT_METADATA_KLASS);
        if (strstr (klass, "Parser"))
          /* caps have parsed=true, so skip this parser to avoid loops */
          continue;

        autoplug_more = TRUE;
        break;
      }
    } else {
      /* caps don't have parsed=true set and we have a demuxer/parser */
      autoplug_more = TRUE;
    }

    gst_plugin_feature_list_free (list);
  }

  if (autoplug_more)
    /* we have a demuxer, try that one first */
    return NULL;

  /* no demuxer try a depayloader */
  list = gst_element_factory_list_filter (priv->payloaders, caps,
      GST_PAD_SINK, FALSE);

  if (list == NULL) {
    if (priv->use_gstpay) {
      /* no depayloader or parser/demuxer, use gstpay when allowed */
      factory = gst_element_factory_find ("rtpgstpay");
    } else {
      /* no depayloader, try a decoder, we'll get to a payloader for a decoded
       * video or audio format, worst case. */
      list = gst_element_factory_list_filter (priv->decoders, caps,
          GST_PAD_SINK, FALSE);

      if (list != NULL) {
        /* we have a decoder, try that one first */
        gst_plugin_feature_list_free (list);
        return NULL;
      }
    }
  }

  if (list != NULL) {
    factory = GST_ELEMENT_FACTORY_CAST (list->data);
    g_object_ref (factory);
    gst_plugin_feature_list_free (list);
  }
  return factory;
}

static gboolean
autoplug_continue_cb (GstElement * uribin, GstPad * pad, GstCaps * caps,
    GstElement * element)
{
  FactoryData *data;
  GstElementFactory *factory;

  GST_DEBUG ("found pad %s:%s of caps %" GST_PTR_FORMAT,
      GST_DEBUG_PAD_NAME (pad), caps);

  data = g_object_get_data (G_OBJECT (element), factory_key);

  if (!(factory = find_payloader (data->factory, caps)))
    goto no_factory;

  /* we found a payloader, stop autoplugging so we can plug the
   * payloader. */
  GST_DEBUG ("found factory %s",
      gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
  gst_object_unref (factory);

  return FALSE;

  /* ERRORS */
no_factory:
  {
    /* no payloader, continue autoplugging */
    GST_DEBUG ("no payloader found");
    return TRUE;
  }
}

static void
pad_added_cb (GstElement * uribin, GstPad * pad, GstElement * element)
{
  GstRTSPMediaFactoryURI *urifact;
  GstRTSPMediaFactoryURIPrivate *priv;
  FactoryData *data;
  GstElementFactory *factory;
  GstElement *payloader;
  GstCaps *caps;
  GstPad *sinkpad, *srcpad, *ghostpad;
  GstElement *convert;
  gchar *padname, *payloader_name;

  GST_DEBUG ("added pad %s:%s", GST_DEBUG_PAD_NAME (pad));

  /* link the element now and expose the pad */
  data = g_object_get_data (G_OBJECT (element), factory_key);
  urifact = data->factory;
  priv = urifact->priv;

  /* ref to make refcounting easier later */
  gst_object_ref (pad);
  padname = gst_pad_get_name (pad);

  /* get pad caps first, then call get_caps, then fail */
  if ((caps = gst_pad_get_current_caps (pad)) == NULL)
    if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
      goto no_caps;

  /* check for raw caps */
  if (gst_caps_can_intersect (caps, priv->raw_vcaps)) {
    /* we have raw video caps, insert converter */
    convert = gst_element_factory_make ("videoconvert", NULL);
  } else if (gst_caps_can_intersect (caps, priv->raw_acaps)) {
    /* we have raw audio caps, insert converter */
    convert = gst_element_factory_make ("audioconvert", NULL);
  } else {
    convert = NULL;
  }

  if (convert) {
    gst_bin_add (GST_BIN_CAST (element), convert);
    gst_element_set_state (convert, GST_STATE_PLAYING);

    sinkpad = gst_element_get_static_pad (convert, "sink");
    gst_pad_link (pad, sinkpad);
    gst_object_unref (sinkpad);

    /* unref old pad, we reffed before */
    gst_object_unref (pad);
    gst_caps_unref (caps);

    /* continue with new pad and caps */
    pad = gst_element_get_static_pad (convert, "src");
    if ((caps = gst_pad_get_current_caps (pad)) == NULL)
      if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
        goto no_caps;
  }

  if (!(factory = find_payloader (urifact, caps)))
    goto no_factory;

  gst_caps_unref (caps);

  /* we have a payloader now */
  GST_DEBUG ("found payloader factory %s",
      gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));

  payloader_name = g_strdup_printf ("pay_%s", padname);
  payloader = gst_element_factory_create (factory, payloader_name);
  g_free (payloader_name);
  if (payloader == NULL)
    goto no_payloader;

  g_object_set (payloader, "pt", data->pt, NULL);
  data->pt++;

  if (g_object_class_find_property (G_OBJECT_GET_CLASS (payloader),
          "buffer-list"))
    g_object_set (payloader, "buffer-list", TRUE, NULL);

  /* add the payloader to the pipeline */
  gst_bin_add (GST_BIN_CAST (element), payloader);
  gst_element_set_state (payloader, GST_STATE_PLAYING);

  /* link the pad to the sinkpad of the payloader */
  sinkpad = gst_element_get_static_pad (payloader, "sink");
  gst_pad_link (pad, sinkpad);
  gst_object_unref (sinkpad);
  gst_object_unref (pad);

  /* now expose the srcpad of the payloader as a ghostpad with the same name
   * as the uridecodebin pad name. */
  srcpad = gst_element_get_static_pad (payloader, "src");
  ghostpad = gst_ghost_pad_new (padname, srcpad);
  gst_object_unref (srcpad);
  g_free (padname);

  gst_pad_set_active (ghostpad, TRUE);
  gst_element_add_pad (element, ghostpad);

  return;

  /* ERRORS */
no_caps:
  {
    GST_WARNING ("could not get caps from pad");
    g_free (padname);
    gst_object_unref (pad);
    return;
  }
no_factory:
  {
    GST_DEBUG ("no payloader found");
    g_free (padname);
    gst_caps_unref (caps);
    gst_object_unref (pad);
    return;
  }
no_payloader:
  {
    GST_ERROR ("could not create payloader from factory");
    g_free (padname);
    gst_caps_unref (caps);
    gst_object_unref (pad);
    return;
  }
}

static void
no_more_pads_cb (GstElement * uribin, GstElement * element)
{
  GST_DEBUG ("no-more-pads");
  gst_element_no_more_pads (element);
}

static GstElement *
rtsp_media_factory_uri_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMediaFactoryURIPrivate *priv;
  GstElement *topbin, *element, *uribin;
  GstRTSPMediaFactoryURI *urifact;
  FactoryData *data;

  urifact = GST_RTSP_MEDIA_FACTORY_URI_CAST (factory);
  priv = urifact->priv;

  GST_LOG ("creating element");

  topbin = gst_bin_new ("GstRTSPMediaFactoryURI");
  g_assert (topbin != NULL);

  /* our bin will dynamically expose payloaded pads */
  element = gst_bin_new ("dynpay0");
  g_assert (element != NULL);

  uribin = gst_element_factory_make ("uridecodebin", "uribin");
  if (uribin == NULL)
    goto no_uridecodebin;

  g_object_set (uribin, "uri", priv->uri, NULL);

  /* keep factory data around */
  data = g_new0 (FactoryData, 1);
  data->factory = g_object_ref (urifact);
  data->pt = 96;

  g_object_set_data_full (G_OBJECT (element), factory_key,
      data, (GDestroyNotify) free_data);

  /* connect to the signals */
  g_signal_connect (uribin, "autoplug-continue",
      (GCallback) autoplug_continue_cb, element);
  g_signal_connect (uribin, "pad-added", (GCallback) pad_added_cb, element);
  g_signal_connect (uribin, "no-more-pads", (GCallback) no_more_pads_cb,
      element);

  gst_bin_add (GST_BIN_CAST (element), uribin);
  gst_bin_add (GST_BIN_CAST (topbin), element);

  return topbin;

no_uridecodebin:
  {
    g_critical ("can't create uridecodebin element");
    gst_object_unref (element);
    return NULL;
  }
}
0707010000003F000081A400000000000000000000000169D554BF00000D83000000000000000000000000000000000000004000000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media-factory-uri.h   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include "rtsp-media-factory.h"

#ifndef __GST_RTSP_MEDIA_FACTORY_URI_H__
#define __GST_RTSP_MEDIA_FACTORY_URI_H__

G_BEGIN_DECLS

/* types for the media factory */
#define GST_TYPE_RTSP_MEDIA_FACTORY_URI              (gst_rtsp_media_factory_uri_get_type ())
#define GST_IS_RTSP_MEDIA_FACTORY_URI(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_URI))
#define GST_IS_RTSP_MEDIA_FACTORY_URI_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA_FACTORY_URI))
#define GST_RTSP_MEDIA_FACTORY_URI_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_URI, GstRTSPMediaFactoryURIClass))
#define GST_RTSP_MEDIA_FACTORY_URI(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_URI, GstRTSPMediaFactoryURI))
#define GST_RTSP_MEDIA_FACTORY_URI_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA_FACTORY_URI, GstRTSPMediaFactoryURIClass))
#define GST_RTSP_MEDIA_FACTORY_URI_CAST(obj)         ((GstRTSPMediaFactoryURI*)(obj))
#define GST_RTSP_MEDIA_FACTORY_URI_CLASS_CAST(klass) ((GstRTSPMediaFactoryURIClass*)(klass))

typedef struct _GstRTSPMediaFactoryURI GstRTSPMediaFactoryURI;
typedef struct _GstRTSPMediaFactoryURIClass GstRTSPMediaFactoryURIClass;
typedef struct _GstRTSPMediaFactoryURIPrivate GstRTSPMediaFactoryURIPrivate;

/**
 * GstRTSPMediaFactoryURI:
 *
 * A media factory that creates a pipeline to play any uri.
 */
struct _GstRTSPMediaFactoryURI {
  GstRTSPMediaFactory   parent;

  /*< private >*/
  GstRTSPMediaFactoryURIPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPMediaFactoryURIClass:
 *
 * The #GstRTSPMediaFactoryURI class structure.
 */
struct _GstRTSPMediaFactoryURIClass {
  GstRTSPMediaFactoryClass  parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_media_factory_uri_get_type   (void);

/* creating the factory */

GST_RTSP_SERVER_API
GstRTSPMediaFactoryURI * gst_rtsp_media_factory_uri_new     (void) G_GNUC_WARN_UNUSED_RESULT;

/* configuring the factory */

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_uri_set_uri  (GstRTSPMediaFactoryURI *factory,
                                                           const gchar *uri);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_media_factory_uri_get_uri  (GstRTSPMediaFactoryURI *factory) G_GNUC_WARN_UNUSED_RESULT;

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMediaFactoryURI, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_MEDIA_FACTORY_URI_H__ */
 07070100000040000081A400000000000000000000000169D554BF0000F875000000000000000000000000000000000000003C00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media-factory.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-media-factory
 * @short_description: A factory for media pipelines
 * @see_also: #GstRTSPMountPoints, #GstRTSPMedia
 *
 * The #GstRTSPMediaFactory is responsible for creating or recycling
 * #GstRTSPMedia objects based on the passed URL.
 *
 * The default implementation of the object can create #GstRTSPMedia objects
 * containing a pipeline created from a launch description set with
 * gst_rtsp_media_factory_set_launch().
 *
 * Media from a factory can be shared by setting the shared flag with
 * gst_rtsp_media_factory_set_shared(). When a factory is shared,
 * gst_rtsp_media_factory_construct() will return the same #GstRTSPMedia when
 * the url matches.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rtsp-server-internal.h"
#include "rtsp-media-factory.h"

#define GST_RTSP_MEDIA_FACTORY_GET_LOCK(f)       (&(GST_RTSP_MEDIA_FACTORY_CAST(f)->priv->lock))
#define GST_RTSP_MEDIA_FACTORY_LOCK(f)           (g_mutex_lock(GST_RTSP_MEDIA_FACTORY_GET_LOCK(f)))
#define GST_RTSP_MEDIA_FACTORY_UNLOCK(f)         (g_mutex_unlock(GST_RTSP_MEDIA_FACTORY_GET_LOCK(f)))

struct _GstRTSPMediaFactoryPrivate
{
  GMutex lock;                  /* protects everything but medias */
  GstRTSPPermissions *permissions;
  gchar *launch;
  gboolean shared;
  GstRTSPSuspendMode suspend_mode;
  gboolean eos_shutdown;
  GstRTSPProfile profiles;
  GstRTSPLowerTrans protocols;
  guint buffer_size;
  gboolean ensure_keyunit_on_start;
  guint ensure_keyunit_on_start_timeout;
  gint dscp_qos;
  GstRTSPAddressPool *pool;
  GstRTSPTransportMode transport_mode;
  gboolean stop_on_disconnect;
  gchar *multicast_iface;
  guint max_mcast_ttl;
  gboolean bind_mcast_address;
  gboolean enable_rtcp;

  GstClockTime rtx_time;
  guint latency;
  gboolean do_retransmission;

  GMutex medias_lock;
  GHashTable *medias;           /* protected by medias_lock */

  GType media_gtype;

  GstClock *clock;

  GstRTSPPublishClockMode publish_clock_mode;
};

#define DEFAULT_LAUNCH          NULL
#define DEFAULT_SHARED          FALSE
#define DEFAULT_SUSPEND_MODE    GST_RTSP_SUSPEND_MODE_NONE
#define DEFAULT_EOS_SHUTDOWN    FALSE
#define DEFAULT_PROFILES        GST_RTSP_PROFILE_AVP
#define DEFAULT_PROTOCOLS       GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
                                        GST_RTSP_LOWER_TRANS_TCP
#define DEFAULT_BUFFER_SIZE     0x80000
#define DEFAULT_ENSURE_KEYUNIT_ON_START FALSE
#define DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT 100
#define DEFAULT_LATENCY         200
#define DEFAULT_MAX_MCAST_TTL   255
#define DEFAULT_BIND_MCAST_ADDRESS FALSE
#define DEFAULT_TRANSPORT_MODE  GST_RTSP_TRANSPORT_MODE_PLAY
#define DEFAULT_STOP_ON_DISCONNECT TRUE
#define DEFAULT_DO_RETRANSMISSION FALSE
#define DEFAULT_DSCP_QOS        (-1)
#define DEFAULT_ENABLE_RTCP     TRUE

enum
{
  PROP_0,
  PROP_LAUNCH,
  PROP_SHARED,
  PROP_SUSPEND_MODE,
  PROP_EOS_SHUTDOWN,
  PROP_PROFILES,
  PROP_PROTOCOLS,
  PROP_BUFFER_SIZE,
  PROP_ENSURE_KEYUNIT_ON_START,
  PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT,
  PROP_LATENCY,
  PROP_TRANSPORT_MODE,
  PROP_STOP_ON_DISCONNECT,
  PROP_CLOCK,
  PROP_MAX_MCAST_TTL,
  PROP_BIND_MCAST_ADDRESS,
  PROP_DSCP_QOS,
  PROP_ENABLE_RTCP,
  PROP_LAST
};

enum
{
  SIGNAL_MEDIA_CONSTRUCTED,
  SIGNAL_MEDIA_CONFIGURE,
  SIGNAL_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug);
#define GST_CAT_DEFAULT rtsp_media_debug

static guint gst_rtsp_media_factory_signals[SIGNAL_LAST] = { 0 };

static void gst_rtsp_media_factory_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_factory_finalize (GObject * obj);

static gchar *default_gen_key (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url);
static GstElement *default_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url);
static GstRTSPMedia *default_construct (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url);
static void default_configure (GstRTSPMediaFactory * factory,
    GstRTSPMedia * media);
static GstElement *default_create_pipeline (GstRTSPMediaFactory * factory,
    GstRTSPMedia * media);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMediaFactory, gst_rtsp_media_factory,
    G_TYPE_OBJECT);

static void
gst_rtsp_media_factory_class_init (GstRTSPMediaFactoryClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_media_factory_get_property;
  gobject_class->set_property = gst_rtsp_media_factory_set_property;
  gobject_class->finalize = gst_rtsp_media_factory_finalize;

  /**
   * GstRTSPMediaFactory::launch:
   *
   * The gst_parse_launch() line to use for constructing the pipeline in the
   * default prepare vmethod.
   *
   * The pipeline description should return a GstBin as the toplevel element
   * which can be accomplished by enclosing the description with brackets '('
   * ')'.
   *
   * The description should return a pipeline with payloaders named pay0, pay1,
   * etc.. Each of the payloaders will result in a stream.
   *
   * Support for dynamic payloaders can be accomplished by adding payloaders
   * named dynpay0, dynpay1, etc..
   */
  g_object_class_install_property (gobject_class, PROP_LAUNCH,
      g_param_spec_string ("launch", "Launch",
          "A launch description of the pipeline", DEFAULT_LAUNCH,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_SHARED,
      g_param_spec_boolean ("shared", "Shared",
          "If media from this factory is shared", DEFAULT_SHARED,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_SUSPEND_MODE,
      g_param_spec_enum ("suspend-mode", "Suspend Mode",
          "Control how media will be suspended", GST_TYPE_RTSP_SUSPEND_MODE,
          DEFAULT_SUSPEND_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_EOS_SHUTDOWN,
      g_param_spec_boolean ("eos-shutdown", "EOS Shutdown",
          "Send EOS down the pipeline before shutting down",
          DEFAULT_EOS_SHUTDOWN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROFILES,
      g_param_spec_flags ("profiles", "Profiles",
          "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
          DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
      g_param_spec_flags ("protocols", "Protocols",
          "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
          DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
      g_param_spec_uint ("buffer-size", "Buffer Size",
          "The kernel UDP buffer size to use", 0, G_MAXUINT,
          DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPMediaFactory:ensure-keyunit-on-start:
   *
   * If media from this factory should ensure a key unit when a client connects.
   *
   * This property will ensure that the stream always starts on a key unit
   * instead of a delta unit which the client would not be able to decode.
   *
   * Note that this will only affect non-shared medias for now.
   *
   * Since: 1.24
   */
  g_object_class_install_property (gobject_class, PROP_ENSURE_KEYUNIT_ON_START,
      g_param_spec_boolean ("ensure-keyunit-on-start",
          "Ensure keyunit on start",
          "If media from this factory should ensure a key unit when a client "
          "connects.",
          DEFAULT_ENSURE_KEYUNIT_ON_START,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPMediaFactory:ensure-keyunit-on-start-timeout:
   *
   * Timeout in milliseconds used to determine if a keyunit should be discarded
   * when a client connects.
   *
   * If the timeout has been reached a new keyframe will be forced, otherwise
   * the currently blocking keyframe will be used.
   *
   * This options is only relevant when ensure-keyunit-on-start is enabled.
   *
   * Since: 1.24
   */
  g_object_class_install_property (gobject_class,
      PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT,
      g_param_spec_uint ("ensure-keyunit-on-start-timeout",
          "Timeout for discarding old keyunit on start",
          "Timeout in milliseconds used to determine if a keyunit should be "
          "discarded when a client connects.", 0, G_MAXUINT,
          DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_LATENCY,
      g_param_spec_uint ("latency", "Latency",
          "Latency used for receiving media in milliseconds", 0, G_MAXUINT,
          DEFAULT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TRANSPORT_MODE,
      g_param_spec_flags ("transport-mode", "Transport Mode",
          "If media from this factory is for PLAY or RECORD",
          GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT,
      g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect",
          "If media from this factory should be stopped "
          "when a client disconnects without TEARDOWN",
          DEFAULT_STOP_ON_DISCONNECT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_CLOCK,
      g_param_spec_object ("clock", "Clock",
          "Clock to be used by the pipelines created for all "
          "medias of this factory", GST_TYPE_CLOCK,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_MAX_MCAST_TTL,
      g_param_spec_uint ("max-mcast-ttl", "Maximum multicast ttl",
          "The maximum time-to-live value of outgoing multicast packets", 1,
          255, DEFAULT_MAX_MCAST_TTL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_BIND_MCAST_ADDRESS,
      g_param_spec_boolean ("bind-mcast-address", "Bind mcast address",
          "Whether the multicast sockets should be bound to multicast addresses "
          "or INADDR_ANY",
          DEFAULT_BIND_MCAST_ADDRESS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPMediaFactory:enable-rtcp:
   *
   * Whether the created media should send and receive RTCP
   *
   * Since: 1.20
   */
  g_object_class_install_property (gobject_class, PROP_ENABLE_RTCP,
      g_param_spec_boolean ("enable-rtcp", "Enable RTCP",
          "Whether the created media should send and receive RTCP",
          DEFAULT_ENABLE_RTCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_DSCP_QOS,
      g_param_spec_int ("dscp-qos", "DSCP QoS",
          "The IP DSCP field to use", -1, 63,
          DEFAULT_DSCP_QOS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_rtsp_media_factory_signals[SIGNAL_MEDIA_CONSTRUCTED] =
      g_signal_new ("media-constructed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaFactoryClass,
          media_constructed), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_MEDIA);

  gst_rtsp_media_factory_signals[SIGNAL_MEDIA_CONFIGURE] =
      g_signal_new ("media-configure", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaFactoryClass,
          media_configure), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_MEDIA);

  klass->gen_key = default_gen_key;
  klass->create_element = default_create_element;
  klass->construct = default_construct;
  klass->configure = default_configure;
  klass->create_pipeline = default_create_pipeline;

  GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmediafactory", 0,
      "GstRTSPMediaFactory");
}

static void
gst_rtsp_media_factory_init (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv =
      gst_rtsp_media_factory_get_instance_private (factory);
  factory->priv = priv;

  priv->launch = g_strdup (DEFAULT_LAUNCH);
  priv->shared = DEFAULT_SHARED;
  priv->suspend_mode = DEFAULT_SUSPEND_MODE;
  priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
  priv->profiles = DEFAULT_PROFILES;
  priv->protocols = DEFAULT_PROTOCOLS;
  priv->buffer_size = DEFAULT_BUFFER_SIZE;
  priv->ensure_keyunit_on_start = DEFAULT_ENSURE_KEYUNIT_ON_START;
  priv->ensure_keyunit_on_start_timeout =
      DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT;
  priv->latency = DEFAULT_LATENCY;
  priv->transport_mode = DEFAULT_TRANSPORT_MODE;
  priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
  priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
  priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
  priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
  priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
  priv->enable_rtcp = DEFAULT_ENABLE_RTCP;
  priv->dscp_qos = DEFAULT_DSCP_QOS;

  g_mutex_init (&priv->lock);
  g_mutex_init (&priv->medias_lock);
  priv->medias = g_hash_table_new_full (g_str_hash, g_str_equal,
      g_free, g_object_unref);
  priv->media_gtype = GST_TYPE_RTSP_MEDIA;
}

static void
gst_rtsp_media_factory_finalize (GObject * obj)
{
  GstRTSPMediaFactory *factory = GST_RTSP_MEDIA_FACTORY (obj);
  GstRTSPMediaFactoryPrivate *priv = factory->priv;

  if (priv->clock)
    gst_object_unref (priv->clock);
  if (priv->permissions)
    gst_rtsp_permissions_unref (priv->permissions);
  g_hash_table_unref (priv->medias);
  g_mutex_clear (&priv->medias_lock);
  g_free (priv->launch);
  g_mutex_clear (&priv->lock);
  if (priv->pool)
    g_object_unref (priv->pool);
  g_free (priv->multicast_iface);

  G_OBJECT_CLASS (gst_rtsp_media_factory_parent_class)->finalize (obj);
}

static void
gst_rtsp_media_factory_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactory *factory = GST_RTSP_MEDIA_FACTORY (object);

  switch (propid) {
    case PROP_LAUNCH:
      g_value_take_string (value, gst_rtsp_media_factory_get_launch (factory));
      break;
    case PROP_SHARED:
      g_value_set_boolean (value, gst_rtsp_media_factory_is_shared (factory));
      break;
    case PROP_SUSPEND_MODE:
      g_value_set_enum (value,
          gst_rtsp_media_factory_get_suspend_mode (factory));
      break;
    case PROP_EOS_SHUTDOWN:
      g_value_set_boolean (value,
          gst_rtsp_media_factory_is_eos_shutdown (factory));
      break;
    case PROP_PROFILES:
      g_value_set_flags (value, gst_rtsp_media_factory_get_profiles (factory));
      break;
    case PROP_PROTOCOLS:
      g_value_set_flags (value, gst_rtsp_media_factory_get_protocols (factory));
      break;
    case PROP_BUFFER_SIZE:
      g_value_set_uint (value,
          gst_rtsp_media_factory_get_buffer_size (factory));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START:
      g_value_set_boolean (value,
          gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT:
      g_value_set_uint (value,
          gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout (factory));
      break;
    case PROP_LATENCY:
      g_value_set_uint (value, gst_rtsp_media_factory_get_latency (factory));
      break;
    case PROP_TRANSPORT_MODE:
      g_value_set_flags (value,
          gst_rtsp_media_factory_get_transport_mode (factory));
      break;
    case PROP_STOP_ON_DISCONNECT:
      g_value_set_boolean (value,
          gst_rtsp_media_factory_is_stop_on_disonnect (factory));
      break;
    case PROP_CLOCK:
      g_value_take_object (value, gst_rtsp_media_factory_get_clock (factory));
      break;
    case PROP_MAX_MCAST_TTL:
      g_value_set_uint (value,
          gst_rtsp_media_factory_get_max_mcast_ttl (factory));
      break;
    case PROP_BIND_MCAST_ADDRESS:
      g_value_set_boolean (value,
          gst_rtsp_media_factory_is_bind_mcast_address (factory));
      break;
    case PROP_DSCP_QOS:
      g_value_set_int (value, gst_rtsp_media_factory_get_dscp_qos (factory));
      break;
    case PROP_ENABLE_RTCP:
      g_value_set_boolean (value,
          gst_rtsp_media_factory_is_enable_rtcp (factory));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_media_factory_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPMediaFactory *factory = GST_RTSP_MEDIA_FACTORY (object);

  switch (propid) {
    case PROP_LAUNCH:
      gst_rtsp_media_factory_set_launch (factory, g_value_get_string (value));
      break;
    case PROP_SHARED:
      gst_rtsp_media_factory_set_shared (factory, g_value_get_boolean (value));
      break;
    case PROP_SUSPEND_MODE:
      gst_rtsp_media_factory_set_suspend_mode (factory,
          g_value_get_enum (value));
      break;
    case PROP_EOS_SHUTDOWN:
      gst_rtsp_media_factory_set_eos_shutdown (factory,
          g_value_get_boolean (value));
      break;
    case PROP_PROFILES:
      gst_rtsp_media_factory_set_profiles (factory, g_value_get_flags (value));
      break;
    case PROP_PROTOCOLS:
      gst_rtsp_media_factory_set_protocols (factory, g_value_get_flags (value));
      break;
    case PROP_BUFFER_SIZE:
      gst_rtsp_media_factory_set_buffer_size (factory,
          g_value_get_uint (value));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START:
      gst_rtsp_media_factory_set_ensure_keyunit_on_start (factory,
          g_value_get_boolean (value));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT:
      gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout (factory,
          g_value_get_uint (value));
      break;
    case PROP_LATENCY:
      gst_rtsp_media_factory_set_latency (factory, g_value_get_uint (value));
      break;
    case PROP_TRANSPORT_MODE:
      gst_rtsp_media_factory_set_transport_mode (factory,
          g_value_get_flags (value));
      break;
    case PROP_STOP_ON_DISCONNECT:
      gst_rtsp_media_factory_set_stop_on_disconnect (factory,
          g_value_get_boolean (value));
      break;
    case PROP_CLOCK:
      gst_rtsp_media_factory_set_clock (factory, g_value_get_object (value));
      break;
    case PROP_MAX_MCAST_TTL:
      gst_rtsp_media_factory_set_max_mcast_ttl (factory,
          g_value_get_uint (value));
      break;
    case PROP_BIND_MCAST_ADDRESS:
      gst_rtsp_media_factory_set_bind_mcast_address (factory,
          g_value_get_boolean (value));
      break;
    case PROP_DSCP_QOS:
      gst_rtsp_media_factory_set_dscp_qos (factory, g_value_get_int (value));
      break;
    case PROP_ENABLE_RTCP:
      gst_rtsp_media_factory_set_enable_rtcp (factory,
          g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_media_factory_new:
 *
 * Create a new #GstRTSPMediaFactory instance.
 *
 * Returns: (transfer full): a new #GstRTSPMediaFactory object.
 */
GstRTSPMediaFactory *
gst_rtsp_media_factory_new (void)
{
  GstRTSPMediaFactory *result;

  result = g_object_new (GST_TYPE_RTSP_MEDIA_FACTORY, NULL);

  return result;
}

/**
 * gst_rtsp_media_factory_set_permissions:
 * @factory: a #GstRTSPMediaFactory
 * @permissions: (transfer none) (nullable): a #GstRTSPPermissions
 *
 * Set @permissions on @factory.
 */
void
gst_rtsp_media_factory_set_permissions (GstRTSPMediaFactory * factory,
    GstRTSPPermissions * permissions)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if (priv->permissions)
    gst_rtsp_permissions_unref (priv->permissions);
  if ((priv->permissions = permissions))
    gst_rtsp_permissions_ref (permissions);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_permissions:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the permissions object from @factory.
 *
 * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage.
 */
GstRTSPPermissions *
gst_rtsp_media_factory_get_permissions (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPPermissions *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if ((result = priv->permissions))
    gst_rtsp_permissions_ref (result);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_add_role:
 * @factory: a #GstRTSPMediaFactory
 * @role: a role
 * @fieldname: the first field name
 * @...: additional arguments
 *
 * A convenience method to add @role with @fieldname and additional arguments to
 * the permissions of @factory. If @factory had no permissions, new permissions
 * will be created and the role will be added to it.
 */
void
gst_rtsp_media_factory_add_role (GstRTSPMediaFactory * factory,
    const gchar * role, const gchar * fieldname, ...)
{
  GstRTSPMediaFactoryPrivate *priv;
  va_list var_args;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
  g_return_if_fail (role != NULL);
  g_return_if_fail (fieldname != NULL);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if (priv->permissions == NULL)
    priv->permissions = gst_rtsp_permissions_new ();

  va_start (var_args, fieldname);
  gst_rtsp_permissions_add_role_valist (priv->permissions, role, fieldname,
      var_args);
  va_end (var_args);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_add_role_from_structure:
 *
 * A convenience wrapper around gst_rtsp_permissions_add_role_from_structure().
 * If @factory had no permissions, new permissions will be created and the
 * role will be added to it.
 *
 * Since: 1.14
 */
void
gst_rtsp_media_factory_add_role_from_structure (GstRTSPMediaFactory * factory,
    GstStructure * structure)
{
  GstRTSPMediaFactoryPrivate *priv;
  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
  g_return_if_fail (GST_IS_STRUCTURE (structure));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if (priv->permissions == NULL)
    priv->permissions = gst_rtsp_permissions_new ();

  gst_rtsp_permissions_add_role_from_structure (priv->permissions, structure);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_set_launch:
 * @factory: a #GstRTSPMediaFactory
 * @launch: the launch description
 *
 *
 * The gst_parse_launch() line to use for constructing the pipeline in the
 * default prepare vmethod.
 *
 * The pipeline description should return a GstBin as the toplevel element
 * which can be accomplished by enclosing the description with brackets '('
 * ')'.
 *
 * The description should return a pipeline with payloaders named pay0, pay1,
 * etc.. Each of the payloaders will result in a stream.
 */
void
gst_rtsp_media_factory_set_launch (GstRTSPMediaFactory * factory,
    const gchar * launch)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
  g_return_if_fail (launch != NULL);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  g_free (priv->launch);
  priv->launch = g_strdup (launch);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_launch:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the gst_parse_launch() pipeline description that will be used in the
 * default prepare vmethod.
 *
 * Returns: (transfer full) (nullable): the configured launch description. g_free() after
 * usage.
 */
gchar *
gst_rtsp_media_factory_get_launch (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = g_strdup (priv->launch);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_suspend_mode:
 * @factory: a #GstRTSPMediaFactory
 * @mode: the new #GstRTSPSuspendMode
 *
 * Configure how media created from this factory will be suspended.
 */
void
gst_rtsp_media_factory_set_suspend_mode (GstRTSPMediaFactory * factory,
    GstRTSPSuspendMode mode)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->suspend_mode = mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_suspend_mode:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get how media created from this factory will be suspended.
 *
 * Returns: a #GstRTSPSuspendMode.
 */
GstRTSPSuspendMode
gst_rtsp_media_factory_get_suspend_mode (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPSuspendMode result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory),
      GST_RTSP_SUSPEND_MODE_NONE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->suspend_mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_shared:
 * @factory: a #GstRTSPMediaFactory
 * @shared: the new value
 *
 * Configure if media created from this factory can be shared between clients.
 */
void
gst_rtsp_media_factory_set_shared (GstRTSPMediaFactory * factory,
    gboolean shared)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->shared = shared;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_is_shared:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get if media created from this factory can be shared between clients.
 *
 * Returns: %TRUE if the media will be shared between clients.
 */
gboolean
gst_rtsp_media_factory_is_shared (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->shared;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_eos_shutdown:
 * @factory: a #GstRTSPMediaFactory
 * @eos_shutdown: the new value
 *
 * Configure if media created from this factory will have an EOS sent to the
 * pipeline before shutdown.
 */
void
gst_rtsp_media_factory_set_eos_shutdown (GstRTSPMediaFactory * factory,
    gboolean eos_shutdown)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->eos_shutdown = eos_shutdown;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_is_eos_shutdown:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get if media created from this factory will have an EOS event sent to the
 * pipeline before shutdown.
 *
 * Returns: %TRUE if the media will receive EOS before shutdown.
 */
gboolean
gst_rtsp_media_factory_is_eos_shutdown (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->eos_shutdown;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_buffer_size:
 * @factory: a #GstRTSPMedia
 * @size: the new value
 *
 * Set the kernel UDP buffer size.
 */
void
gst_rtsp_media_factory_set_buffer_size (GstRTSPMediaFactory * factory,
    guint size)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->buffer_size = size;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_buffer_size:
 * @factory: a #GstRTSPMedia
 *
 * Get the kernel UDP buffer size.
 *
 * Returns: the kernel UDP buffer size.
 */
guint
gst_rtsp_media_factory_get_buffer_size (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->buffer_size;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_ensure_keyunit_on_start:
 * @factory: a #GstRTSPMediaFactory
 * @ensure_keyunit_on_start: the new value
 *
 * If media from this factory should ensure a key unit when a client connects.
 *
 * Since: 1.24
 */
void
gst_rtsp_media_factory_set_ensure_keyunit_on_start (GstRTSPMediaFactory *
    factory, gboolean ensure_keyunit_on_start)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->ensure_keyunit_on_start = ensure_keyunit_on_start;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_ensure_keyunit_on_start:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get ensure-keyunit-on-start flag.
 *
 * Returns: The ensure-keyunit-on-start flag.
 *
 * Since: 1.24
 */
gboolean
gst_rtsp_media_factory_get_ensure_keyunit_on_start (GstRTSPMediaFactory *
    factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->ensure_keyunit_on_start;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout:
 * @factory: a #GstRTSPMediaFactory
 * @timeout: the new value
 *
 * Configures medias from this factory to consider keyunits older than timeout
 * to be expired. Expired keyunits will be discarded.
 *
 * Since: 1.24
 */
void
gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory
    * factory, guint timeout)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->ensure_keyunit_on_start_timeout = timeout;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get ensure-keyunit-on-start-timeout time.
 *
 * Returns: The ensure-keyunit-on-start-timeout time.
 *
 * Since: 1.24
 */
guint
gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout (GstRTSPMediaFactory
    * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->ensure_keyunit_on_start_timeout;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_dscp_qos:
 * @factory: a #GstRTSPMediaFactory
 * @dscp_qos: a new dscp qos value (0-63, or -1 to disable)
 *
 * Configure the media dscp qos to @dscp_qos.
 *
 * Since: 1.18
 */
void
gst_rtsp_media_factory_set_dscp_qos (GstRTSPMediaFactory * factory,
    gint dscp_qos)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  if (dscp_qos < -1 || dscp_qos > 63) {
    GST_WARNING_OBJECT (factory, "trying to set illegal dscp qos %d", dscp_qos);
    return;
  }

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->dscp_qos = dscp_qos;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_dscp_qos:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the configured media DSCP QoS.
 *
 * Returns: the media DSCP QoS value or -1 if disabled.
 *
 * Since: 1.18
 */
gint
gst_rtsp_media_factory_get_dscp_qos (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->dscp_qos;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_address_pool:
 * @factory: a #GstRTSPMediaFactory
 * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
 *
 * configure @pool to be used as the address pool of @factory.
 */
void
gst_rtsp_media_factory_set_address_pool (GstRTSPMediaFactory * factory,
    GstRTSPAddressPool * pool)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPAddressPool *old;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if ((old = priv->pool) != pool)
    priv->pool = pool ? g_object_ref (pool) : NULL;
  else
    old = NULL;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_media_factory_get_address_pool:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the #GstRTSPAddressPool used as the address pool of @factory.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @factory. g_object_unref() after
 * usage.
 */
GstRTSPAddressPool *
gst_rtsp_media_factory_get_address_pool (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPAddressPool *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if ((result = priv->pool))
    g_object_ref (result);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_multicast_iface:
 * @factory: a #GstRTSPMediaFactory
 * @multicast_iface: (transfer none) (nullable): a multicast interface name
 *
 * configure @multicast_iface to be used for @factory.
 */
void
gst_rtsp_media_factory_set_multicast_iface (GstRTSPMediaFactory * media_factory,
    const gchar * multicast_iface)
{
  GstRTSPMediaFactoryPrivate *priv;
  gchar *old;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (media_factory));

  priv = media_factory->priv;

  GST_LOG_OBJECT (media_factory, "set multicast interface %s", multicast_iface);

  g_mutex_lock (&priv->lock);
  if ((old = priv->multicast_iface) != multicast_iface)
    priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
  else
    old = NULL;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_free (old);
}

/**
 * gst_rtsp_media_factory_get_multicast_iface:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the multicast interface used for @factory.
 *
 * Returns: (transfer full) (nullable): the multicast interface for @factory. g_free() after
 * usage.
 */
gchar *
gst_rtsp_media_factory_get_multicast_iface (GstRTSPMediaFactory * media_factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (media_factory), NULL);

  priv = media_factory->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->multicast_iface))
    result = g_strdup (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_factory_set_profiles:
 * @factory: a #GstRTSPMediaFactory
 * @profiles: the new flags
 *
 * Configure the allowed profiles for @factory.
 */
void
gst_rtsp_media_factory_set_profiles (GstRTSPMediaFactory * factory,
    GstRTSPProfile profiles)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "profiles %d", profiles);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->profiles = profiles;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_profiles:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the allowed profiles of @factory.
 *
 * Returns: a #GstRTSPProfile
 */
GstRTSPProfile
gst_rtsp_media_factory_get_profiles (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPProfile res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory),
      GST_RTSP_PROFILE_UNKNOWN);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  res = priv->profiles;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return res;
}

/**
 * gst_rtsp_media_factory_set_protocols:
 * @factory: a #GstRTSPMediaFactory
 * @protocols: the new flags
 *
 * Configure the allowed lower transport for @factory.
 */
void
gst_rtsp_media_factory_set_protocols (GstRTSPMediaFactory * factory,
    GstRTSPLowerTrans protocols)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "protocols %d", protocols);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->protocols = protocols;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_protocols:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the allowed protocols of @factory.
 *
 * Returns: a #GstRTSPLowerTrans
 */
GstRTSPLowerTrans
gst_rtsp_media_factory_get_protocols (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPLowerTrans res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory),
      GST_RTSP_LOWER_TRANS_UNKNOWN);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  res = priv->protocols;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return res;
}

/**
 * gst_rtsp_media_factory_set_stop_on_disconnect:
 * @factory: a #GstRTSPMediaFactory
 * @stop_on_disconnect: the new value
 *
 * Configure if media created from this factory should be stopped
 * when a client disconnects without sending TEARDOWN.
 */
void
gst_rtsp_media_factory_set_stop_on_disconnect (GstRTSPMediaFactory * factory,
    gboolean stop_on_disconnect)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->stop_on_disconnect = stop_on_disconnect;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_is_stop_on_disconnect:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get if media created from this factory should be stopped when a client
 * disconnects without sending TEARDOWN.
 *
 * Returns: %TRUE if the media will be stopped when a client disconnects
 *     without sending TEARDOWN.
 */
gboolean
gst_rtsp_media_factory_is_stop_on_disonnect (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), TRUE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->stop_on_disconnect;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_retransmission_time:
 * @factory: a #GstRTSPMediaFactory
 * @time: a #GstClockTime
 *
 * Configure the time to store for possible retransmission
 */
void
gst_rtsp_media_factory_set_retransmission_time (GstRTSPMediaFactory * factory,
    GstClockTime time)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "retransmission time %" G_GUINT64_FORMAT, time);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->rtx_time = time;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_retransmission_time:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the time that is stored for retransmission purposes
 *
 * Returns: a #GstClockTime
 */
GstClockTime
gst_rtsp_media_factory_get_retransmission_time (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstClockTime res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  res = priv->rtx_time;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return res;
}

/**
 * gst_rtsp_media_factory_set_do_retransmission:
 *
 * Set whether retransmission requests will be sent for
 * receiving media
 *
 * Since: 1.16
 */
void
gst_rtsp_media_factory_set_do_retransmission (GstRTSPMediaFactory * factory,
    gboolean do_retransmission)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "Do retransmission %d", do_retransmission);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->do_retransmission = do_retransmission;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_do_retransmission:
 *
 * Returns: Whether retransmission requests will be sent for receiving media
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_factory_get_do_retransmission (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  res = priv->do_retransmission;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return res;
}

/**
 * gst_rtsp_media_factory_set_latency:
 * @factory: a #GstRTSPMediaFactory
 * @latency: latency in milliseconds
 *
 * Configure the latency used for receiving media
 */
void
gst_rtsp_media_factory_set_latency (GstRTSPMediaFactory * factory,
    guint latency)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_DEBUG_OBJECT (factory, "latency %ums", latency);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->latency = latency;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_latency:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the latency that is used for receiving media
 *
 * Returns: latency in milliseconds
 */
guint
gst_rtsp_media_factory_get_latency (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  res = priv->latency;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return res;
}

static gboolean
compare_media (gpointer key, GstRTSPMedia * media1, GstRTSPMedia * media2)
{
  return (media1 == media2);
}

static void
media_unprepared (GstRTSPMedia * media, GWeakRef * ref)
{
  GstRTSPMediaFactory *factory = g_weak_ref_get (ref);
  GstRTSPMediaFactoryPrivate *priv;

  if (!factory)
    return;

  priv = factory->priv;

  g_mutex_lock (&priv->medias_lock);
  g_hash_table_foreach_remove (priv->medias, (GHRFunc) compare_media, media);
  g_mutex_unlock (&priv->medias_lock);

  g_object_unref (factory);
}

static GWeakRef *
weak_ref_new (gpointer obj)
{
  GWeakRef *ref = g_new (GWeakRef, 1);

  g_weak_ref_init (ref, obj);
  return ref;
}

static void
weak_ref_free (GWeakRef * ref)
{
  g_weak_ref_clear (ref);
  g_free (ref);
}

/**
 * gst_rtsp_media_factory_construct:
 * @factory: a #GstRTSPMediaFactory
 * @url: the url used
 *
 * Construct the media object and create its streams. Implementations
 * should create the needed gstreamer elements and add them to the result
 * object. No state changes should be performed on them yet.
 *
 * One or more GstRTSPStream objects should be created from the result
 * with gst_rtsp_media_create_stream ().
 *
 * After the media is constructed, it can be configured and then prepared
 * with gst_rtsp_media_prepare ().
 *
 * The returned media will be locked and must be unlocked afterwards.
 *
 * Returns: (transfer full) (nullable): a new #GstRTSPMedia if the media could be prepared.
 */
GstRTSPMedia *
gst_rtsp_media_factory_construct (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMediaFactoryPrivate *priv;
  gchar *key;
  GstRTSPMedia *media = NULL;
  GstRTSPMediaFactoryClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);
  g_return_val_if_fail (url != NULL, NULL);

  priv = factory->priv;
  klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory);

  /* convert the url to a key for the hashtable. NULL return or a NULL function
   * will not cache anything for this factory. */
  if (klass->gen_key)
    key = klass->gen_key (factory, url);
  else
    key = NULL;

  g_mutex_lock (&priv->medias_lock);
  if (key) {
    /* we have a key, see if we find a cached media */
    media = g_hash_table_lookup (priv->medias, key);
    if (media) {
      g_object_ref (media);
      g_mutex_unlock (&priv->medias_lock);

      /* now need to check if the media is curently in the process of being
       * unprepared. That always happens while the lock is taken, so take it
       * here now and then check if we can really use the media */
      gst_rtsp_media_lock (media);
      if (!gst_rtsp_media_can_be_shared (media)) {
        gst_rtsp_media_unlock (media);
        g_object_unref (media);
        media = NULL;
      }

      if (media) {
        if (key)
          g_free (key);

        GST_INFO ("reusing cached media %p for url %s", media, url->abspath);

        return media;
      }

      g_mutex_lock (&priv->medias_lock);
    }
  }

  /* nothing cached found, try to create one */
  if (klass->construct) {
    media = klass->construct (factory, url);
    if (media)
      g_signal_emit (factory,
          gst_rtsp_media_factory_signals[SIGNAL_MEDIA_CONSTRUCTED], 0, media,
          NULL);
  }

  if (media) {
    gst_rtsp_media_lock (media);

    /* configure the media */
    if (klass->configure)
      klass->configure (factory, media);

    g_signal_emit (factory,
        gst_rtsp_media_factory_signals[SIGNAL_MEDIA_CONFIGURE], 0, media, NULL);

    /* check if we can cache this media */
    if (gst_rtsp_media_is_shared (media) && key) {
      /* insert in the hashtable, takes ownership of the key */
      g_object_ref (media);
      g_hash_table_insert (priv->medias, key, media);
      key = NULL;
    }
    if (!gst_rtsp_media_is_reusable (media)) {
      /* when not reusable, connect to the unprepare signal to remove the item
       * from our cache when it gets unprepared */
      g_signal_connect_data (media, "unprepared",
          (GCallback) media_unprepared, weak_ref_new (factory),
          (GClosureNotify) weak_ref_free, 0);
    }
  }
  g_mutex_unlock (&priv->medias_lock);

  if (key)
    g_free (key);

  GST_INFO ("constructed media %p for url %s", media, url->abspath);

  return media;
}

/**
 * gst_rtsp_media_factory_set_media_gtype:
 * @factory: a #GstRTSPMediaFactory
 * @media_gtype: the GType of the class to create
 *
 * Configure the GType of the GstRTSPMedia subclass to
 * create (by default, overridden construct vmethods
 * may of course do something different)
 *
 * Since: 1.6
 */
void
gst_rtsp_media_factory_set_media_gtype (GstRTSPMediaFactory * factory,
    GType media_gtype)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (g_type_is_a (media_gtype, GST_TYPE_RTSP_MEDIA));

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv = factory->priv;
  priv->media_gtype = media_gtype;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_media_gtype:
 * @factory: a #GstRTSPMediaFactory
 *
 * Return the GType of the GstRTSPMedia subclass this
 * factory will create.
 *
 * Since: 1.6
 */
GType
gst_rtsp_media_factory_get_media_gtype (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GType ret;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv = factory->priv;
  ret = priv->media_gtype;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return ret;
}

/**
 * gst_rtsp_media_factory_set_clock:
 * @factory: a #GstRTSPMediaFactory
 * @clock: (nullable): the clock to be used by the media factory
 *
 * Configures a specific clock to be used by the pipelines
 * of all medias created from this factory.
 *
 * Since: 1.8
 */
void
gst_rtsp_media_factory_set_clock (GstRTSPMediaFactory * factory,
    GstClock * clock)
{
  GstClock **clock_p;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
  g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  clock_p = &factory->priv->clock;
  gst_object_replace ((GstObject **) clock_p, (GstObject *) clock);
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_clock:
 * @factory: a #GstRTSPMediaFactory
 *
 * Returns the clock that is going to be used by the pipelines
 * of all medias created from this factory.
 *
 * Returns: (transfer full) (nullable): The GstClock
 *
 * Since: 1.8
 */
GstClock *
gst_rtsp_media_factory_get_clock (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstClock *ret;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv = factory->priv;
  ret = priv->clock ? gst_object_ref (priv->clock) : NULL;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return ret;
}

/**
 * gst_rtsp_media_factory_set_publish_clock_mode:
 * @factory: a #GstRTSPMediaFactory
 * @mode: the clock publish mode
 *
 * Sets if and how the media clock should be published according to RFC7273.
 *
 * Since: 1.8
 */
void
gst_rtsp_media_factory_set_publish_clock_mode (GstRTSPMediaFactory * factory,
    GstRTSPPublishClockMode mode)
{
  GstRTSPMediaFactoryPrivate *priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv = factory->priv;
  priv->publish_clock_mode = mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_publish_clock_mode:
 * @factory: a #GstRTSPMediaFactory
 *
 * Gets if and how the media clock should be published according to RFC7273.
 *
 * Returns: The GstRTSPPublishClockMode
 *
 * Since: 1.8
 */
GstRTSPPublishClockMode
gst_rtsp_media_factory_get_publish_clock_mode (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPPublishClockMode ret;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv = factory->priv;
  ret = priv->publish_clock_mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return ret;
}

/**
 * gst_rtsp_media_factory_set_max_mcast_ttl:
 * @factory: a #GstRTSPMedia
 * @ttl: the new multicast ttl value
 *
 * Set the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: %TRUE if the requested ttl has been set successfully.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_factory_set_max_mcast_ttl (GstRTSPMediaFactory * factory,
    guint ttl)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
    GST_WARNING_OBJECT (factory, "The requested mcast TTL value is not valid.");
    GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
    return FALSE;
  }
  priv->max_mcast_ttl = ttl;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return TRUE;
}

/**
 * gst_rtsp_media_factory_get_max_mcast_ttl:
 * @factory: a #GstRTSPMedia
 *
 * Get the the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: the maximum time-to-live value of outgoing multicast packets.
 *
 * Since: 1.16
 */
guint
gst_rtsp_media_factory_get_max_mcast_ttl (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), 0);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->max_mcast_ttl;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_bind_mcast_address:
 * @factory: a #GstRTSPMediaFactory
 * @bind_mcast_addr: the new value
 *
 * Decide whether the multicast socket should be bound to a multicast address or
 * INADDR_ANY.
 *
 * Since: 1.16
 */
void
gst_rtsp_media_factory_set_bind_mcast_address (GstRTSPMediaFactory * factory,
    gboolean bind_mcast_addr)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->bind_mcast_address = bind_mcast_addr;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_is_bind_mcast_address:
 * @factory: a #GstRTSPMediaFactory
 *
 * Check if multicast sockets are configured to be bound to multicast addresses.
 *
 * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_factory_is_bind_mcast_address (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->bind_mcast_address;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

/**
 * gst_rtsp_media_factory_set_enable_rtcp:
 * @factory: a #GstRTSPMediaFactory
 * @enable: the new value
 *
 * Decide whether the created media should send and receive RTCP
 *
 * Since: 1.20
 */
void
gst_rtsp_media_factory_set_enable_rtcp (GstRTSPMediaFactory * factory,
    gboolean enable)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->enable_rtcp = enable;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_is_enable_rtcp:
 * @factory: a #GstRTSPMediaFactory
 *
 * Check if created media will send and receive RTCP
 *
 * Returns: %TRUE if created media will send and receive RTCP
 *
 * Since: 1.20
 */
gboolean
gst_rtsp_media_factory_is_enable_rtcp (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->enable_rtcp;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}

static gchar *
default_gen_key (GstRTSPMediaFactory * factory, const GstRTSPUrl * url)
{
  gchar *result;
  const gchar *pre_query;
  const gchar *query;
  guint16 port;

  pre_query = url->query ? "?" : "";
  query = url->query ? url->query : "";

  gst_rtsp_url_get_port (url, &port);

  result = g_strdup_printf ("%u%s%s%s", port, url->abspath, pre_query, query);

  return result;
}

static GstElement *
default_create_element (GstRTSPMediaFactory * factory, const GstRTSPUrl * url)
{
  GstRTSPMediaFactoryPrivate *priv = factory->priv;
  GstElement *element;
  GError *error = NULL;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  /* we need a parse syntax */
  if (priv->launch == NULL)
    goto no_launch;

  /* parse the user provided launch line */
  element =
      gst_parse_launch_full (priv->launch, NULL, GST_PARSE_FLAG_PLACE_IN_BIN,
      &error);
  if (element == NULL)
    goto parse_error;

  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  if (error != NULL) {
    /* a recoverable error was encountered */
    GST_WARNING ("recoverable parsing error: %s", error->message);
    g_error_free (error);
  }
  return element;

  /* ERRORS */
no_launch:
  {
    GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
    g_critical ("no launch line specified");
    return NULL;
  }
parse_error:
  {
    g_critical ("could not parse launch syntax (%s): %s", priv->launch,
        (error ? error->message : "unknown reason"));
    GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
    if (error)
      g_error_free (error);
    return NULL;
  }
}

static GstRTSPMedia *
default_construct (GstRTSPMediaFactory * factory, const GstRTSPUrl * url)
{
  GstRTSPMedia *media;
  GstElement *element, *pipeline;
  GstRTSPMediaFactoryClass *klass;
  GType media_gtype;
  gboolean enable_rtcp;

  klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory);

  if (!klass->create_pipeline)
    goto no_create;

  element = gst_rtsp_media_factory_create_element (factory, url);
  if (element == NULL)
    goto no_element;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  media_gtype = factory->priv->media_gtype;
  enable_rtcp = factory->priv->enable_rtcp;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  /* create a new empty media */
  media =
      g_object_new (media_gtype, "element", element, "transport-mode",
      factory->priv->transport_mode, NULL);

  /* We need to call this prior to collecting streams */
  gst_rtsp_media_set_enable_rtcp (media, enable_rtcp);
  gst_rtsp_media_set_ensure_keyunit_on_start (media,
      gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory));

  gst_rtsp_media_collect_streams (media);

  pipeline = klass->create_pipeline (factory, media);
  if (pipeline == NULL)
    goto no_pipeline;

  return media;

  /* ERRORS */
no_create:
  {
    g_critical ("no create_pipeline function");
    return NULL;
  }
no_element:
  {
    g_critical ("could not create element");
    return NULL;
  }
no_pipeline:
  {
    g_critical ("can't create pipeline");
    g_object_unref (media);
    return NULL;
  }
}

static GstElement *
default_create_pipeline (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
{
  GstElement *pipeline;

  pipeline = gst_pipeline_new ("media-pipeline");

  /* FIXME 2.0: This should be done by the caller, not the vfunc. Every
   * implementation of the vfunc has to call it otherwise at the end.
   * Also it does not allow use to add further behaviour here that could
   * be reused by subclasses that chain up */
  gst_rtsp_media_take_pipeline (media, GST_PIPELINE_CAST (pipeline));

  return pipeline;
}

static void
default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
{
  GstRTSPMediaFactoryPrivate *priv = factory->priv;
  gboolean shared, eos_shutdown, stop_on_disconnect;
  guint size;
  gboolean ensure_keyunit_on_start;
  guint ensure_keyunit_on_start_timeout;
  gint dscp_qos;
  GstRTSPSuspendMode suspend_mode;
  GstRTSPProfile profiles;
  GstRTSPLowerTrans protocols;
  GstRTSPAddressPool *pool;
  GstRTSPPermissions *perms;
  GstClockTime rtx_time;
  guint latency;
  GstRTSPTransportMode transport_mode;
  GstClock *clock;
  gchar *multicast_iface;
  GstRTSPPublishClockMode publish_clock_mode;
  guint ttl;
  gboolean bind_mcast;

  /* configure the sharedness */
  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  suspend_mode = priv->suspend_mode;
  shared = priv->shared;
  eos_shutdown = priv->eos_shutdown;
  size = priv->buffer_size;
  ensure_keyunit_on_start = priv->ensure_keyunit_on_start;
  ensure_keyunit_on_start_timeout = priv->ensure_keyunit_on_start_timeout;
  dscp_qos = priv->dscp_qos;
  profiles = priv->profiles;
  protocols = priv->protocols;
  rtx_time = priv->rtx_time;
  latency = priv->latency;
  transport_mode = priv->transport_mode;
  stop_on_disconnect = priv->stop_on_disconnect;
  clock = priv->clock ? gst_object_ref (priv->clock) : NULL;
  publish_clock_mode = priv->publish_clock_mode;
  ttl = priv->max_mcast_ttl;
  bind_mcast = priv->bind_mcast_address;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  gst_rtsp_media_set_suspend_mode (media, suspend_mode);
  gst_rtsp_media_set_shared (media, shared);
  gst_rtsp_media_set_eos_shutdown (media, eos_shutdown);
  gst_rtsp_media_set_buffer_size (media, size);
  gst_rtsp_media_set_ensure_keyunit_on_start (media, ensure_keyunit_on_start);
  gst_rtsp_media_set_ensure_keyunit_on_start_timeout (media,
      ensure_keyunit_on_start_timeout);
  gst_rtsp_media_set_dscp_qos (media, dscp_qos);
  gst_rtsp_media_set_profiles (media, profiles);
  gst_rtsp_media_set_protocols (media, protocols);
  gst_rtsp_media_set_retransmission_time (media, rtx_time);
  gst_rtsp_media_set_do_retransmission (media, priv->do_retransmission);
  gst_rtsp_media_set_latency (media, latency);
  gst_rtsp_media_set_transport_mode (media, transport_mode);
  gst_rtsp_media_set_stop_on_disconnect (media, stop_on_disconnect);
  gst_rtsp_media_set_publish_clock_mode (media, publish_clock_mode);
  gst_rtsp_media_set_max_mcast_ttl (media, ttl);
  gst_rtsp_media_set_bind_mcast_address (media, bind_mcast);

  if (clock) {
    gst_rtsp_media_set_clock (media, clock);
    gst_object_unref (clock);
  }

  if ((pool = gst_rtsp_media_factory_get_address_pool (factory))) {
    gst_rtsp_media_set_address_pool (media, pool);
    g_object_unref (pool);
  }
  if ((multicast_iface = gst_rtsp_media_factory_get_multicast_iface (factory))) {
    gst_rtsp_media_set_multicast_iface (media, multicast_iface);
    g_free (multicast_iface);
  }
  if ((perms = gst_rtsp_media_factory_get_permissions (factory))) {
    gst_rtsp_media_set_permissions (media, perms);
    gst_rtsp_permissions_unref (perms);
  }
}

/**
 * gst_rtsp_media_factory_create_element:
 * @factory: a #GstRTSPMediaFactory
 * @url: the url used
 *
 * Construct and return a #GstElement that is a #GstBin containing
 * the elements to use for streaming the media.
 *
 * The bin should contain payloaders pay\%d for each stream. The default
 * implementation of this function returns the bin created from the
 * launch parameter.
 *
 * Returns: (transfer floating) (nullable): a new #GstElement.
 */
GstElement *
gst_rtsp_media_factory_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMediaFactoryClass *klass;
  GstElement *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), NULL);
  g_return_val_if_fail (url != NULL, NULL);

  klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory);

  if (klass->create_element)
    result = klass->create_element (factory, url);
  else
    result = NULL;

  return result;
}

/**
 * gst_rtsp_media_factory_set_transport_mode:
 * @factory: a #GstRTSPMediaFactory
 * @mode: the new value
 *
 * Configure if this factory creates media for PLAY or RECORD modes.
 */
void
gst_rtsp_media_factory_set_transport_mode (GstRTSPMediaFactory * factory,
    GstRTSPTransportMode mode)
{
  GstRTSPMediaFactoryPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  priv->transport_mode = mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
}

/**
 * gst_rtsp_media_factory_get_transport_mode:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get if media created from this factory can be used for PLAY or RECORD
 * methods.
 *
 * Returns: The transport mode.
 */
GstRTSPTransportMode
gst_rtsp_media_factory_get_transport_mode (GstRTSPMediaFactory * factory)
{
  GstRTSPMediaFactoryPrivate *priv;
  GstRTSPTransportMode result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);

  priv = factory->priv;

  GST_RTSP_MEDIA_FACTORY_LOCK (factory);
  result = priv->transport_mode;
  GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);

  return result;
}
   07070100000041000081A400000000000000000000000169D554BF00003639000000000000000000000000000000000000003C00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media-factory.h   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp/gstrtspurl.h>

#include "rtsp-media.h"
#include "rtsp-permissions.h"
#include "rtsp-address-pool.h"

#ifndef __GST_RTSP_MEDIA_FACTORY_H__
#define __GST_RTSP_MEDIA_FACTORY_H__

G_BEGIN_DECLS

/* types for the media factory */
#define GST_TYPE_RTSP_MEDIA_FACTORY              (gst_rtsp_media_factory_get_type ())
#define GST_IS_RTSP_MEDIA_FACTORY(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA_FACTORY))
#define GST_IS_RTSP_MEDIA_FACTORY_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA_FACTORY))
#define GST_RTSP_MEDIA_FACTORY_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA_FACTORY, GstRTSPMediaFactoryClass))
#define GST_RTSP_MEDIA_FACTORY(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA_FACTORY, GstRTSPMediaFactory))
#define GST_RTSP_MEDIA_FACTORY_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA_FACTORY, GstRTSPMediaFactoryClass))
#define GST_RTSP_MEDIA_FACTORY_CAST(obj)         ((GstRTSPMediaFactory*)(obj))
#define GST_RTSP_MEDIA_FACTORY_CLASS_CAST(klass) ((GstRTSPMediaFactoryClass*)(klass))

typedef struct _GstRTSPMediaFactory GstRTSPMediaFactory;
typedef struct _GstRTSPMediaFactoryClass GstRTSPMediaFactoryClass;
typedef struct _GstRTSPMediaFactoryPrivate GstRTSPMediaFactoryPrivate;

/**
 * GstRTSPMediaFactory:
 *
 * The definition and logic for constructing the pipeline for a media. The media
 * can contain multiple streams like audio and video.
 */
struct _GstRTSPMediaFactory {
  GObject            parent;

  /*< private >*/
  GstRTSPMediaFactoryPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPMediaFactoryClass:
 * @gen_key: convert @url to a key for caching shared #GstRTSPMedia objects.
 *       The default implementation of this function will use the complete URL
 *       including the query parameters to return a key.
 * @create_element: Construct and return a #GstElement that is a #GstBin containing
 *       the elements to use for streaming the media. The bin should contain
 *       payloaders pay\%d for each stream. The default implementation of this
 *       function returns the bin created from the launch parameter.
 * @construct: the vmethod that will be called when the factory has to create the
 *       #GstRTSPMedia for @url. The default implementation of this
 *       function calls create_element to retrieve an element and then looks for
 *       pay\%d to create the streams.
 * @create_pipeline: create a new pipeline or re-use an existing one and
 *       add the #GstRTSPMedia's element created by @construct to the pipeline.
 * @configure: configure the media created with @construct. The default
 *       implementation will configure the 'shared' property of the media.
 * @media_constructed: signal emitted when a media was constructed
 * @media_configure: signal emitted when a media should be configured
 *
 * The #GstRTSPMediaFactory class structure.
 */
struct _GstRTSPMediaFactoryClass {
  GObjectClass  parent_class;

  gchar *         (*gen_key)            (GstRTSPMediaFactory *factory, const GstRTSPUrl *url);

  GstElement *    (*create_element)     (GstRTSPMediaFactory *factory, const GstRTSPUrl *url);
  GstRTSPMedia *  (*construct)          (GstRTSPMediaFactory *factory, const GstRTSPUrl *url);
  GstElement *    (*create_pipeline)    (GstRTSPMediaFactory *factory, GstRTSPMedia *media);
  void            (*configure)          (GstRTSPMediaFactory *factory, GstRTSPMedia *media);

  /* signals */
  void            (*media_constructed)  (GstRTSPMediaFactory *factory, GstRTSPMedia *media);
  void            (*media_configure)    (GstRTSPMediaFactory *factory, GstRTSPMedia *media);

  /*< private >*/
  gpointer         _gst_reserved[GST_PADDING_LARGE];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_media_factory_get_type     (void);

/* creating the factory */

GST_RTSP_SERVER_API
GstRTSPMediaFactory * gst_rtsp_media_factory_new          (void) G_GNUC_WARN_UNUSED_RESULT;

/* configuring the factory */

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_launch       (GstRTSPMediaFactory *factory,
                                                               const gchar *launch);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_media_factory_get_launch       (GstRTSPMediaFactory *factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_permissions  (GstRTSPMediaFactory *factory,
                                                               GstRTSPPermissions *permissions);

GST_RTSP_SERVER_API
GstRTSPPermissions *  gst_rtsp_media_factory_get_permissions  (GstRTSPMediaFactory *factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_add_role         (GstRTSPMediaFactory *factory,
                                                               const gchar *role,
                                                               const gchar *fieldname, ...);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_add_role_from_structure (GstRTSPMediaFactory * factory,
                                                               GstStructure *structure);
GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_shared       (GstRTSPMediaFactory *factory,
                                                               gboolean shared);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_is_shared        (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_stop_on_disconnect       (GstRTSPMediaFactory *factory,
                                                                           gboolean stop_on_disconnect);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_is_stop_on_disonnect        (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_suspend_mode (GstRTSPMediaFactory *factory,
                                                               GstRTSPSuspendMode mode);

GST_RTSP_SERVER_API
GstRTSPSuspendMode    gst_rtsp_media_factory_get_suspend_mode (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_eos_shutdown (GstRTSPMediaFactory *factory,
                                                               gboolean eos_shutdown);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_is_eos_shutdown  (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_profiles     (GstRTSPMediaFactory *factory,
                                                               GstRTSPProfile profiles);

GST_RTSP_SERVER_API
GstRTSPProfile        gst_rtsp_media_factory_get_profiles     (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_protocols    (GstRTSPMediaFactory *factory,
                                                               GstRTSPLowerTrans protocols);

GST_RTSP_SERVER_API
GstRTSPLowerTrans     gst_rtsp_media_factory_get_protocols    (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_address_pool (GstRTSPMediaFactory * factory,
                                                               GstRTSPAddressPool * pool);

GST_RTSP_SERVER_API
GstRTSPAddressPool *  gst_rtsp_media_factory_get_address_pool (GstRTSPMediaFactory * factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_multicast_iface (GstRTSPMediaFactory *factory, const gchar *multicast_iface);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_media_factory_get_multicast_iface (GstRTSPMediaFactory *factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_buffer_size  (GstRTSPMediaFactory * factory,
                                                               guint size);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_factory_get_buffer_size  (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_ensure_keyunit_on_start  (GstRTSPMediaFactory * factory,
                                                                           gboolean ensure_keyunit_on_start);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_get_ensure_keyunit_on_start  (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_ensure_keyunit_on_start_timeout  (GstRTSPMediaFactory * factory,
                                                                                   guint timeout);

GST_RTSP_SERVER_API
guint          gst_rtsp_media_factory_get_ensure_keyunit_on_start_timeout  (GstRTSPMediaFactory * factory);


GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_retransmission_time (GstRTSPMediaFactory * factory,
                                                                      GstClockTime time);

GST_RTSP_SERVER_API
GstClockTime          gst_rtsp_media_factory_get_retransmission_time (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_do_retransmission (GstRTSPMediaFactory * factory,
                                                                    gboolean do_retransmission);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_get_do_retransmission (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_latency      (GstRTSPMediaFactory * factory,
                                                               guint                 latency);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_factory_get_latency      (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_transport_mode (GstRTSPMediaFactory *factory,
                                                                 GstRTSPTransportMode mode);

GST_RTSP_SERVER_API
GstRTSPTransportMode  gst_rtsp_media_factory_get_transport_mode (GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_media_gtype  (GstRTSPMediaFactory * factory,
                                                               GType media_gtype);

GST_RTSP_SERVER_API
GType                 gst_rtsp_media_factory_get_media_gtype  (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_clock        (GstRTSPMediaFactory *factory,
                                                               GstClock * clock);

GST_RTSP_SERVER_API
GstClock *            gst_rtsp_media_factory_get_clock        (GstRTSPMediaFactory *factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                    gst_rtsp_media_factory_set_publish_clock_mode (GstRTSPMediaFactory * factory, GstRTSPPublishClockMode mode);

GST_RTSP_SERVER_API
GstRTSPPublishClockMode gst_rtsp_media_factory_get_publish_clock_mode (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
gboolean                gst_rtsp_media_factory_set_max_mcast_ttl (GstRTSPMediaFactory * factory,
                                                                  guint                 ttl);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_factory_get_max_mcast_ttl (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_bind_mcast_address (GstRTSPMediaFactory * factory,
                                                                     gboolean bind_mcast_addr);
GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_is_bind_mcast_address (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_dscp_qos (GstRTSPMediaFactory * factory,
                                                           gint dscp_qos);
GST_RTSP_SERVER_API
gint                  gst_rtsp_media_factory_get_dscp_qos (GstRTSPMediaFactory * factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_factory_set_enable_rtcp (GstRTSPMediaFactory * factory,
                                                              gboolean enable);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_factory_is_enable_rtcp (GstRTSPMediaFactory * factory);

/* creating the media from the factory and a url */

GST_RTSP_SERVER_API
GstRTSPMedia *        gst_rtsp_media_factory_construct        (GstRTSPMediaFactory *factory,
                                                               const GstRTSPUrl *url) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstElement *          gst_rtsp_media_factory_create_element   (GstRTSPMediaFactory *factory,
                                                               const GstRTSPUrl *url);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMediaFactory, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_MEDIA_FACTORY_H__ */
   07070100000042000081A400000000000000000000000169D554BF00025674000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-media
 * @short_description: The media pipeline
 * @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession,
 *     #GstRTSPSessionMedia
 *
 * a #GstRTSPMedia contains the complete GStreamer pipeline to manage the
 * streaming to the clients. The actual data transfer is done by the
 * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
 *
 * The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the
 * client does a DESCRIBE or SETUP of a resource.
 *
 * A media is created with gst_rtsp_media_new() that takes the element that will
 * provide the streaming elements. For each of the streams, a new #GstRTSPStream
 * object needs to be made with the gst_rtsp_media_create_stream() which takes
 * the payloader element and the source pad that produces the RTP stream.
 *
 * The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The
 * prepare method will add rtpbin and sinks and sources to send and receive RTP
 * and RTCP packets from the clients. Each stream srcpad is connected to an
 * input into the internal rtpbin.
 *
 * It is also possible to dynamically create #GstRTSPStream objects during the
 * prepare phase. With gst_rtsp_media_get_status() you can check the status of
 * the prepare phase.
 *
 * After the media is prepared, it is ready for streaming. It will usually be
 * managed in a session with gst_rtsp_session_manage_media(). See
 * #GstRTSPSession and #GstRTSPSessionMedia.
 *
 * The state of the media can be controlled with gst_rtsp_media_set_state ().
 * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full()
 * or gst_rtsp_media_seek_trickmode() for finer control of the seek.
 *
 * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When
 * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to
 * cleanly shut down.
 *
 * With gst_rtsp_media_set_shared(), the media can be shared between multiple
 * clients. With gst_rtsp_media_set_reusable() you can control if the pipeline
 * can be prepared again after an unprepare.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>

#include <gst/sdp/gstmikey.h>
#include <gst/rtp/gstrtppayloads.h>

#include <gst/video/video-event.h>

#define AES_128_KEY_LEN 16
#define AES_256_KEY_LEN 32

#define HMAC_32_KEY_LEN 4
#define HMAC_80_KEY_LEN 10

#include "rtsp-media.h"
#include "rtsp-server-internal.h"

struct _GstRTSPMediaPrivate
{
  GMutex lock;
  GCond cond;

  /* the global lock is used to lock the entire media. This is needed by callers
     such as rtsp_client to protect the media when it is shared by many clients.
     The lock prevents that concurrenting clients messes up media.
     Typically the lock is taken in external API calls such as SETUP */
  GMutex global_lock;

  /* protected by lock */
  GstRTSPPermissions *permissions;
  gboolean shared;
  gboolean suspend_mode;
  gboolean reusable;
  GstRTSPProfile profiles;
  GstRTSPLowerTrans protocols;
  gboolean reused;
  gboolean eos_shutdown;
  guint buffer_size;
  gboolean ensure_keyunit_on_start;
  guint ensure_keyunit_on_start_timeout;
  gboolean keyunit_is_expired;  /* if the blocking keyunit has expired */
  GSource *keyunit_expiration_source;
  gint dscp_qos;
  GstRTSPAddressPool *pool;
  gchar *multicast_iface;
  guint max_mcast_ttl;
  gboolean bind_mcast_address;
  gboolean enable_rtcp;
  gboolean blocked;
  GstRTSPTransportMode transport_mode;
  gboolean stop_on_disconnect;
  guint blocking_msg_received;

  GstElement *element;
  GRecMutex state_lock;         /* locking order: state lock, lock */
  GPtrArray *streams;           /* protected by lock */
  GList *dynamic;               /* protected by lock */
  GstRTSPMediaStatus status;    /* protected by lock */
  gint prepare_count;
  gint n_active;
  gboolean complete;
  gboolean finishing_unprepare;

  /* the pipeline for the media */
  GstElement *pipeline;
  GSource *source;
  GstRTSPThread *thread;
  GList *pending_pipeline_elements;

  gboolean time_provider;
  GstNetTimeProvider *nettime;

  gboolean is_live;
  GstClockTimeDiff seekable;
  gboolean buffering;
  GstState target_state;

  /* RTP session manager */
  GstElement *rtpbin;

  /* the range of media */
  GstRTSPTimeRange range;       /* protected by lock */
  GstClockTime range_start;
  GstClockTime range_stop;

  GList *payloads;              /* protected by lock */
  GstClockTime rtx_time;        /* protected by lock */
  gboolean do_retransmission;   /* protected by lock */
  guint latency;                /* protected by lock */
  GstClock *clock;              /* protected by lock */
  gboolean do_rate_control;     /* protected by lock */
  GstRTSPPublishClockMode publish_clock_mode;

  /* Dynamic element handling */
  guint nb_dynamic_elements;
  guint no_more_pads_pending;
  gboolean expected_async_done;
};

#define DEFAULT_SHARED          FALSE
#define DEFAULT_SUSPEND_MODE    GST_RTSP_SUSPEND_MODE_NONE
#define DEFAULT_REUSABLE        FALSE
#define DEFAULT_PROFILES        GST_RTSP_PROFILE_AVP
#define DEFAULT_PROTOCOLS       GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
                                        GST_RTSP_LOWER_TRANS_TCP
#define DEFAULT_EOS_SHUTDOWN    FALSE
#define DEFAULT_BUFFER_SIZE     0x80000
#define DEFAULT_ENSURE_KEYUNIT_ON_START FALSE
#define DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT 100
#define DEFAULT_DSCP_QOS        (-1)
#define DEFAULT_TIME_PROVIDER   FALSE
#define DEFAULT_LATENCY         200
#define DEFAULT_TRANSPORT_MODE  GST_RTSP_TRANSPORT_MODE_PLAY
#define DEFAULT_STOP_ON_DISCONNECT TRUE
#define DEFAULT_MAX_MCAST_TTL   255
#define DEFAULT_BIND_MCAST_ADDRESS FALSE
#define DEFAULT_DO_RATE_CONTROL TRUE
#define DEFAULT_ENABLE_RTCP     TRUE

#define DEFAULT_DO_RETRANSMISSION FALSE

/* define to dump received RTCP packets */
#undef DUMP_STATS

enum
{
  PROP_0,
  PROP_SHARED,
  PROP_SUSPEND_MODE,
  PROP_REUSABLE,
  PROP_PROFILES,
  PROP_PROTOCOLS,
  PROP_EOS_SHUTDOWN,
  PROP_BUFFER_SIZE,
  PROP_ENSURE_KEYUNIT_ON_START,
  PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT,
  PROP_ELEMENT,
  PROP_TIME_PROVIDER,
  PROP_LATENCY,
  PROP_TRANSPORT_MODE,
  PROP_STOP_ON_DISCONNECT,
  PROP_CLOCK,
  PROP_MAX_MCAST_TTL,
  PROP_BIND_MCAST_ADDRESS,
  PROP_DSCP_QOS,
  PROP_LAST
};

enum
{
  SIGNAL_NEW_STREAM,
  SIGNAL_REMOVED_STREAM,
  SIGNAL_PREPARED,
  SIGNAL_UNPREPARED,
  SIGNAL_TARGET_STATE,
  SIGNAL_NEW_STATE,
  SIGNAL_HANDLE_MESSAGE,
  SIGNAL_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug);
#define GST_CAT_DEFAULT rtsp_media_debug

static void gst_rtsp_media_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_media_finalize (GObject * obj);

static gboolean default_handle_message (GstRTSPMedia * media,
    GstMessage * message);
static void finish_unprepare (GstRTSPMedia * media);
static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread);
static gboolean default_unprepare (GstRTSPMedia * media);
static gboolean default_suspend (GstRTSPMedia * media);
static gboolean default_unsuspend (GstRTSPMedia * media);
static gboolean default_convert_range (GstRTSPMedia * media,
    GstRTSPTimeRange * range, GstRTSPRangeUnit unit);
static gboolean default_query_position (GstRTSPMedia * media,
    gint64 * position);
static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop);
static GstElement *default_create_rtpbin (GstRTSPMedia * media);
static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
    GstSDPInfo * info);
static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);

static gboolean wait_preroll (GstRTSPMedia * media);

static GstElement *find_payload_element (GstElement * payloader, GstPad * pad);

static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 };

static gboolean check_complete (GstRTSPMedia * media);

#define C_ENUM(v) ((gint) v)

#define TRICKMODE_FLAGS (GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED)

GType
gst_rtsp_suspend_mode_get_type (void)
{
  static gsize id = 0;
  static const GEnumValue values[] = {
    {C_ENUM (GST_RTSP_SUSPEND_MODE_NONE), "GST_RTSP_SUSPEND_MODE_NONE", "none"},
    {C_ENUM (GST_RTSP_SUSPEND_MODE_PAUSE), "GST_RTSP_SUSPEND_MODE_PAUSE",
        "pause"},
    {C_ENUM (GST_RTSP_SUSPEND_MODE_RESET), "GST_RTSP_SUSPEND_MODE_RESET",
        "reset"},
    {0, NULL, NULL}
  };

  if (g_once_init_enter (&id)) {
    GType tmp = g_enum_register_static ("GstRTSPSuspendMode", values);
    g_once_init_leave (&id, tmp);
  }
  return (GType) id;
}

#define C_FLAGS(v) ((guint) v)

GType
gst_rtsp_transport_mode_get_type (void)
{
  static gsize id = 0;
  static const GFlagsValue values[] = {
    {C_FLAGS (GST_RTSP_TRANSPORT_MODE_PLAY), "GST_RTSP_TRANSPORT_MODE_PLAY",
        "play"},
    {C_FLAGS (GST_RTSP_TRANSPORT_MODE_RECORD), "GST_RTSP_TRANSPORT_MODE_RECORD",
        "record"},
    {0, NULL, NULL}
  };

  if (g_once_init_enter (&id)) {
    GType tmp = g_flags_register_static ("GstRTSPTransportMode", values);
    g_once_init_leave (&id, tmp);
  }
  return (GType) id;
}

GType
gst_rtsp_publish_clock_mode_get_type (void)
{
  static gsize id = 0;
  static const GEnumValue values[] = {
    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE),
        "GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"},
    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK),
          "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK",
        "clock"},
    {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET),
          "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET",
        "clock-and-offset"},
    {0, NULL, NULL}
  };

  if (g_once_init_enter (&id)) {
    GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values);
    g_once_init_leave (&id, tmp);
  }
  return (GType) id;
}

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);

static void
gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_media_get_property;
  gobject_class->set_property = gst_rtsp_media_set_property;
  gobject_class->finalize = gst_rtsp_media_finalize;

  g_object_class_install_property (gobject_class, PROP_SHARED,
      g_param_spec_boolean ("shared", "Shared",
          "If this media pipeline can be shared", DEFAULT_SHARED,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_SUSPEND_MODE,
      g_param_spec_enum ("suspend-mode", "Suspend Mode",
          "How to suspend the media in PAUSED", GST_TYPE_RTSP_SUSPEND_MODE,
          DEFAULT_SUSPEND_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_REUSABLE,
      g_param_spec_boolean ("reusable", "Reusable",
          "If this media pipeline can be reused after an unprepare",
          DEFAULT_REUSABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROFILES,
      g_param_spec_flags ("profiles", "Profiles",
          "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
          DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
      g_param_spec_flags ("protocols", "Protocols",
          "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
          DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_EOS_SHUTDOWN,
      g_param_spec_boolean ("eos-shutdown", "EOS Shutdown",
          "Send an EOS event to the pipeline before unpreparing",
          DEFAULT_EOS_SHUTDOWN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
      g_param_spec_uint ("buffer-size", "Buffer Size",
          "The kernel UDP buffer size to use", 0, G_MAXUINT,
          DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

/**
 * GstRTSPMedia:ensure-keyunit-on-start:
 *
 * Whether or not a keyunit should be ensured when a client connects. It
 * will also configure the streams to drop delta units to ensure that they start
 * on a keyunit.
 *
 * Note that this will only affect non-shared medias for now.
 *
 * Since: 1.24
 */
  g_object_class_install_property (gobject_class, PROP_ENSURE_KEYUNIT_ON_START,
      g_param_spec_boolean ("ensure-keyunit-on-start",
          "Ensure keyunit on start",
          "Whether the stream will ensure a keyunit when a client connects.",
          DEFAULT_ENSURE_KEYUNIT_ON_START,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

/**
 * GstRTSPMedia:ensure-keyunit-on-start-timeout:
 *
 * The maximum allowed time before the first keyunit is considered
 * expired.
 *
 * Note that this will only have an effect when ensure-keyunit-on-start is
 * enabled.
 *
 * Since: 1.24
 */
  g_object_class_install_property (gobject_class,
      PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT,
      g_param_spec_uint ("ensure-keyunit-on-start-timeout",
          "Timeout for discarding old keyunit on start",
          "Timeout in milliseconds used to determine if a keyunit should be "
          "discarded when a client connects.", 0, G_MAXUINT,
          DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_ELEMENT,
      g_param_spec_object ("element", "The Element",
          "The GstBin to use for streaming the media", GST_TYPE_ELEMENT,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TIME_PROVIDER,
      g_param_spec_boolean ("time-provider", "Time Provider",
          "Use a NetTimeProvider for clients",
          DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_LATENCY,
      g_param_spec_uint ("latency", "Latency",
          "Latency used for receiving media in milliseconds", 0, G_MAXUINT,
          DEFAULT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TRANSPORT_MODE,
      g_param_spec_flags ("transport-mode", "Transport Mode",
          "If this media pipeline can be used for PLAY or RECORD",
          GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT,
      g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect",
          "If this media pipeline should be stopped "
          "when a client disconnects without TEARDOWN",
          DEFAULT_STOP_ON_DISCONNECT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_CLOCK,
      g_param_spec_object ("clock", "Clock",
          "Clock to be used by the media pipeline",
          GST_TYPE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_MAX_MCAST_TTL,
      g_param_spec_uint ("max-mcast-ttl", "Maximum multicast ttl",
          "The maximum time-to-live value of outgoing multicast packets", 1,
          255, DEFAULT_MAX_MCAST_TTL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_BIND_MCAST_ADDRESS,
      g_param_spec_boolean ("bind-mcast-address", "Bind mcast address",
          "Whether the multicast sockets should be bound to multicast addresses "
          "or INADDR_ANY",
          DEFAULT_BIND_MCAST_ADDRESS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_DSCP_QOS,
      g_param_spec_int ("dscp-qos", "DSCP QoS",
          "The IP DSCP field to use for each related stream", -1, 63,
          DEFAULT_DSCP_QOS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
      g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM);

  gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM] =
      g_signal_new ("removed-stream", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, removed_stream),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM);

  gst_rtsp_media_signals[SIGNAL_PREPARED] =
      g_signal_new ("prepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPMediaClass, prepared), NULL, NULL, NULL,
      G_TYPE_NONE, 0, G_TYPE_NONE);

  gst_rtsp_media_signals[SIGNAL_UNPREPARED] =
      g_signal_new ("unprepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPMediaClass, unprepared), NULL, NULL, NULL,
      G_TYPE_NONE, 0, G_TYPE_NONE);

  gst_rtsp_media_signals[SIGNAL_TARGET_STATE] =
      g_signal_new ("target-state", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, target_state),
      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);

  gst_rtsp_media_signals[SIGNAL_NEW_STATE] =
      g_signal_new ("new-state", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET (GstRTSPMediaClass, new_state), NULL, NULL, NULL,
      G_TYPE_NONE, 1, G_TYPE_INT);

  /**
   * GstRTSPMedia::handle-message:
   * @media: a #GstRTSPMedia
   * @message: a #GstMessage
   *
   * Will be emitted when a message appears on the pipeline bus.
   *
   * Returns: a #gboolean indicating if the call was successful or not.
   *
   * Since: 1.22
   */
  gst_rtsp_media_signals[SIGNAL_HANDLE_MESSAGE] =
      g_signal_new ("handle-message", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, G_STRUCT_OFFSET (GstRTSPMediaClass,
          handle_message), NULL, NULL, NULL, G_TYPE_BOOLEAN, 1,
      GST_TYPE_MESSAGE);

  GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmedia", 0, "GstRTSPMedia");

  klass->handle_message = default_handle_message;
  klass->prepare = default_prepare;
  klass->unprepare = default_unprepare;
  klass->suspend = default_suspend;
  klass->unsuspend = default_unsuspend;
  klass->convert_range = default_convert_range;
  klass->query_position = default_query_position;
  klass->query_stop = default_query_stop;
  klass->create_rtpbin = default_create_rtpbin;
  klass->setup_sdp = default_setup_sdp;
  klass->handle_sdp = default_handle_sdp;
}

static void
gst_rtsp_media_init (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = gst_rtsp_media_get_instance_private (media);

  media->priv = priv;

  priv->streams = g_ptr_array_new_with_free_func (g_object_unref);
  g_mutex_init (&priv->lock);
  g_mutex_init (&priv->global_lock);
  g_cond_init (&priv->cond);
  g_rec_mutex_init (&priv->state_lock);

  priv->shared = DEFAULT_SHARED;
  priv->suspend_mode = DEFAULT_SUSPEND_MODE;
  priv->reusable = DEFAULT_REUSABLE;
  priv->profiles = DEFAULT_PROFILES;
  priv->protocols = DEFAULT_PROTOCOLS;
  priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
  priv->buffer_size = DEFAULT_BUFFER_SIZE;
  priv->ensure_keyunit_on_start = DEFAULT_ENSURE_KEYUNIT_ON_START;
  priv->ensure_keyunit_on_start_timeout =
      DEFAULT_ENSURE_KEYUNIT_ON_START_TIMEOUT;
  priv->keyunit_is_expired = FALSE;
  priv->keyunit_expiration_source = NULL;
  priv->time_provider = DEFAULT_TIME_PROVIDER;
  priv->transport_mode = DEFAULT_TRANSPORT_MODE;
  priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
  priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
  priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
  priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
  priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
  priv->enable_rtcp = DEFAULT_ENABLE_RTCP;
  priv->do_rate_control = DEFAULT_DO_RATE_CONTROL;
  priv->dscp_qos = DEFAULT_DSCP_QOS;
  priv->expected_async_done = FALSE;
  priv->blocking_msg_received = 0;
}

static void
gst_rtsp_media_finalize (GObject * obj)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPMedia *media;

  media = GST_RTSP_MEDIA (obj);
  priv = media->priv;

  GST_INFO ("finalize media %p", media);

  if (priv->permissions)
    gst_rtsp_permissions_unref (priv->permissions);

  g_ptr_array_unref (priv->streams);

  g_list_free_full (priv->dynamic, gst_object_unref);
  g_list_free_full (priv->pending_pipeline_elements, gst_object_unref);

  if (priv->pipeline)
    gst_object_unref (priv->pipeline);
  if (priv->nettime)
    gst_object_unref (priv->nettime);
  gst_object_unref (priv->element);
  if (priv->pool)
    g_object_unref (priv->pool);
  if (priv->payloads)
    g_list_free (priv->payloads);
  if (priv->clock)
    gst_object_unref (priv->clock);
  g_free (priv->multicast_iface);
  g_mutex_clear (&priv->lock);
  g_mutex_clear (&priv->global_lock);
  g_cond_clear (&priv->cond);
  g_rec_mutex_clear (&priv->state_lock);

  if (priv->keyunit_expiration_source != NULL) {
    g_source_destroy (priv->keyunit_expiration_source);
    g_source_unref (priv->keyunit_expiration_source);
    priv->keyunit_expiration_source = NULL;
  }

  G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj);
}

static void
gst_rtsp_media_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPMedia *media = GST_RTSP_MEDIA (object);

  switch (propid) {
    case PROP_ELEMENT:
      g_value_set_object (value, media->priv->element);
      break;
    case PROP_SHARED:
      g_value_set_boolean (value, gst_rtsp_media_is_shared (media));
      break;
    case PROP_SUSPEND_MODE:
      g_value_set_enum (value, gst_rtsp_media_get_suspend_mode (media));
      break;
    case PROP_REUSABLE:
      g_value_set_boolean (value, gst_rtsp_media_is_reusable (media));
      break;
    case PROP_PROFILES:
      g_value_set_flags (value, gst_rtsp_media_get_profiles (media));
      break;
    case PROP_PROTOCOLS:
      g_value_set_flags (value, gst_rtsp_media_get_protocols (media));
      break;
    case PROP_EOS_SHUTDOWN:
      g_value_set_boolean (value, gst_rtsp_media_is_eos_shutdown (media));
      break;
    case PROP_BUFFER_SIZE:
      g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START:
      g_value_set_boolean (value,
          gst_rtsp_media_get_ensure_keyunit_on_start (media));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT:
      g_value_set_uint (value,
          gst_rtsp_media_get_ensure_keyunit_on_start_timeout (media));
      break;
    case PROP_TIME_PROVIDER:
      g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media));
      break;
    case PROP_LATENCY:
      g_value_set_uint (value, gst_rtsp_media_get_latency (media));
      break;
    case PROP_TRANSPORT_MODE:
      g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media));
      break;
    case PROP_STOP_ON_DISCONNECT:
      g_value_set_boolean (value, gst_rtsp_media_is_stop_on_disconnect (media));
      break;
    case PROP_CLOCK:
      g_value_take_object (value, gst_rtsp_media_get_clock (media));
      break;
    case PROP_MAX_MCAST_TTL:
      g_value_set_uint (value, gst_rtsp_media_get_max_mcast_ttl (media));
      break;
    case PROP_BIND_MCAST_ADDRESS:
      g_value_set_boolean (value, gst_rtsp_media_is_bind_mcast_address (media));
      break;
    case PROP_DSCP_QOS:
      g_value_set_int (value, gst_rtsp_media_get_dscp_qos (media));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_media_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPMedia *media = GST_RTSP_MEDIA (object);

  switch (propid) {
    case PROP_ELEMENT:
      media->priv->element = g_value_get_object (value);
      gst_object_ref_sink (media->priv->element);
      break;
    case PROP_SHARED:
      gst_rtsp_media_set_shared (media, g_value_get_boolean (value));
      break;
    case PROP_SUSPEND_MODE:
      gst_rtsp_media_set_suspend_mode (media, g_value_get_enum (value));
      break;
    case PROP_REUSABLE:
      gst_rtsp_media_set_reusable (media, g_value_get_boolean (value));
      break;
    case PROP_PROFILES:
      gst_rtsp_media_set_profiles (media, g_value_get_flags (value));
      break;
    case PROP_PROTOCOLS:
      gst_rtsp_media_set_protocols (media, g_value_get_flags (value));
      break;
    case PROP_EOS_SHUTDOWN:
      gst_rtsp_media_set_eos_shutdown (media, g_value_get_boolean (value));
      break;
    case PROP_BUFFER_SIZE:
      gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START:
      gst_rtsp_media_set_ensure_keyunit_on_start (media,
          g_value_get_boolean (value));
      break;
    case PROP_ENSURE_KEYUNIT_ON_START_TIMEOUT:
      gst_rtsp_media_set_ensure_keyunit_on_start_timeout (media,
          g_value_get_uint (value));
      break;
    case PROP_TIME_PROVIDER:
      gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value));
      break;
    case PROP_LATENCY:
      gst_rtsp_media_set_latency (media, g_value_get_uint (value));
      break;
    case PROP_TRANSPORT_MODE:
      gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value));
      break;
    case PROP_STOP_ON_DISCONNECT:
      gst_rtsp_media_set_stop_on_disconnect (media,
          g_value_get_boolean (value));
      break;
    case PROP_CLOCK:
      gst_rtsp_media_set_clock (media, g_value_get_object (value));
      break;
    case PROP_MAX_MCAST_TTL:
      gst_rtsp_media_set_max_mcast_ttl (media, g_value_get_uint (value));
      break;
    case PROP_BIND_MCAST_ADDRESS:
      gst_rtsp_media_set_bind_mcast_address (media,
          g_value_get_boolean (value));
      break;
    case PROP_DSCP_QOS:
      gst_rtsp_media_set_dscp_qos (media, g_value_get_int (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

typedef struct
{
  gint64 position;
  gboolean complete_streams_only;
  gboolean ret;
} DoQueryPositionData;

static void
do_query_position (GstRTSPStream * stream, DoQueryPositionData * data)
{
  gint64 tmp;

  if (!gst_rtsp_stream_is_sender (stream))
    return;

  if (data->complete_streams_only && !gst_rtsp_stream_is_complete (stream)) {
    GST_DEBUG_OBJECT (stream, "stream not complete, do not query position");
    return;
  }

  if (gst_rtsp_stream_query_position (stream, &tmp)) {
    data->position = MIN (data->position, tmp);
    data->ret = TRUE;
  }

  GST_INFO_OBJECT (stream, "media position: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (data->position));
}

static gboolean
default_query_position (GstRTSPMedia * media, gint64 * position)
{
  GstRTSPMediaPrivate *priv;
  DoQueryPositionData data;

  priv = media->priv;

  data.position = G_MAXINT64;
  data.ret = FALSE;

  /* if the media is complete, i.e. one or more streams have been configured
   * with sinks, then we want to query the position on those streams only.
   * a query on an incmplete stream may return a position that originates from
   * an earlier preroll */
  if (check_complete (media))
    data.complete_streams_only = TRUE;
  else
    data.complete_streams_only = FALSE;

  g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data);

  if (!data.ret)
    *position = GST_CLOCK_TIME_NONE;
  else
    *position = data.position;

  return data.ret;
}

typedef struct
{
  gint64 stop;
  gboolean ret;
} DoQueryStopData;

static void
do_query_stop (GstRTSPStream * stream, DoQueryStopData * data)
{
  gint64 tmp = 0;

  if (gst_rtsp_stream_query_stop (stream, &tmp)) {
    data->stop = MAX (data->stop, tmp);
    data->ret = TRUE;
  }
}

static gboolean
default_query_stop (GstRTSPMedia * media, gint64 * stop)
{
  GstRTSPMediaPrivate *priv;
  DoQueryStopData data;

  priv = media->priv;

  data.stop = -1;
  data.ret = FALSE;

  g_ptr_array_foreach (priv->streams, (GFunc) do_query_stop, &data);

  *stop = data.stop;

  return data.ret;
}

static GstElement *
default_create_rtpbin (GstRTSPMedia * media)
{
  GstElement *rtpbin;

  rtpbin = gst_element_factory_make ("rtpbin", NULL);

  return rtpbin;
}

/* Must be called with priv->lock */
static gboolean
is_receive_only (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gboolean receive_only = TRUE;
  guint i;

  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    if (gst_rtsp_stream_is_sender (stream) ||
        !gst_rtsp_stream_is_receiver (stream)) {
      receive_only = FALSE;
      break;
    }
  }

  return receive_only;
}

/* must be called with state lock */
static void
check_seekable (GstRTSPMedia * media)
{
  GstQuery *query;
  GstRTSPMediaPrivate *priv = media->priv;

  g_mutex_lock (&priv->lock);
  /* Update the seekable state of the pipeline in case it changed */
  if (is_receive_only (media)) {
    /* TODO: Seeking for "receive-only"? */
    priv->seekable = -1;
  } else {
    guint i, n = priv->streams->len;

    for (i = 0; i < n; i++) {
      GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);

      if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
          GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
        priv->seekable = -1;
        g_mutex_unlock (&priv->lock);
        return;
      }
    }
  }

  query = gst_query_new_seeking (GST_FORMAT_TIME);
  if (gst_element_query (priv->pipeline, query)) {
    GstFormat format;
    gboolean seekable;
    gint64 start, end;

    gst_query_parse_seeking (query, &format, &seekable, &start, &end);
    priv->seekable = seekable ? G_MAXINT64 : 0;
  } else if (priv->streams->len) {
    gboolean seekable = TRUE;
    guint i, n = priv->streams->len;

    GST_DEBUG_OBJECT (media, "Checking %d streams", n);
    for (i = 0; i < n; i++) {
      GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
      seekable &= gst_rtsp_stream_seekable (stream);
    }
    priv->seekable = seekable ? G_MAXINT64 : -1;
  }

  GST_DEBUG_OBJECT (media, "seekable:%" G_GINT64_FORMAT, priv->seekable);
  g_mutex_unlock (&priv->lock);
  gst_query_unref (query);
}

/* must be called with state lock */
static gboolean
check_complete (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;

  guint i, n = priv->streams->len;

  for (i = 0; i < n; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);

    if (gst_rtsp_stream_is_complete (stream))
      return TRUE;
  }

  return FALSE;
}

/* must be called with state lock and private lock */
static void
collect_media_stats (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gint64 position = 0, stop = -1;

  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
      priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) {
    return;
  }

  priv->range.unit = GST_RTSP_RANGE_NPT;

  GST_INFO ("collect media stats");

  if (priv->is_live) {
    priv->range.min.type = GST_RTSP_TIME_NOW;
    priv->range.min.seconds = -1;
    priv->range_start = -1;
    priv->range.max.type = GST_RTSP_TIME_END;
    priv->range.max.seconds = -1;
    priv->range_stop = -1;
  } else {
    GstRTSPMediaClass *klass;
    gboolean ret;

    klass = GST_RTSP_MEDIA_GET_CLASS (media);

    /* get the position */
    ret = FALSE;
    if (klass->query_position)
      ret = klass->query_position (media, &position);

    if (!ret) {
      GST_INFO ("position query failed");
      position = 0;
    }

    /* get the current segment stop */
    ret = FALSE;
    if (klass->query_stop)
      ret = klass->query_stop (media, &stop);

    if (!ret) {
      GST_INFO ("stop query failed");
      stop = -1;
    }

    GST_INFO ("stats: position %" GST_TIME_FORMAT ", stop %"
        GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (stop));

    if (position == -1) {
      priv->range.min.type = GST_RTSP_TIME_NOW;
      priv->range.min.seconds = -1;
      priv->range_start = -1;
    } else {
      priv->range.min.type = GST_RTSP_TIME_SECONDS;
      priv->range.min.seconds = ((gdouble) position) / GST_SECOND;
      priv->range_start = position;
    }
    if (stop == -1) {
      priv->range.max.type = GST_RTSP_TIME_END;
      priv->range.max.seconds = -1;
      priv->range_stop = -1;
    } else {
      priv->range.max.type = GST_RTSP_TIME_SECONDS;
      priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
      priv->range_stop = stop;
    }
    g_mutex_unlock (&priv->lock);
    check_seekable (media);
    g_mutex_lock (&priv->lock);
  }
}

/**
 * gst_rtsp_media_new:
 * @element: (transfer full): a #GstElement
 *
 * Create a new #GstRTSPMedia instance. @element is the bin element that
 * provides the different streams. The #GstRTSPMedia object contains the
 * element to produce RTP data for one or more related (audio/video/..)
 * streams.
 *
 * Ownership is taken of @element.
 *
 * Returns: (transfer full): a new #GstRTSPMedia object.
 */
GstRTSPMedia *
gst_rtsp_media_new (GstElement * element)
{
  GstRTSPMedia *result;

  g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);

  result = g_object_new (GST_TYPE_RTSP_MEDIA, "element", element, NULL);

  return result;
}

/**
 * gst_rtsp_media_get_element:
 * @media: a #GstRTSPMedia
 *
 * Get the element that was used when constructing @media.
 *
 * Returns: (transfer full): a #GstElement. Unref after usage.
 */
GstElement *
gst_rtsp_media_get_element (GstRTSPMedia * media)
{
  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  return gst_object_ref (media->priv->element);
}

/**
 * gst_rtsp_media_take_pipeline:
 * @media: a #GstRTSPMedia
 * @pipeline: (transfer floating): a #GstPipeline
 *
 * Set @pipeline as the #GstPipeline for @media. Ownership is
 * taken of @pipeline.
 */
void
gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline)
{
  GstRTSPMediaPrivate *priv;
  GstElement *old;
  GstNetTimeProvider *nettime;
  GList *l;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
  g_return_if_fail (GST_IS_PIPELINE (pipeline));

  priv = media->priv;
  GST_DEBUG_OBJECT (media, "Taking pipeline %" GST_PTR_FORMAT, pipeline);

  g_mutex_lock (&priv->lock);
  old = priv->pipeline;
  priv->pipeline = gst_object_ref_sink (GST_ELEMENT_CAST (pipeline));
  nettime = priv->nettime;
  priv->nettime = NULL;
  g_mutex_unlock (&priv->lock);

  if (old)
    gst_object_unref (old);

  if (nettime)
    gst_object_unref (nettime);

  gst_bin_add (GST_BIN_CAST (pipeline), priv->element);

  for (l = priv->pending_pipeline_elements; l; l = l->next) {
    gst_bin_add (GST_BIN_CAST (pipeline), l->data);
  }
  g_list_free (priv->pending_pipeline_elements);
  priv->pending_pipeline_elements = NULL;
}

/**
 * gst_rtsp_media_set_permissions:
 * @media: a #GstRTSPMedia
 * @permissions: (transfer none) (nullable): a #GstRTSPPermissions
 *
 * Set @permissions on @media.
 */
void
gst_rtsp_media_set_permissions (GstRTSPMedia * media,
    GstRTSPPermissions * permissions)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if (priv->permissions)
    gst_rtsp_permissions_unref (priv->permissions);
  if ((priv->permissions = permissions))
    gst_rtsp_permissions_ref (permissions);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_permissions:
 * @media: a #GstRTSPMedia
 *
 * Get the permissions object from @media.
 *
 * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage.
 */
GstRTSPPermissions *
gst_rtsp_media_get_permissions (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPPermissions *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->permissions))
    gst_rtsp_permissions_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_set_suspend_mode:
 * @media: a #GstRTSPMedia
 * @mode: the new #GstRTSPSuspendMode
 *
 * Control how @ media will be suspended after the SDP has been generated and
 * after a PAUSE request has been performed.
 *
 * Media must be unprepared when setting the suspend mode.
 */
void
gst_rtsp_media_set_suspend_mode (GstRTSPMedia * media, GstRTSPSuspendMode mode)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED)
    goto was_prepared;
  priv->suspend_mode = mode;
  g_rec_mutex_unlock (&priv->state_lock);

  return;

  /* ERRORS */
was_prepared:
  {
    GST_WARNING ("media %p was prepared", media);
    g_rec_mutex_unlock (&priv->state_lock);
  }
}

/**
 * gst_rtsp_media_get_suspend_mode:
 * @media: a #GstRTSPMedia
 *
 * Get how @media will be suspended.
 *
 * Returns: #GstRTSPSuspendMode.
 */
GstRTSPSuspendMode
gst_rtsp_media_get_suspend_mode (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPSuspendMode res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_SUSPEND_MODE_NONE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  res = priv->suspend_mode;
  g_rec_mutex_unlock (&priv->state_lock);

  return res;
}

/**
 * gst_rtsp_media_set_shared:
 * @media: a #GstRTSPMedia
 * @shared: the new value
 *
 * Set or unset if the pipeline for @media can be shared will multiple clients.
 * When @shared is %TRUE, client requests for this media will share the media
 * pipeline.
 */
void
gst_rtsp_media_set_shared (GstRTSPMedia * media, gboolean shared)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->shared = shared;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_shared:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media can be shared between multiple clients in
 * theory. This simply returns the value set via gst_rtsp_media_set_shared().
 *
 * To know if a media can be shared in practice, i.e. if it's shareable and
 * either reusable or was never unprepared before, use
 * gst_rtsp_media_can_be_shared().
 *
 * Returns: %TRUE if the media can be shared between clients.
 */
gboolean
gst_rtsp_media_is_shared (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->shared;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_can_be_shared:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media can be shared between multiple clients.
 *
 * This checks if the media is shareable and whether it is either reusable or
 * was never unprepared before.
 *
 * The function must be called with gst_rtsp_media_lock().
 *
 * Returns: %TRUE if the media can be shared between clients.
 *
 * Since: 1.24
 */
gboolean
gst_rtsp_media_can_be_shared (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->shared && (priv->reusable || !priv->reused);
  g_mutex_unlock (&priv->lock);

  return res;
}


/**
 * gst_rtsp_media_set_reusable:
 * @media: a #GstRTSPMedia
 * @reusable: the new value
 *
 * Set or unset if the pipeline for @media can be reused after the pipeline has
 * been unprepared.
 */
void
gst_rtsp_media_set_reusable (GstRTSPMedia * media, gboolean reusable)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->reusable = reusable;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_reusable:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media can be reused after an unprepare.
 *
 * Returns: %TRUE if the media can be reused
 */
gboolean
gst_rtsp_media_is_reusable (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->reusable;
  g_mutex_unlock (&priv->lock);

  return res;
}

static void
do_set_profiles (GstRTSPStream * stream, GstRTSPProfile * profiles)
{
  gst_rtsp_stream_set_profiles (stream, *profiles);
}

/**
 * gst_rtsp_media_set_profiles:
 * @media: a #GstRTSPMedia
 * @profiles: the new flags
 *
 * Configure the allowed lower transport for @media.
 */
void
gst_rtsp_media_set_profiles (GstRTSPMedia * media, GstRTSPProfile profiles)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->profiles = profiles;
  g_ptr_array_foreach (priv->streams, (GFunc) do_set_profiles, &profiles);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_profiles:
 * @media: a #GstRTSPMedia
 *
 * Get the allowed profiles of @media.
 *
 * Returns: a #GstRTSPProfile
 */
GstRTSPProfile
gst_rtsp_media_get_profiles (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPProfile res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_PROFILE_UNKNOWN);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->profiles;
  g_mutex_unlock (&priv->lock);

  return res;
}

static void
do_set_protocols (GstRTSPStream * stream, GstRTSPLowerTrans * protocols)
{
  gst_rtsp_stream_set_protocols (stream, *protocols);
}

/**
 * gst_rtsp_media_set_protocols:
 * @media: a #GstRTSPMedia
 * @protocols: the new flags
 *
 * Configure the allowed lower transport for @media.
 */
void
gst_rtsp_media_set_protocols (GstRTSPMedia * media, GstRTSPLowerTrans protocols)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->protocols = protocols;
  g_ptr_array_foreach (priv->streams, (GFunc) do_set_protocols, &protocols);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_protocols:
 * @media: a #GstRTSPMedia
 *
 * Get the allowed protocols of @media.
 *
 * Returns: a #GstRTSPLowerTrans
 */
GstRTSPLowerTrans
gst_rtsp_media_get_protocols (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPLowerTrans res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media),
      GST_RTSP_LOWER_TRANS_UNKNOWN);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->protocols;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_eos_shutdown:
 * @media: a #GstRTSPMedia
 * @eos_shutdown: the new value
 *
 * Set or unset if an EOS event will be sent to the pipeline for @media before
 * it is unprepared.
 */
void
gst_rtsp_media_set_eos_shutdown (GstRTSPMedia * media, gboolean eos_shutdown)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->eos_shutdown = eos_shutdown;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_eos_shutdown:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media will send an EOS down the pipeline before
 * unpreparing.
 *
 * Returns: %TRUE if the media will send EOS before unpreparing.
 */
gboolean
gst_rtsp_media_is_eos_shutdown (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->eos_shutdown;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_buffer_size:
 * @media: a #GstRTSPMedia
 * @size: the new value
 *
 * Set the kernel UDP buffer size.
 */
void
gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  GST_LOG_OBJECT (media, "set buffer size %u", size);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->buffer_size = size;

  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    gst_rtsp_stream_set_buffer_size (stream, size);
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_buffer_size:
 * @media: a #GstRTSPMedia
 *
 * Get the kernel UDP buffer size.
 *
 * Returns: the kernel UDP buffer size.
 */
guint
gst_rtsp_media_get_buffer_size (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->buffer_size;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_ensure_keyunit_on_start:
 * @media: a #GstRTSPMedia
 * @ensure_keyunit_on_start: the new value
 *
 * Set whether or not a keyunit should be ensured when a client connects. It
 * will also configure the streams to drop delta units to ensure that they start
 * on a keyunit.
 *
 * Note that this will only affect non-shared medias for now.
 *
 * Since: 1.24
 */
void
gst_rtsp_media_set_ensure_keyunit_on_start (GstRTSPMedia * media,
    gboolean ensure_keyunit_on_start)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->ensure_keyunit_on_start = ensure_keyunit_on_start;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_ensure_keyunit_on_start:
 * @media: a #GstRTSPMedia
 *
 * Get ensure-keyunit-on-start flag.
 *
 * Returns: The ensure-keyunit-on-start flag.
 *
 * Since: 1.24
 */
gboolean
gst_rtsp_media_get_ensure_keyunit_on_start (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  result = priv->ensure_keyunit_on_start;
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_set_ensure_keyunit_on_start_timeout:
 * @media: a #GstRTSPMedia
 * @timeout: the new value
 *
 * Sets the maximum allowed time before the first keyunit is considered
 * expired.
 *
 * Note that this will only have an effect when ensure-keyunit-on-start is
 * enabled.
 *
 * Since: 1.24
 */
void
gst_rtsp_media_set_ensure_keyunit_on_start_timeout (GstRTSPMedia * media,
    guint timeout)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->ensure_keyunit_on_start_timeout = timeout;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_ensure_keyunit_on_start_timeout
 * @media: a #GstRTSPMedia
 *
 * Get ensure-keyunit-on-start-timeout time.
 *
 * Returns: The ensure-keyunit-on-start-timeout time.
 *
 * Since: 1.24
 */
guint
gst_rtsp_media_get_ensure_keyunit_on_start_timeout (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  result = priv->ensure_keyunit_on_start_timeout;
  g_mutex_unlock (&priv->lock);

  return result;
}

static void
do_set_dscp_qos (GstRTSPStream * stream, gint * dscp_qos)
{
  gst_rtsp_stream_set_dscp_qos (stream, *dscp_qos);
}

/**
 * gst_rtsp_media_set_dscp_qos:
 * @media: a #GstRTSPMedia
 * @dscp_qos: a new dscp qos value (0-63, or -1 to disable)
 *
 * Configure the dscp qos of attached streams to @dscp_qos.
 *
 * Since: 1.18
 */
void
gst_rtsp_media_set_dscp_qos (GstRTSPMedia * media, gint dscp_qos)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  GST_LOG_OBJECT (media, "set DSCP QoS %d", dscp_qos);

  if (dscp_qos < -1 || dscp_qos > 63) {
    GST_WARNING_OBJECT (media, "trying to set illegal dscp qos %d", dscp_qos);
    return;
  }

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->dscp_qos = dscp_qos;
  g_ptr_array_foreach (priv->streams, (GFunc) do_set_dscp_qos, &dscp_qos);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_dscp_qos:
 * @media: a #GstRTSPMedia
 *
 * Get the configured DSCP QoS of attached media.
 *
 * Returns: the DSCP QoS value of attached streams or -1 if disabled.
 *
 * Since: 1.18
 */
gint
gst_rtsp_media_get_dscp_qos (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->dscp_qos;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_stop_on_disconnect:
 * @media: a #GstRTSPMedia
 * @stop_on_disconnect: the new value
 *
 * Set or unset if the pipeline for @media should be stopped when a
 * client disconnects without sending TEARDOWN.
 */
void
gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia * media,
    gboolean stop_on_disconnect)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->stop_on_disconnect = stop_on_disconnect;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_stop_on_disconnect:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media will be stopped when a client disconnects
 * without sending TEARDOWN.
 *
 * Returns: %TRUE if the media will be stopped when a client disconnects
 *     without sending TEARDOWN.
 */
gboolean
gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), TRUE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->stop_on_disconnect;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_retransmission_time:
 * @media: a #GstRTSPMedia
 * @time: the new value
 *
 * Set the amount of time to store retransmission packets.
 */
void
gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  GST_LOG_OBJECT (media, "set retransmission time %" G_GUINT64_FORMAT, time);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->rtx_time = time;
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);

    gst_rtsp_stream_set_retransmission_time (stream, time);
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_retransmission_time:
 * @media: a #GstRTSPMedia
 *
 * Get the amount of time to store retransmission data.
 *
 * Returns: the amount of time to store retransmission data.
 */
GstClockTime
gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstClockTime res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->rtx_time;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_do_retransmission:
 *
 * Set whether retransmission requests will be sent
 *
 * Since: 1.16
 */
void
gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
    gboolean do_retransmission)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->do_retransmission = do_retransmission;

  if (priv->rtpbin)
    g_object_set (priv->rtpbin, "do-retransmission", do_retransmission, NULL);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_do_retransmission:
 *
 * Returns: Whether retransmission requests will be sent
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->do_retransmission;
  g_mutex_unlock (&priv->lock);

  return res;
}

static void
update_stream_storage_size (GstRTSPMedia * media, GstRTSPStream * stream,
    guint sessid)
{
  GObject *storage = NULL;

  g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage",
      sessid, &storage);

  if (storage) {
    guint64 size_time = 0;

    if (!gst_rtsp_stream_is_tcp_receiver (stream))
      size_time = (media->priv->latency + 50) * GST_MSECOND;

    g_object_set (storage, "size-time", size_time, NULL);

    g_object_unref (storage);
  }
}

/**
 * gst_rtsp_media_set_latency:
 * @media: a #GstRTSPMedia
 * @latency: latency in milliseconds
 *
 * Configure the latency used for receiving media.
 */
void
gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  GST_LOG_OBJECT (media, "set latency %ums", latency);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->latency = latency;
  if (priv->rtpbin) {
    g_object_set (priv->rtpbin, "latency", latency, NULL);

    for (i = 0; i < media->priv->streams->len; i++) {
      GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
      update_stream_storage_size (media, stream, i);
    }
  }

  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_latency:
 * @media: a #GstRTSPMedia
 *
 * Get the latency that is used for receiving media.
 *
 * Returns: latency in milliseconds
 */
guint
gst_rtsp_media_get_latency (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->latency;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_use_time_provider:
 * @media: a #GstRTSPMedia
 * @time_provider: if a #GstNetTimeProvider should be used
 *
 * Set @media to provide a #GstNetTimeProvider.
 */
void
gst_rtsp_media_use_time_provider (GstRTSPMedia * media, gboolean time_provider)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->time_provider = time_provider;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_time_provider:
 * @media: a #GstRTSPMedia
 *
 * Check if @media can provide a #GstNetTimeProvider for its pipeline clock.
 *
 * Use gst_rtsp_media_get_time_provider() to get the network clock.
 *
 * Returns: %TRUE if @media can provide a #GstNetTimeProvider.
 */
gboolean
gst_rtsp_media_is_time_provider (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->time_provider;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_clock:
 * @media: a #GstRTSPMedia
 * @clock: (nullable): #GstClock to be used
 *
 * Configure the clock used for the media.
 */
void
gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));
  g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL);

  GST_LOG_OBJECT (media, "setting clock %" GST_PTR_FORMAT, clock);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if (priv->clock)
    gst_object_unref (priv->clock);
  priv->clock = clock ? gst_object_ref (clock) : NULL;
  if (priv->pipeline) {
    if (clock)
      gst_pipeline_use_clock (GST_PIPELINE_CAST (priv->pipeline), clock);
    else
      gst_pipeline_auto_clock (GST_PIPELINE_CAST (priv->pipeline));
  }

  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_set_publish_clock_mode:
 * @media: a #GstRTSPMedia
 * @mode: the clock publish mode
 *
 * Sets if and how the media clock should be published according to RFC7273.
 *
 * Since: 1.8
 */
void
gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media,
    GstRTSPPublishClockMode mode)
{
  GstRTSPMediaPrivate *priv;
  guint i, n;

  priv = media->priv;
  g_mutex_lock (&priv->lock);
  priv->publish_clock_mode = mode;

  n = priv->streams->len;
  for (i = 0; i < n; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);

    gst_rtsp_stream_set_publish_clock_mode (stream, mode);
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_publish_clock_mode:
 * @media: a #GstRTSPMedia
 *
 * Gets if and how the media clock should be published according to RFC7273.
 *
 * Returns: The GstRTSPPublishClockMode
 *
 * Since: 1.8
 */
GstRTSPPublishClockMode
gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPPublishClockMode ret;

  priv = media->priv;
  g_mutex_lock (&priv->lock);
  ret = priv->publish_clock_mode;
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_media_set_address_pool:
 * @media: a #GstRTSPMedia
 * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
 *
 * configure @pool to be used as the address pool of @media.
 */
void
gst_rtsp_media_set_address_pool (GstRTSPMedia * media,
    GstRTSPAddressPool * pool)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPAddressPool *old;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  GST_LOG_OBJECT (media, "set address pool %p", pool);

  g_mutex_lock (&priv->lock);
  if ((old = priv->pool) != pool)
    priv->pool = pool ? g_object_ref (pool) : NULL;
  else
    old = NULL;
  g_ptr_array_foreach (priv->streams, (GFunc) gst_rtsp_stream_set_address_pool,
      pool);
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_media_get_address_pool:
 * @media: a #GstRTSPMedia
 *
 * Get the #GstRTSPAddressPool used as the address pool of @media.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @media.
 * g_object_unref() after usage.
 */
GstRTSPAddressPool *
gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPAddressPool *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->pool))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_set_multicast_iface:
 * @media: a #GstRTSPMedia
 * @multicast_iface: (transfer none) (nullable): a multicast interface name
 *
 * configure @multicast_iface to be used for @media.
 */
void
gst_rtsp_media_set_multicast_iface (GstRTSPMedia * media,
    const gchar * multicast_iface)
{
  GstRTSPMediaPrivate *priv;
  gchar *old;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  GST_LOG_OBJECT (media, "set multicast interface %s", multicast_iface);

  g_mutex_lock (&priv->lock);
  if ((old = priv->multicast_iface) != multicast_iface)
    priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
  else
    old = NULL;
  g_ptr_array_foreach (priv->streams,
      (GFunc) gst_rtsp_stream_set_multicast_iface, (gchar *) multicast_iface);
  g_mutex_unlock (&priv->lock);

  if (old)
    g_free (old);
}

/**
 * gst_rtsp_media_get_multicast_iface:
 * @media: a #GstRTSPMedia
 *
 * Get the multicast interface used for @media.
 *
 * Returns: (transfer full) (nullable): the multicast interface for @media.
 * g_free() after usage.
 */
gchar *
gst_rtsp_media_get_multicast_iface (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->multicast_iface))
    result = g_strdup (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_set_max_mcast_ttl:
 * @media: a #GstRTSPMedia
 * @ttl: the new multicast ttl value
 *
 * Set the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: %TRUE if the requested ttl has been set successfully.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_set_max_mcast_ttl (GstRTSPMedia * media, guint ttl)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  GST_LOG_OBJECT (media, "set max mcast ttl %u", ttl);

  priv = media->priv;

  g_mutex_lock (&priv->lock);

  if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
    GST_WARNING_OBJECT (media, "The reqested mcast TTL value is not valid.");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
  priv->max_mcast_ttl = ttl;

  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    gst_rtsp_stream_set_max_mcast_ttl (stream, ttl);
  }
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_media_get_max_mcast_ttl:
 * @media: a #GstRTSPMedia
 *
 * Get the the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: the maximum time-to-live value of outgoing multicast packets.
 *
 * Since: 1.16
 */
guint
gst_rtsp_media_get_max_mcast_ttl (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->max_mcast_ttl;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_set_bind_mcast_address:
 * @media: a #GstRTSPMedia
 * @bind_mcast_addr: the new value
 *
 * Decide whether the multicast socket should be bound to a multicast address or
 * INADDR_ANY.
 *
 * Since: 1.16
 */
void
gst_rtsp_media_set_bind_mcast_address (GstRTSPMedia * media,
    gboolean bind_mcast_addr)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->bind_mcast_address = bind_mcast_addr;
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    gst_rtsp_stream_set_bind_mcast_address (stream, bind_mcast_addr);
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_is_bind_mcast_address:
 * @media: a #GstRTSPMedia
 *
 * Check if multicast sockets are configured to be bound to multicast addresses.
 *
 * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_media_is_bind_mcast_address (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  result = priv->bind_mcast_address;
  g_mutex_unlock (&priv->lock);

  return result;
}

void
gst_rtsp_media_set_enable_rtcp (GstRTSPMedia * media, gboolean enable)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->enable_rtcp = enable;
  g_mutex_unlock (&priv->lock);
}

static GList *
_find_payload_types (GstRTSPMedia * media)
{
  gint i, n;
  GQueue queue = G_QUEUE_INIT;

  n = media->priv->streams->len;
  for (i = 0; i < n; i++) {
    GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
    guint pt = gst_rtsp_stream_get_pt (stream);

    g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
  }

  return queue.head;
}

static guint
_next_available_pt (GList * payloads)
{
  guint i;

  for (i = 96; i <= 127; i++) {
    GList *iter = g_list_find (payloads, GINT_TO_POINTER (i));
    if (!iter)
      return GPOINTER_TO_UINT (i);
  }

  return 0;
}

/**
 * gst_rtsp_media_collect_streams:
 * @media: a #GstRTSPMedia
 *
 * Find all payloader elements, they should be named pay\%d in the
 * element of @media, and create #GstRTSPStreams for them.
 *
 * Collect all dynamic elements, named dynpay\%d, and add them to
 * the list of dynamic elements.
 *
 * Find all depayloader elements, they should be named depay\%d in the
 * element of @media, and create #GstRTSPStreams for them.
 */
void
gst_rtsp_media_collect_streams (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstElement *element, *elem;
  GstPad *pad;
  gint i;
  gboolean have_elem;
  gboolean more_elem_remaining = TRUE;
  GstRTSPTransportMode mode = 0;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;
  element = priv->element;

  have_elem = FALSE;
  for (i = 0; more_elem_remaining; i++) {
    gchar *name;

    more_elem_remaining = FALSE;

    name = g_strdup_printf ("pay%d", i);
    if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
      GstElement *pay;
      GST_INFO ("found stream %d with payloader %p", i, elem);

      /* take the pad of the payloader */
      pad = gst_element_get_static_pad (elem, "src");

      /* find the real payload element in case elem is a GstBin */
      pay = find_payload_element (elem, pad);

      /* create the stream */
      if (pay == NULL) {
        GST_WARNING ("could not find real payloader, using bin");
        gst_rtsp_media_create_stream (media, elem, pad);
      } else {
        gst_rtsp_media_create_stream (media, pay, pad);
        gst_object_unref (pay);
      }

      gst_object_unref (pad);
      gst_object_unref (elem);

      have_elem = TRUE;
      more_elem_remaining = TRUE;
      mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
    }
    g_free (name);

    name = g_strdup_printf ("dynpay%d", i);
    if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
      /* a stream that will dynamically create pads to provide RTP packets */
      GST_INFO ("found dynamic element %d, %p", i, elem);

      g_mutex_lock (&priv->lock);
      priv->dynamic = g_list_prepend (priv->dynamic, elem);
      g_mutex_unlock (&priv->lock);

      priv->nb_dynamic_elements++;

      have_elem = TRUE;
      more_elem_remaining = TRUE;
      mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
    }
    g_free (name);

    name = g_strdup_printf ("depay%d", i);
    if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
      GST_INFO ("found stream %d with depayloader %p", i, elem);

      /* take the pad of the payloader */
      pad = gst_element_get_static_pad (elem, "sink");
      /* create the stream */
      gst_rtsp_media_create_stream (media, elem, pad);
      gst_object_unref (pad);
      gst_object_unref (elem);

      have_elem = TRUE;
      more_elem_remaining = TRUE;
      mode |= GST_RTSP_TRANSPORT_MODE_RECORD;
    }
    g_free (name);
  }

  if (have_elem) {
    if (priv->transport_mode != mode)
      GST_WARNING ("found different mode than expected (0x%02x != 0x%02d)",
          priv->transport_mode, mode);
  }
}

typedef struct
{
  GstElement *appsink, *appsrc;
  GstRTSPStream *stream;
} AppSinkSrcData;

static GstFlowReturn
appsink_new_sample (GstAppSink * appsink, gpointer user_data)
{
  AppSinkSrcData *data = user_data;
  GstSample *sample;
  GstFlowReturn ret;

  sample = gst_app_sink_pull_sample (appsink);
  if (!sample)
    return GST_FLOW_FLUSHING;


  ret = gst_app_src_push_sample (GST_APP_SRC (data->appsrc), sample);
  gst_sample_unref (sample);
  return ret;
}

static GstAppSinkCallbacks appsink_callbacks = {
  NULL,
  NULL,
  appsink_new_sample,
};

static GstPadProbeReturn
appsink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  AppSinkSrcData *data = user_data;

  if (GST_IS_EVENT (info->data)
      && GST_EVENT_TYPE (info->data) == GST_EVENT_LATENCY) {
    GstClockTime min, max;

    if (gst_base_sink_query_latency (GST_BASE_SINK (data->appsink), NULL, NULL,
            &min, &max)) {
      g_object_set (data->appsrc, "min-latency", min, "max-latency", max, NULL);
      GST_DEBUG ("setting latency to min %" GST_TIME_FORMAT " max %"
          GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
    }
  } else if (GST_IS_QUERY (info->data)) {
    GstPad *srcpad = gst_element_get_static_pad (data->appsrc, "src");
    if (gst_pad_peer_query (srcpad, GST_QUERY_CAST (info->data))) {
      gst_object_unref (srcpad);
      return GST_PAD_PROBE_HANDLED;
    }
    gst_object_unref (srcpad);
  }

  return GST_PAD_PROBE_OK;
}

static GstPadProbeReturn
appsrc_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  AppSinkSrcData *data = user_data;

  if (GST_IS_QUERY (info->data)) {
    GstPad *sinkpad = gst_element_get_static_pad (data->appsink, "sink");
    if (gst_pad_peer_query (sinkpad, GST_QUERY_CAST (info->data))) {
      gst_object_unref (sinkpad);
      return GST_PAD_PROBE_HANDLED;
    }
    gst_object_unref (sinkpad);
  }

  return GST_PAD_PROBE_OK;
}

/**
 * gst_rtsp_media_create_stream:
 * @media: a #GstRTSPMedia
 * @payloader: a #GstElement
 * @pad: a #GstPad
 *
 * Create a new stream in @media that provides RTP data on @pad.
 * @pad should be a pad of an element inside @media->element.
 *
 * Returns: (transfer none): a new #GstRTSPStream that remains valid for as long
 * as @media exists.
 */
GstRTSPStream *
gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
    GstPad * pad)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPStream *stream;
  GstPad *streampad;
  gchar *name;
  gint idx;
  AppSinkSrcData *data = NULL;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
  g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
  g_return_val_if_fail (GST_IS_PAD (pad), NULL);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  idx = priv->streams->len;

  GST_DEBUG ("media %p: creating stream with index %d and payloader %"
      GST_PTR_FORMAT, media, idx, payloader);

  if (GST_PAD_IS_SRC (pad))
    name = g_strdup_printf ("src_%u", idx);
  else
    name = g_strdup_printf ("sink_%u", idx);

  if ((GST_PAD_IS_SRC (pad) && priv->element->numsinkpads > 0) ||
      (GST_PAD_IS_SINK (pad) && priv->element->numsrcpads > 0)) {
    GstElement *appsink, *appsrc;
    GstPad *sinkpad, *srcpad;

    GST_DEBUG_OBJECT (media,
        "Using appsrc+appsink to break loops for stream %u", idx);

    gchar *appsink_name = g_strdup_printf ("appsink_stream_%u", idx);
    appsink = gst_element_factory_make ("appsink", appsink_name);
    g_free (appsink_name);

    gchar *appsrc_name = g_strdup_printf ("appsrc_stream_%u", idx);
    appsrc = gst_element_factory_make ("appsrc", appsrc_name);
    g_free (appsrc_name);

    if (GST_PAD_IS_SINK (pad)) {
      srcpad = gst_element_get_static_pad (appsrc, "src");

      gst_bin_add (GST_BIN (priv->element), appsrc);

      GstPadLinkReturn pad_link GST_UNUSED_ASSERT = gst_pad_link (srcpad, pad);
      g_assert (pad_link == GST_PAD_LINK_OK);

      gst_object_unref (srcpad);

      streampad = gst_element_get_static_pad (appsink, "sink");

      if (priv->pipeline != NULL) {
        gst_bin_add (GST_BIN_CAST (priv->pipeline), appsink);
      } else {
        priv->pending_pipeline_elements =
            g_list_prepend (priv->pending_pipeline_elements, appsink);
      }
    } else {
      sinkpad = gst_element_get_static_pad (appsink, "sink");

      gst_bin_add (GST_BIN (priv->element), appsink);

      GstPadLinkReturn pad_link GST_UNUSED_ASSERT = gst_pad_link (pad, sinkpad);
      g_assert (pad_link == GST_PAD_LINK_OK);

      gst_object_unref (sinkpad);

      streampad = gst_element_get_static_pad (appsrc, "src");

      if (priv->pipeline != NULL) {
        gst_bin_add (GST_BIN_CAST (priv->pipeline), appsrc);
      } else {
        priv->pending_pipeline_elements =
            g_list_prepend (priv->pending_pipeline_elements, appsrc);
      }
    }

    g_object_set (appsrc, "block", TRUE, "format", GST_FORMAT_TIME, "is-live",
        TRUE, "emit-signals", FALSE, NULL);
    g_object_set (appsink, "sync", FALSE, "async", FALSE, "emit-signals",
        FALSE, "buffer-list", TRUE, NULL);

    data = g_new0 (AppSinkSrcData, 1);
    data->appsink = appsink;
    data->appsrc = appsrc;

    sinkpad = gst_element_get_static_pad (appsink, "sink");
    gst_pad_add_probe (sinkpad,
        GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
        appsink_pad_probe, data, NULL);
    gst_object_unref (sinkpad);

    srcpad = gst_element_get_static_pad (appsrc, "src");
    gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
        appsrc_pad_probe, data, NULL);
    gst_object_unref (srcpad);

    gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &appsink_callbacks,
        data, NULL);
    g_object_set_data_full (G_OBJECT (streampad), "media-appsink-appsrc", data,
        g_free);
  } else {
    streampad = gst_ghost_pad_new (name, pad);
    gst_pad_set_active (streampad, TRUE);
    gst_element_add_pad (priv->element, streampad);
  }
  g_free (name);

  stream = gst_rtsp_stream_new (idx, payloader, streampad);
  if (data)
    data->stream = stream;
  if (priv->pool)
    gst_rtsp_stream_set_address_pool (stream, priv->pool);
  gst_rtsp_stream_set_multicast_iface (stream, priv->multicast_iface);
  gst_rtsp_stream_set_max_mcast_ttl (stream, priv->max_mcast_ttl);
  gst_rtsp_stream_set_bind_mcast_address (stream, priv->bind_mcast_address);
  gst_rtsp_stream_set_enable_rtcp (stream, priv->enable_rtcp);
  gst_rtsp_stream_set_profiles (stream, priv->profiles);
  gst_rtsp_stream_set_protocols (stream, priv->protocols);
  gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time);
  gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size);
  gst_rtsp_stream_set_drop_delta_units (stream, priv->ensure_keyunit_on_start);
  gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode);
  gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control);

  g_ptr_array_add (priv->streams, stream);

  if (GST_PAD_IS_SRC (pad)) {
    gint i, n;

    if (priv->payloads)
      g_list_free (priv->payloads);
    priv->payloads = _find_payload_types (media);

    n = priv->streams->len;
    for (i = 0; i < n; i++) {
      GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
      guint rtx_pt = _next_available_pt (priv->payloads);

      if (rtx_pt == 0) {
        GST_WARNING ("Ran out of space of dynamic payload types");
        break;
      }

      gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);

      priv->payloads =
          g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt));
    }
  }
  g_mutex_unlock (&priv->lock);

  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STREAM], 0, stream,
      NULL);

  return stream;
}

GstRTSPStream *
gst_rtsp_media_create_and_join_stream (GstRTSPMedia * media,
    GstElement * payloader, GstPad * pad)
{
  GstRTSPStream *stream = gst_rtsp_media_create_stream (media, payloader, pad);
  GstRTSPMediaPrivate *priv = media->priv;

  if (stream == NULL) {
    return NULL;
  }

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) {
    /* join the element in the PAUSED state because this callback is
     * called from the streaming thread and it is PAUSED */
    if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
            priv->rtpbin, GST_STATE_PAUSED)) {
      GST_WARNING ("failed to join bin element");
    }

    if (priv->blocked)
      gst_rtsp_stream_set_blocked (stream, TRUE);
  }

  g_rec_mutex_unlock (&priv->state_lock);

  return stream;
}

static void
gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream)
{
  GstRTSPMediaPrivate *priv;
  GstPad *srcpad;
  AppSinkSrcData *data;

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  /* remove the ghostpad */
  srcpad = gst_rtsp_stream_get_srcpad (stream);
  data = g_object_get_data (G_OBJECT (srcpad), "media-appsink-appsrc");
  if (data) {
    if (GST_OBJECT_PARENT (data->appsrc) == GST_OBJECT_CAST (priv->pipeline))
      gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsrc);
    else if (GST_OBJECT_PARENT (data->appsrc) ==
        GST_OBJECT_CAST (priv->element))
      gst_bin_remove (GST_BIN_CAST (priv->element), data->appsrc);
    if (GST_OBJECT_PARENT (data->appsink) == GST_OBJECT_CAST (priv->pipeline))
      gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsink);
    else if (GST_OBJECT_PARENT (data->appsink) ==
        GST_OBJECT_CAST (priv->element))
      gst_bin_remove (GST_BIN_CAST (priv->element), data->appsink);
  } else {
    gst_element_remove_pad (priv->element, srcpad);
  }
  gst_object_unref (srcpad);
  /* now remove the stream */
  g_object_ref (stream);
  g_ptr_array_remove (priv->streams, stream);
  g_mutex_unlock (&priv->lock);

  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM], 0,
      stream, NULL);

  g_object_unref (stream);
}

/**
 * gst_rtsp_media_n_streams:
 * @media: a #GstRTSPMedia
 *
 * Get the number of streams in this media.
 *
 * Returns: The number of streams.
 */
guint
gst_rtsp_media_n_streams (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->streams->len;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_get_stream:
 * @media: a #GstRTSPMedia
 * @idx: the stream index
 *
 * Retrieve the stream with index @idx from @media.
 *
 * Returns: (nullable) (transfer none): the #GstRTSPStream at index
 * @idx or %NULL when a stream with that index did not exist.
 */
GstRTSPStream *
gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPStream *res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  if (idx < priv->streams->len)
    res = g_ptr_array_index (priv->streams, idx);
  else
    res = NULL;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_find_stream:
 * @media: a #GstRTSPMedia
 * @control: the control of the stream
 *
 * Find a stream in @media with @control as the control uri.
 *
 * Returns: (nullable) (transfer none): the #GstRTSPStream with
 * control uri @control or %NULL when a stream with that control did
 * not exist.
 */
GstRTSPStream *
gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPStream *res;
  gint i;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
  g_return_val_if_fail (control != NULL, NULL);

  priv = media->priv;

  res = NULL;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *test;

    test = g_ptr_array_index (priv->streams, i);
    if (gst_rtsp_stream_has_control (test, control)) {
      res = test;
      break;
    }
  }
  g_mutex_unlock (&priv->lock);

  return res;
}

/* called with state-lock */
static gboolean
default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range,
    GstRTSPRangeUnit unit)
{
  return gst_rtsp_range_convert_units (range, unit);
}

/**
 * gst_rtsp_media_get_range_string:
 * @media: a #GstRTSPMedia
 * @play: for the PLAY request
 * @unit: the unit to use for the string
 *
 * Get the current range as a string. @media must be prepared with
 * gst_rtsp_media_prepare ().
 *
 * Returns: (transfer full) (nullable): The range as a string, g_free() after usage.
 */
gchar *
gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play,
    GstRTSPRangeUnit unit)
{
  GstRTSPMediaClass *klass;
  GstRTSPMediaPrivate *priv;
  gchar *result;
  GstRTSPTimeRange range;

  klass = GST_RTSP_MEDIA_GET_CLASS (media);
  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
  g_return_val_if_fail (klass->convert_range != NULL, FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
      priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
    goto not_prepared;

  /* Update the range value with current position/duration */
  g_mutex_lock (&priv->lock);
  collect_media_stats (media);

  /* make copy */
  range = priv->range;

  if (!play && priv->n_active > 0) {
    range.min.type = GST_RTSP_TIME_NOW;
    range.min.seconds = -1;
  }
  g_mutex_unlock (&priv->lock);
  g_rec_mutex_unlock (&priv->state_lock);

  if (!klass->convert_range (media, &range, unit))
    goto conversion_failed;

  result = gst_rtsp_range_to_string (&range);

  return result;

  /* ERRORS */
not_prepared:
  {
    GST_WARNING ("media %p was not prepared", media);
    g_rec_mutex_unlock (&priv->state_lock);
    return NULL;
  }
conversion_failed:
  {
    GST_WARNING ("range conversion to unit %d failed", unit);
    return NULL;
  }
}

/**
 * gst_rtsp_media_get_rates:
 * @media: a #GstRTSPMedia
 * @rate: (optional) (out caller-allocates): the rate of the current segment
 * @applied_rate: (optional) (out caller-allocates): the applied_rate of the current segment
 *
 * Get the rate and applied_rate of the current segment.
 *
 * Returns: %FALSE if looking up the rate and applied rate failed. Otherwise
 * %TRUE is returned and @rate and @applied_rate are set to the rate and
 * applied_rate of the current segment.
 * Since: 1.18
 */
gboolean
gst_rtsp_media_get_rates (GstRTSPMedia * media, gdouble * rate,
    gdouble * applied_rate)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPStream *stream;
  gdouble save_rate, save_applied_rate;
  gboolean result = TRUE;
  gboolean first_stream = TRUE;
  gint i;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  if (!rate && !applied_rate) {
    GST_WARNING_OBJECT (media, "rate and applied_rate are both NULL");
    return FALSE;
  }

  priv = media->priv;

  g_mutex_lock (&priv->lock);

  g_assert (priv->streams->len > 0);
  for (i = 0; i < priv->streams->len; i++) {
    stream = g_ptr_array_index (priv->streams, i);
    if (gst_rtsp_stream_is_complete (stream)
        && gst_rtsp_stream_is_sender (stream)) {
      if (gst_rtsp_stream_get_rates (stream, rate, applied_rate)) {
        if (first_stream) {
          save_rate = *rate;
          save_applied_rate = *applied_rate;
          first_stream = FALSE;
        } else {
          if (save_rate != *rate || save_applied_rate != *applied_rate) {
            /* different rate or applied_rate, weird */
            result = FALSE;
            break;
          }
        }
      } else {
        /* complete stream without rate and applied_rate, weird */
        result = FALSE;
        break;
      }
    }
  }

  if (!result) {
    GST_WARNING_OBJECT (media,
        "failed to obtain consistent rate and applied_rate");
  }

  g_mutex_unlock (&priv->lock);

  return result;
}

static void
stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media)
{
  /* only unblock complete live streams when media is prepared */
  if (media->priv->is_live &&
      media->priv->status == GST_RTSP_MEDIA_STATUS_PREPARED &&
      !media->priv->blocked && !gst_rtsp_stream_is_complete (stream))
    return;

  gst_rtsp_stream_set_blocked (stream, media->priv->blocked);
}

static void
media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked)
{
  GstRTSPMediaPrivate *priv = media->priv;

  GST_DEBUG ("media %p set blocked %d", media, blocked);
  priv->blocked = blocked;
  g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media);

  if (!blocked)
    priv->blocking_msg_received = 0;
}

static void
stream_install_drop_probe (GstRTSPStream * stream, gpointer user_data)
{
  if (!gst_rtsp_stream_is_complete (stream))
    return;

  gst_rtsp_stream_install_drop_probe (stream);
}

static void
media_streams_install_drop_probe (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;

  g_ptr_array_foreach (priv->streams, (GFunc) stream_install_drop_probe, NULL);
}

static void
gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status)
{
  GstRTSPMediaPrivate *priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->status = status;
  GST_DEBUG ("setting new status to %d", status);
  g_cond_broadcast (&priv->cond);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_status:
 * @media: a #GstRTSPMedia
 *
 * Get the status of @media. When @media is busy preparing, this function waits
 * until @media is prepared or in error.
 *
 * Returns: the status of @media.
 */
GstRTSPMediaStatus
gst_rtsp_media_get_status (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPMediaStatus result;
  gint64 end_time;

  g_mutex_lock (&priv->lock);
  end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND;
  /* while we are preparing, wait */
  while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) {
    GST_DEBUG ("waiting for status change");
    if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) {
      GST_DEBUG ("timeout, assuming error status");
      priv->status = GST_RTSP_MEDIA_STATUS_ERROR;
    }
  }
  /* could be success or error */
  result = priv->status;
  GST_DEBUG ("got status %d", result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_media_seek_trickmode:
 * @media: a #GstRTSPMedia
 * @range: (transfer none): a #GstRTSPTimeRange
 * @flags: The minimal set of #GstSeekFlags to use
 * @rate: the rate to use in the seek
 * @trickmode_interval: The trickmode interval to use for KEY_UNITS trick mode
 *
 * Seek the pipeline of @media to @range with the given @flags and @rate,
 * and @trickmode_interval.
 * @media must be prepared with gst_rtsp_media_prepare().
 * In order to perform the seek operation, the pipeline must contain all
 * needed transport parts (transport sinks).
 *
 * Returns: %TRUE on success.
 *
 * Since: 1.18
 */
gboolean
gst_rtsp_media_seek_trickmode (GstRTSPMedia * media,
    GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate,
    GstClockTime trickmode_interval)
{
  GstRTSPMediaClass *klass;
  GstRTSPMediaPrivate *priv;
  gboolean res;
  GstClockTime start, stop;
  GstSeekType start_type, stop_type;
  gint64 current_position;
  gboolean force_seek;

  klass = GST_RTSP_MEDIA_GET_CLASS (media);

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
  /* if there's a range then klass->convert_range must be set */
  g_return_val_if_fail (range == NULL || klass->convert_range != NULL, FALSE);

  GST_DEBUG ("flags=%x  rate=%f", flags, rate);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
    goto not_prepared;

  /* check if the media pipeline is complete in order to perform a
   * seek operation on it */
  if (!check_complete (media))
    goto not_complete;

  /* Update the seekable state of the pipeline in case it changed */
  check_seekable (media);

  if (priv->seekable == 0) {
    GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
        " not seekable streams.");

    goto not_seekable;
  } else if (priv->seekable < 0) {
    goto not_seekable;
  }

  start_type = stop_type = GST_SEEK_TYPE_NONE;
  start = stop = GST_CLOCK_TIME_NONE;

  /* if caller provided a range convert it to NPT format
   * if no range provided the seek is assumed to be the same position but with
   * e.g. the rate changed */
  if (range != NULL) {
    if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT))
      goto not_supported;
    gst_rtsp_range_get_times (range, &start, &stop);

    GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
        GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
    GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
        GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop));
  }

  current_position = -1;
  if (klass->query_position)
    klass->query_position (media, &current_position);
  GST_INFO ("current media position %" GST_TIME_FORMAT,
      GST_TIME_ARGS (current_position));

  if (start != GST_CLOCK_TIME_NONE)
    start_type = GST_SEEK_TYPE_SET;

  if (stop != GST_CLOCK_TIME_NONE)
    stop_type = GST_SEEK_TYPE_SET;

  /* we force a seek if any trickmode flag is set, or if the flush flag is set or
   * the rate is non-standard, i.e. not 1.0 */
  force_seek = (flags & TRICKMODE_FLAGS) || (flags & GST_SEEK_FLAG_FLUSH) ||
      rate != 1.0;

  if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE || force_seek) {
    GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
        GST_TIME_ARGS (start), GST_TIME_ARGS (stop));

    /* depends on the current playing state of the pipeline. We might need to
     * queue this until we get EOS. */
    flags |= GST_SEEK_FLAG_FLUSH;

    /* if range start was not supplied we must continue from current position.
     * but since we're doing a flushing seek, let us query the current position
     * so we end up at exactly the same position after the seek. */
    if (range == NULL || range->min.type == GST_RTSP_TIME_END) {
      if (current_position == -1) {
        GST_WARNING ("current position unknown");
      } else {
        GST_DEBUG ("doing accurate seek to %" GST_TIME_FORMAT,
            GST_TIME_ARGS (current_position));
        start = current_position;
        start_type = GST_SEEK_TYPE_SET;
      }
    }

    if (!force_seek &&
        (start_type == GST_SEEK_TYPE_NONE || start == current_position) &&
        (stop_type == GST_SEEK_TYPE_NONE || stop == priv->range_stop)) {
      GST_DEBUG ("no position change, no flags set by caller, so not seeking");
      res = TRUE;
    } else {
      GstEvent *seek_event;
      gboolean unblock = FALSE;

      /* Handle expected async-done before waiting on next async-done.
       *
       * Since the seek further down in code will cause a preroll and
       * a async-done will be generated it's important to wait on async-done
       * if that is expected. Otherwise there is the risk that the waiting
       * for async-done after the seek is detecting the expected async-done
       * instead of the one that corresponds to the seek. Then execution
       * continue and act as if the pipeline is prerolled, but it's not.
       *
       * During wait_preroll message GST_MESSAGE_ASYNC_DONE will come
       * and then the state will change from preparing to prepared */
      if (priv->expected_async_done) {
        GST_DEBUG (" expected to get async-done, waiting ");
        gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
        g_rec_mutex_unlock (&priv->state_lock);

        /* wait until pipeline is prerolled  */
        if (!wait_preroll (media))
          goto preroll_failed_expected_async_done;

        g_rec_mutex_lock (&priv->state_lock);
        GST_DEBUG (" got expected async-done");
      }

      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);

      if (rate < 0.0) {
        GstClockTime temp_time = start;
        GstSeekType temp_type = start_type;

        start = stop;
        start_type = stop_type;
        stop = temp_time;
        stop_type = temp_type;
      }

      seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
          start, stop_type, stop);

      gst_event_set_seek_trickmode_interval (seek_event, trickmode_interval);

      if (!media->priv->blocked) {
        /* Prevent a race condition with multiple streams,
         * where one stream may have time to preroll before others
         * have even started flushing, causing async-done to be
         * posted too early.
         */
        media_streams_set_blocked (media, TRUE);
        unblock = TRUE;
      }

      res = gst_element_send_event (priv->pipeline, seek_event);

      if (unblock)
        media_streams_set_blocked (media, FALSE);

      /* and block for the seek to complete */
      GST_INFO ("done seeking %d", res);
      if (!res)
        goto seek_failed;

      g_rec_mutex_unlock (&priv->state_lock);

      /* wait until pipeline is prerolled again, this will also collect stats */
      if (!wait_preroll (media))
        goto preroll_failed;

      g_rec_mutex_lock (&priv->state_lock);
      GST_INFO ("prerolled again");
    }
  } else {
    GST_INFO ("no seek needed");
    res = TRUE;
  }
  g_rec_mutex_unlock (&priv->state_lock);

  return res;

  /* ERRORS */
not_prepared:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("media %p is not prepared", media);
    return FALSE;
  }
not_complete:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("pipeline is not complete");
    return FALSE;
  }
not_seekable:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("pipeline is not seekable");
    return FALSE;
  }
not_supported:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_WARNING ("conversion to npt not supported");
    return FALSE;
  }
seek_failed:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("seeking failed");
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
    return FALSE;
  }
preroll_failed:
  {
    GST_WARNING ("failed to preroll after seek");
    return FALSE;
  }
preroll_failed_expected_async_done:
  {
    GST_WARNING ("failed to preroll");
    return FALSE;
  }
}

/**
 * gst_rtsp_media_seek_full:
 * @media: a #GstRTSPMedia
 * @range: (transfer none): a #GstRTSPTimeRange
 * @flags: The minimal set of #GstSeekFlags to use
 *
 * Seek the pipeline of @media to @range with the given @flags.
 * @media must be prepared with gst_rtsp_media_prepare().
 *
 * Returns: %TRUE on success.
 * Since: 1.18
 */
gboolean
gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range,
    GstSeekFlags flags)
{
  return gst_rtsp_media_seek_trickmode (media, range, flags, 1.0, 0);
}

/**
 * gst_rtsp_media_seek:
 * @media: a #GstRTSPMedia
 * @range: (transfer none): a #GstRTSPTimeRange
 *
 * Seek the pipeline of @media to @range. @media must be prepared with
 * gst_rtsp_media_prepare().
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
{
  return gst_rtsp_media_seek_trickmode (media, range, GST_SEEK_FLAG_NONE,
      1.0, 0);
}

static void
stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked)
{
  if (gst_rtsp_stream_is_sender (stream)) {
    *blocked &= gst_rtsp_stream_is_blocking (stream);
  }
}

static gboolean
media_streams_blocking (GstRTSPMedia * media)
{
  gboolean blocking = TRUE;

  g_ptr_array_foreach (media->priv->streams, (GFunc) stream_collect_blocking,
      &blocking);

  return blocking;
}

static GstStateChangeReturn
set_state (GstRTSPMedia * media, GstState state)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstStateChangeReturn ret;

  GST_INFO ("set state to %s for media %p", gst_state_get_name (state), media);
  ret = gst_element_set_state (priv->pipeline, state);

  return ret;
}

static GstStateChangeReturn
set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstStateChangeReturn ret;

  GST_INFO ("set target state to %s for media %p",
      gst_state_get_name (state), media);
  priv->target_state = state;

  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_TARGET_STATE], 0,
      priv->target_state, NULL);

  if (do_state)
    ret = set_state (media, state);
  else
    ret = GST_STATE_CHANGE_SUCCESS;

  return ret;
}

static void
stream_collect_receiver_streams (GstRTSPStream * stream,
    guint * receiver_streams)
{
  if (!gst_rtsp_stream_is_sender (stream))
    (*receiver_streams)++;
}

static guint
get_num_receiver_streams (GstRTSPMedia * media)
{
  guint ret = 0;

  g_ptr_array_foreach (media->priv->streams,
      (GFunc) stream_collect_receiver_streams, &ret);

  return ret;
}


static void
stream_collect_complete_sender (GstRTSPStream * stream, guint * active_streams)
{
  if (gst_rtsp_stream_is_complete (stream)
      && gst_rtsp_stream_is_sender (stream))
    (*active_streams)++;
}

static guint
get_num_complete_sender_streams (GstRTSPMedia * media)
{
  guint ret = 0;

  g_ptr_array_foreach (media->priv->streams,
      (GFunc) stream_collect_complete_sender, &ret);

  return ret;
}

 /* called with state-lock */
/* called with state-lock */
static gboolean
default_handle_message (GstRTSPMedia * media, GstMessage * message)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstMessageType type;

  type = GST_MESSAGE_TYPE (message);

  switch (type) {
    case GST_MESSAGE_STATE_CHANGED:
    {
      GstState old, new, pending;

      if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->pipeline))
        break;

      gst_message_parse_state_changed (message, &old, &new, &pending);

      GST_DEBUG ("%p: went from %s to %s (pending %s)", media,
          gst_state_get_name (old), gst_state_get_name (new),
          gst_state_get_name (pending));
      if (priv->no_more_pads_pending == 0
          && gst_rtsp_media_is_receive_only (media) && old == GST_STATE_READY
          && new == GST_STATE_PAUSED) {
        GST_INFO ("%p: went to PAUSED, prepared now", media);
        g_mutex_lock (&priv->lock);
        collect_media_stats (media);
        g_mutex_unlock (&priv->lock);

        if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
          gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
      }

      break;
    }
    case GST_MESSAGE_BUFFERING:
    {
      gint percent;

      gst_message_parse_buffering (message, &percent);

      /* no state management needed for live pipelines */
      if (priv->is_live)
        break;

      if (percent == 100) {
        /* a 100% message means buffering is done */
        priv->buffering = FALSE;
        /* if the desired state is playing, go back */
        if (priv->target_state == GST_STATE_PLAYING) {
          GST_INFO ("Buffering done, setting pipeline to PLAYING");
          set_state (media, GST_STATE_PLAYING);
        } else {
          GST_INFO ("Buffering done");
        }
      } else {
        /* buffering busy */
        if (priv->buffering == FALSE) {
          if (priv->target_state == GST_STATE_PLAYING) {
            /* we were not buffering but PLAYING, PAUSE  the pipeline. */
            GST_INFO ("Buffering, setting pipeline to PAUSED ...");
            set_state (media, GST_STATE_PAUSED);
          } else {
            GST_INFO ("Buffering ...");
          }
        }
        priv->buffering = TRUE;
      }
      break;
    }
    case GST_MESSAGE_LATENCY:
    {
      gst_bin_recalculate_latency (GST_BIN_CAST (priv->pipeline));
      break;
    }
    case GST_MESSAGE_ERROR:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_error (message, &gerror, &debug);
      GST_WARNING ("%p: got error %s (%s)", media, gerror->message, debug);
      g_error_free (gerror);
      g_free (debug);

      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
      break;
    }
    case GST_MESSAGE_WARNING:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_warning (message, &gerror, &debug);
      GST_WARNING ("%p: got warning %s (%s)", media, gerror->message, debug);
      g_error_free (gerror);
      g_free (debug);
      break;
    }
    case GST_MESSAGE_ELEMENT:
    {
      const GstStructure *s;

      s = gst_message_get_structure (message);
      if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
        gboolean is_complete = FALSE;
        guint num_complete_sender_streams =
            get_num_complete_sender_streams (media);
        guint num_recv_streams = get_num_receiver_streams (media);
        guint expected_num_blocking_msg;

        /* to prevent problems when some streams are complete, some are not,
         * we will ignore incomplete streams. When there are no complete
         * streams (during DESCRIBE), we will listen to all streams. */

        gst_structure_get_boolean (s, "is_complete", &is_complete);
        expected_num_blocking_msg = num_complete_sender_streams;
        GST_DEBUG_OBJECT (media, "media received blocking message,"
            " num_complete_sender_streams = %d, is_complete = %d",
            num_complete_sender_streams, is_complete);

        if (num_complete_sender_streams == 0 || is_complete)
          priv->blocking_msg_received++;

        if (num_complete_sender_streams == 0)
          expected_num_blocking_msg = priv->streams->len - num_recv_streams;

        if (priv->blocked && media_streams_blocking (media) &&
            priv->no_more_pads_pending == 0 &&
            priv->blocking_msg_received == expected_num_blocking_msg) {
          GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "media is blocking");
          g_mutex_lock (&priv->lock);
          collect_media_stats (media);
          g_mutex_unlock (&priv->lock);

          if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
            gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);

          priv->blocking_msg_received = 0;
        }
      }
      break;
    }
    case GST_MESSAGE_STREAM_STATUS:
      break;
    case GST_MESSAGE_ASYNC_DONE:
      if (priv->expected_async_done)
        priv->expected_async_done = FALSE;
      if (priv->complete) {
        /* receive the final ASYNC_DONE, that is posted by the media pipeline
         * after all the transport parts have been successfully added to
         * the media streams. */
        GST_DEBUG_OBJECT (media, "got async-done");
        if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
          gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
      }
      break;
    case GST_MESSAGE_EOS:
      GST_INFO ("%p: got EOS", media);

      if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARING) {
        GST_DEBUG ("shutting down after EOS");
        finish_unprepare (media);
      }
      break;
    default:
      GST_INFO ("%p: got message type %d (%s)", media, type,
          gst_message_type_get_name (type));
      break;
  }
  return TRUE;
}

static gboolean
bus_message (GstBus * bus, GstMessage * message, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GQuark detail = 0;
  gboolean ret;

  detail = gst_message_type_to_quark (GST_MESSAGE_TYPE (message));

  g_rec_mutex_lock (&priv->state_lock);
  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_HANDLE_MESSAGE], detail,
      message, &ret);
  if (!ret) {
    GST_DEBUG_OBJECT (media, "failed emitting pipeline message");
  }
  g_rec_mutex_unlock (&priv->state_lock);

  return TRUE;
}

static void
watch_destroyed (GstRTSPMedia * media)
{
  GST_DEBUG_OBJECT (media, "source destroyed");
  g_object_unref (media);
}

static gboolean
is_payloader (GstElement * element)
{
  GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element);
  const gchar *klass;

  klass = gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS);
  if (klass == NULL)
    return FALSE;

  if (strstr (klass, "Payloader") && strstr (klass, "RTP")) {
    return TRUE;
  }

  return FALSE;
}

static GstElement *
find_payload_element (GstElement * payloader, GstPad * pad)
{
  GstElement *pay = NULL;

  if (GST_IS_BIN (payloader)) {
    GstIterator *iter;
    GValue item = { 0 };
    gchar *pad_name, *payloader_name;
    GstElement *element;

    if ((element = gst_bin_get_by_name (GST_BIN (payloader), "pay"))) {
      if (is_payloader (element))
        return element;
      gst_object_unref (element);
    }

    pad_name = gst_object_get_name (GST_OBJECT (pad));
    payloader_name = g_strdup_printf ("pay_%s", pad_name);
    g_free (pad_name);
    if ((element = gst_bin_get_by_name (GST_BIN (payloader), payloader_name))) {
      g_free (payloader_name);
      if (is_payloader (element))
        return element;
      gst_object_unref (element);
    } else {
      g_free (payloader_name);
    }

    iter = gst_bin_iterate_recurse (GST_BIN (payloader));
    while (gst_iterator_next (iter, &item) == GST_ITERATOR_OK) {
      element = (GstElement *) g_value_get_object (&item);

      if (is_payloader (element)) {
        pay = gst_object_ref (element);
        g_value_unset (&item);
        break;
      }
      g_value_unset (&item);
    }
    gst_iterator_free (iter);
  } else {
    pay = g_object_ref (payloader);
  }

  return pay;
}

/* called from streaming threads */
static void
pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPStream *stream;
  GstElement *pay;

  /* find the real payload element */
  pay = find_payload_element (element, pad);
  stream = gst_rtsp_media_create_stream (media, pay, pad);
  gst_object_unref (pay);

  GST_INFO ("pad added %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
    goto not_preparing;

  g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream);

  /* join the element in the PAUSED state because this callback is
   * called from the streaming thread and it is PAUSED */
  if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
          priv->rtpbin, GST_STATE_PAUSED)) {
    GST_WARNING ("failed to join bin element");
  }

  if (priv->blocked)
    gst_rtsp_stream_set_blocked (stream, TRUE);

  g_rec_mutex_unlock (&priv->state_lock);

  return;

  /* ERRORS */
not_preparing:
  {
    gst_rtsp_media_remove_stream (media, stream);
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("ignore pad because we are not preparing");
    return;
  }
}

static void
pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPStream *stream;

  stream = g_object_get_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream");
  if (stream == NULL)
    return;

  GST_INFO ("pad removed %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);

  g_rec_mutex_lock (&priv->state_lock);
  gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
  g_rec_mutex_unlock (&priv->state_lock);

  gst_rtsp_media_remove_stream (media, stream);
}

static void
no_more_pads_cb (GstElement * element, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;

  GST_INFO_OBJECT (element, "no more pads");
  g_mutex_lock (&priv->lock);
  priv->no_more_pads_pending--;
  g_mutex_unlock (&priv->lock);
}

typedef struct _DynPaySignalHandlers DynPaySignalHandlers;

struct _DynPaySignalHandlers
{
  gulong pad_added_handler;
  gulong pad_removed_handler;
  gulong no_more_pads_handler;
};

static gboolean
start_preroll (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstStateChangeReturn ret;

  GST_INFO ("setting pipeline to PAUSED for media %p", media);

  /* start blocked since it is possible that there are no sink elements yet */
  media_streams_set_blocked (media, TRUE);
  ret = set_target_state (media, GST_STATE_PAUSED, TRUE);

  switch (ret) {
    case GST_STATE_CHANGE_SUCCESS:
      GST_INFO ("SUCCESS state change for media %p", media);
      break;
    case GST_STATE_CHANGE_ASYNC:
      GST_INFO ("ASYNC state change for media %p", media);
      break;
    case GST_STATE_CHANGE_NO_PREROLL:
      /* we need to go to PLAYING */
      GST_INFO ("NO_PREROLL state change: live media %p", media);
      /* FIXME we disable seeking for live streams for now. We should perform a
       * seeking query in preroll instead */
      priv->seekable = -1;
      priv->is_live = TRUE;

      ret = set_state (media, GST_STATE_PLAYING);
      if (ret == GST_STATE_CHANGE_FAILURE)
        goto state_failed;
      break;
    case GST_STATE_CHANGE_FAILURE:
      goto state_failed;
  }

  return TRUE;

state_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    return FALSE;
  }
}

static gboolean
wait_preroll (GstRTSPMedia * media)
{
  GstRTSPMediaStatus status;

  GST_DEBUG ("wait to preroll pipeline");

  /* wait until pipeline is prerolled */
  status = gst_rtsp_media_get_status (media);
  if (status == GST_RTSP_MEDIA_STATUS_ERROR)
    goto preroll_failed;

  return TRUE;

preroll_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    return FALSE;
  }
}

static GstElement *
request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPStream *stream = NULL;
  guint i;
  GstElement *res = NULL;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    stream = g_ptr_array_index (priv->streams, i);

    if (sessid == gst_rtsp_stream_get_index (stream))
      break;

    stream = NULL;
  }
  g_mutex_unlock (&priv->lock);

  if (stream)
    res = gst_rtsp_stream_request_aux_sender (stream, sessid);

  return res;
}

static GstElement *
request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPStream *stream = NULL;
  guint i;
  GstElement *res = NULL;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    stream = g_ptr_array_index (priv->streams, i);

    if (sessid == gst_rtsp_stream_get_index (stream))
      break;

    stream = NULL;
  }
  g_mutex_unlock (&priv->lock);

  if (stream)
    res = gst_rtsp_stream_request_aux_receiver (stream, sessid);

  return res;
}

static GstElement *
request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPStream *stream = NULL;
  guint i;
  GstElement *res = NULL;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    stream = g_ptr_array_index (priv->streams, i);

    if (sessid == gst_rtsp_stream_get_index (stream))
      break;

    stream = NULL;
  }
  g_mutex_unlock (&priv->lock);

  if (stream) {
    res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid);
  }

  return res;
}

static gboolean
start_prepare (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  guint i;
  GList *walk;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
    goto no_longer_preparing;

  g_signal_connect (priv->rtpbin, "request-fec-decoder",
      G_CALLBACK (request_fec_decoder), media);

  /* link streams we already have, other streams might appear when we have
   * dynamic elements */
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream;

    stream = g_ptr_array_index (priv->streams, i);

    if (priv->rtx_time > 0) {
      /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
      g_signal_connect (priv->rtpbin, "request-aux-sender",
          (GCallback) request_aux_sender, media);
    }

    if (priv->do_retransmission) {
      g_signal_connect (priv->rtpbin, "request-aux-receiver",
          (GCallback) request_aux_receiver, media);
    }

    if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
            priv->rtpbin, GST_STATE_NULL)) {
      goto join_bin_failed;
    }
  }

  if (priv->rtpbin)
    g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission,
        "do-lost", TRUE, NULL);

  for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
    GstElement *elem = walk->data;
    DynPaySignalHandlers *handlers = g_new (DynPaySignalHandlers, 1);

    GST_INFO ("adding callbacks for dynamic element %p", elem);

    handlers->pad_added_handler = g_signal_connect (elem, "pad-added",
        (GCallback) pad_added_cb, media);
    handlers->pad_removed_handler = g_signal_connect (elem, "pad-removed",
        (GCallback) pad_removed_cb, media);
    handlers->no_more_pads_handler = g_signal_connect (elem, "no-more-pads",
        (GCallback) no_more_pads_cb, media);

    g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers);
  }

  if (priv->nb_dynamic_elements == 0 && gst_rtsp_media_is_receive_only (media)) {
    /* If we are receive_only (RECORD), do not try to preroll, to avoid
     * a second ASYNC state change failing */
    priv->is_live = TRUE;
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
  } else if (!start_preroll (media)) {
    goto preroll_failed;
  }

  g_rec_mutex_unlock (&priv->state_lock);

  return FALSE;

no_longer_preparing:
  {
    GST_INFO ("media is no longer preparing");
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
join_bin_failed:
  {
    GST_WARNING ("failed to join bin element");
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
preroll_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
}

static gboolean
default_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPMediaClass *klass;
  GstBus *bus;
  GMainContext *context;
  GSource *source;

  priv = media->priv;

  klass = GST_RTSP_MEDIA_GET_CLASS (media);

  if (!klass->create_rtpbin)
    goto no_create_rtpbin;

  priv->rtpbin = klass->create_rtpbin (media);
  if (priv->rtpbin != NULL) {
    gboolean success = TRUE;

    g_object_set (priv->rtpbin, "latency", priv->latency, NULL);

    if (klass->setup_rtpbin)
      success = klass->setup_rtpbin (media, priv->rtpbin);

    if (success == FALSE) {
      gst_object_unref (priv->rtpbin);
      priv->rtpbin = NULL;
    }
  }
  if (priv->rtpbin == NULL)
    goto no_rtpbin;

  priv->thread = thread;
  context = (thread != NULL) ? (thread->context) : NULL;

  bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline));

  /* add the pipeline bus to our custom mainloop */
  priv->source = gst_bus_create_watch (bus);
  gst_object_unref (bus);

  g_source_set_callback (priv->source, (GSourceFunc) bus_message,
      g_object_ref (media), (GDestroyNotify) watch_destroyed);

  g_source_attach (priv->source, context);

  /* add stuff to the bin */
  gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin);

  /* do remainder in context */
  source = g_idle_source_new ();
  g_source_set_callback (source, (GSourceFunc) start_prepare,
      g_object_ref (media), (GDestroyNotify) g_object_unref);
  g_source_attach (source, context);
  g_source_unref (source);

  return TRUE;

  /* ERRORS */
no_create_rtpbin:
  {
    GST_ERROR ("no create_rtpbin function");
    g_critical ("no create_rtpbin vmethod function set");
    return FALSE;
  }
no_rtpbin:
  {
    GST_WARNING ("no rtpbin element");
    g_warning ("failed to create element 'rtpbin', check your installation");
    return FALSE;
  }
}

/**
 * gst_rtsp_media_prepare:
 * @media: a #GstRTSPMedia
 * @thread: (transfer full) (allow-none): a #GstRTSPThread to run the
 *   bus handler or %NULL
 *
 * Prepare @media for streaming. This function will create the objects
 * to manage the streaming. A pipeline must have been set on @media with
 * gst_rtsp_media_take_pipeline().
 *
 * It will preroll the pipeline and collect vital information about the streams
 * such as the duration.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPMediaClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  priv->prepare_count++;

  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED ||
      priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED)
    goto was_prepared;

  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
    goto is_preparing;

  if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED)
    goto not_unprepared;

  if (!priv->reusable && priv->reused)
    goto is_reused;

  GST_INFO ("preparing media %p", media);

  /* reset some variables */
  priv->is_live = FALSE;
  priv->seekable = -1;
  priv->buffering = FALSE;
  priv->no_more_pads_pending = priv->nb_dynamic_elements;

  /* we're preparing now */
  gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);

  klass = GST_RTSP_MEDIA_GET_CLASS (media);
  if (klass->prepare) {
    if (!klass->prepare (media, thread))
      goto prepare_failed;
  }

wait_status:
  g_rec_mutex_unlock (&priv->state_lock);

  /* now wait for all pads to be prerolled, FIXME, we should somehow be
   * able to do this async so that we don't block the server thread. */
  if (!wait_preroll (media))
    goto preroll_failed;

  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARED], 0, NULL);

  GST_INFO ("object %p is prerolled", media);

  return TRUE;

  /* OK */
is_preparing:
  {
    /* we are not going to use the giving thread, so stop it. */
    if (thread)
      gst_rtsp_thread_stop (thread);
    goto wait_status;
  }
was_prepared:
  {
    GST_LOG ("media %p was prepared", media);
    /* we are not going to use the giving thread, so stop it. */
    if (thread)
      gst_rtsp_thread_stop (thread);
    g_rec_mutex_unlock (&priv->state_lock);
    return TRUE;
  }
  /* ERRORS */
not_unprepared:
  {
    /* we are not going to use the giving thread, so stop it. */
    if (thread)
      gst_rtsp_thread_stop (thread);
    GST_WARNING ("media %p was not unprepared", media);
    priv->prepare_count--;
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
is_reused:
  {
    /* we are not going to use the giving thread, so stop it. */
    if (thread)
      gst_rtsp_thread_stop (thread);
    priv->prepare_count--;
    g_rec_mutex_unlock (&priv->state_lock);
    GST_WARNING ("can not reuse media %p", media);
    return FALSE;
  }
prepare_failed:
  {
    /* we are not going to use the giving thread, so stop it. */
    if (thread)
      gst_rtsp_thread_stop (thread);
    priv->prepare_count--;
    g_rec_mutex_unlock (&priv->state_lock);
    GST_ERROR ("failed to prepare media");
    return FALSE;
  }
preroll_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    gst_rtsp_media_unprepare (media);
    return FALSE;
  }
}

/* must be called with state-lock */
static void
finish_unprepare (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gint i;
  GList *walk;

  if (priv->finishing_unprepare)
    return;
  priv->finishing_unprepare = TRUE;

  GST_DEBUG ("shutting down");

  /* release the lock on shutdown, otherwise pad_added_cb might try to
   * acquire the lock and then we deadlock */
  g_rec_mutex_unlock (&priv->state_lock);
  set_state (media, GST_STATE_NULL);
  g_rec_mutex_lock (&priv->state_lock);

  media_streams_set_blocked (media, FALSE);

  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream;

    GST_INFO ("Removing elements of stream %d from pipeline", i);

    stream = g_ptr_array_index (priv->streams, i);

    gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
  }

  /* remove the pad signal handlers */
  for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
    GstElement *elem = walk->data;
    DynPaySignalHandlers *handlers;

    handlers =
        g_object_steal_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers");
    g_assert (handlers != NULL);

    g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_added_handler);
    g_signal_handler_disconnect (G_OBJECT (elem),
        handlers->pad_removed_handler);
    g_signal_handler_disconnect (G_OBJECT (elem),
        handlers->no_more_pads_handler);

    g_free (handlers);
  }

  gst_bin_remove (GST_BIN (priv->pipeline), priv->rtpbin);
  priv->rtpbin = NULL;

  if (priv->nettime)
    gst_object_unref (priv->nettime);
  priv->nettime = NULL;

  priv->reused = TRUE;
  gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARED);

  /* when the media is not reusable, this will effectively unref the media and
   * recreate it */
  g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_UNPREPARED], 0, NULL);

  /* the source has the last ref to the media */
  if (priv->source) {
    GstBus *bus;

    GST_DEBUG ("removing bus watch");
    bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline));
    gst_bus_remove_watch (bus);
    gst_object_unref (bus);

    GST_DEBUG ("destroy source");
    g_source_destroy (priv->source);
    g_source_unref (priv->source);
    priv->source = NULL;
  }
  if (priv->thread) {
    GST_DEBUG ("stop thread");
    gst_rtsp_thread_stop (priv->thread);
  }

  priv->finishing_unprepare = FALSE;
}

/* called with state-lock */
static gboolean
default_unprepare (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;

  gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);

  if (priv->eos_shutdown) {
    /* we need to go to playing again for the EOS to propagate, normally in this
     * state, nothing is receiving data from us anymore so this is ok. */
    GST_DEBUG ("Temporarily go to PLAYING again for sending EOS");
    set_state (media, GST_STATE_PLAYING);
    GST_DEBUG ("sending EOS for shutdown");
    gst_element_send_event (priv->pipeline, gst_event_new_eos ());
  } else {
    finish_unprepare (media);
  }
  return TRUE;
}

/**
 * gst_rtsp_media_unprepare:
 * @media: a #GstRTSPMedia
 *
 * Unprepare @media. After this call, the media should be prepared again before
 * it can be used again. If the media is set to be non-reusable, a new instance
 * must be created.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_unprepare (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean success;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARED)
    goto was_unprepared;

  priv->prepare_count--;
  if (priv->prepare_count > 0)
    goto is_busy;
  if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARING)
    goto is_unpreparing;

  GST_INFO ("unprepare media %p", media);
  set_target_state (media, GST_STATE_NULL, FALSE);
  success = TRUE;

  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED
      || priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED) {
    GstRTSPMediaClass *klass;

    klass = GST_RTSP_MEDIA_GET_CLASS (media);
    if (klass->unprepare)
      success = klass->unprepare (media);
  } else {
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
    finish_unprepare (media);
  }
  g_rec_mutex_unlock (&priv->state_lock);

  return success;

was_unprepared:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("media %p was already unprepared", media);
    return TRUE;
  }
is_unpreparing:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_INFO ("media %p is already unpreparing", media);
    return TRUE;
  }
is_busy:
  {
    GST_INFO ("media %p still prepared %d times", media, priv->prepare_count);
    g_rec_mutex_unlock (&priv->state_lock);
    return TRUE;
  }
}

/* should be called with state-lock */
static GstClock *
get_clock_unlocked (GstRTSPMedia * media)
{
  if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) {
    GST_DEBUG_OBJECT (media, "media was not prepared");
    return NULL;
  }
  return gst_pipeline_get_clock (GST_PIPELINE_CAST (media->priv->pipeline));
}

/**
 * gst_rtsp_media_lock:
 * @media: a #GstRTSPMedia
 *
 * Lock the entire media. This is needed by callers such as rtsp_client to
 * protect the media when it is shared by many clients.
 * The lock prevents that concurrent clients alters the shared media,
 * while one client already is working with it.
 * Typically the lock is taken in external RTSP API calls that uses shared media
 * such as DESCRIBE, SETUP, ANNOUNCE, TEARDOWN, PLAY, PAUSE.
 *
 * As best practice take the lock as soon as the function get hold of a shared
 * media object. Release the lock right before the function returns.
 *
 * Since: 1.18
 */
void
gst_rtsp_media_lock (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->global_lock);
}

/**
 * gst_rtsp_media_unlock:
 * @media: a #GstRTSPMedia
 *
 * Unlock the media.
 *
 * Since: 1.18
 */
void
gst_rtsp_media_unlock (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_unlock (&priv->global_lock);
}

/**
 * gst_rtsp_media_get_clock:
 * @media: a #GstRTSPMedia
 *
 * Get the clock that is used by the pipeline in @media.
 *
 * @media must be prepared before this method returns a valid clock object.
 *
 * Returns: (transfer full) (nullable): the #GstClock used by @media. unref after usage.
 */
GstClock *
gst_rtsp_media_get_clock (GstRTSPMedia * media)
{
  GstClock *clock;
  GstRTSPMediaPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  clock = get_clock_unlocked (media);
  g_rec_mutex_unlock (&priv->state_lock);

  return clock;
}

/**
 * gst_rtsp_media_get_base_time:
 * @media: a #GstRTSPMedia
 *
 * Get the base_time that is used by the pipeline in @media.
 *
 * @media must be prepared before this method returns a valid base_time.
 *
 * Returns: the base_time used by @media.
 */
GstClockTime
gst_rtsp_media_get_base_time (GstRTSPMedia * media)
{
  GstClockTime result;
  GstRTSPMediaPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_CLOCK_TIME_NONE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
    goto not_prepared;

  result = gst_element_get_base_time (media->priv->pipeline);
  g_rec_mutex_unlock (&priv->state_lock);

  return result;

  /* ERRORS */
not_prepared:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_DEBUG_OBJECT (media, "media was not prepared");
    return GST_CLOCK_TIME_NONE;
  }
}

/**
 * gst_rtsp_media_get_time_provider:
 * @media: a #GstRTSPMedia
 * @address: (allow-none): an address or %NULL
 * @port: a port or 0
 *
 * Get the #GstNetTimeProvider for the clock used by @media. The time provider
 * will listen on @address and @port for client time requests.
 *
 * Returns: (transfer full) (nullable): the #GstNetTimeProvider of @media.
 */
GstNetTimeProvider *
gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address,
    guint16 port)
{
  GstRTSPMediaPrivate *priv;
  GstNetTimeProvider *provider = NULL;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->time_provider) {
    if ((provider = priv->nettime) == NULL) {
      GstClock *clock;

      if (priv->time_provider && (clock = get_clock_unlocked (media))) {
        provider = gst_net_time_provider_new (clock, address, port);
        gst_object_unref (clock);

        priv->nettime = provider;
      }
    }
  }
  g_rec_mutex_unlock (&priv->state_lock);

  if (provider)
    gst_object_ref (provider);

  return provider;
}

static gboolean
default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info)
{
  return gst_rtsp_sdp_from_media (sdp, info, media);
}

/**
 * gst_rtsp_media_setup_sdp:
 * @media: a #GstRTSPMedia
 * @sdp: (transfer none): a #GstSDPMessage
 * @info: (transfer none): a #GstSDPInfo
 *
 * Add @media specific info to @sdp. @info is used to configure the connection
 * information in the SDP.
 *
 * Returns: TRUE on success.
 */
gboolean
gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
    GstSDPInfo * info)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPMediaClass *klass;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
  g_return_val_if_fail (sdp != NULL, FALSE);
  g_return_val_if_fail (info != NULL, FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);

  klass = GST_RTSP_MEDIA_GET_CLASS (media);

  if (!klass->setup_sdp)
    goto no_setup_sdp;

  res = klass->setup_sdp (media, sdp, info);

  g_rec_mutex_unlock (&priv->state_lock);

  return res;

  /* ERRORS */
no_setup_sdp:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_ERROR ("no setup_sdp function");
    g_critical ("no setup_sdp vmethod function set");
    return FALSE;
  }
}

static gboolean
default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gint i, medias_len;

  medias_len = gst_sdp_message_medias_len (sdp);
  if (medias_len != priv->streams->len) {
    GST_ERROR ("%p: Media has more or less streams than SDP (%d /= %d)", media,
        priv->streams->len, medias_len);
    return FALSE;
  }

  for (i = 0; i < medias_len; i++) {
    const gchar *proto;
    const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
    GstRTSPStream *stream;
    gint j, formats_len;
    const gchar *control;
    GstRTSPProfile profile, profiles;

    stream = g_ptr_array_index (priv->streams, i);

    /* TODO: Should we do something with the other SDP information? */

    /* get proto */
    proto = gst_sdp_media_get_proto (sdp_media);
    if (proto == NULL) {
      GST_ERROR ("%p: SDP media %d has no proto", media, i);
      return FALSE;
    }

    if (g_str_equal (proto, "RTP/AVP")) {
      profile = GST_RTSP_PROFILE_AVP;
    } else if (g_str_equal (proto, "RTP/SAVP")) {
      profile = GST_RTSP_PROFILE_SAVP;
    } else if (g_str_equal (proto, "RTP/AVPF")) {
      profile = GST_RTSP_PROFILE_AVPF;
    } else if (g_str_equal (proto, "RTP/SAVPF")) {
      profile = GST_RTSP_PROFILE_SAVPF;
    } else {
      GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
      return FALSE;
    }

    profiles = gst_rtsp_stream_get_profiles (stream);
    if ((profiles & profile) == 0) {
      GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
      return FALSE;
    }

    formats_len = gst_sdp_media_formats_len (sdp_media);
    for (j = 0; j < formats_len; j++) {
      gint pt;
      GstCaps *caps;
      GstStructure *s;

      pt = atoi (gst_sdp_media_get_format (sdp_media, j));

      GST_DEBUG (" looking at %d pt: %d", j, pt);

      /* convert caps */
      caps = gst_sdp_media_get_caps_from_media (sdp_media, pt);
      if (caps == NULL) {
        GST_WARNING (" skipping pt %d without caps", pt);
        continue;
      }

      /* do some tweaks */
      GST_DEBUG ("mapping sdp session level attributes to caps");
      gst_sdp_message_attributes_to_caps (sdp, caps);
      GST_DEBUG ("mapping sdp media level attributes to caps");
      gst_sdp_media_attributes_to_caps (sdp_media, caps);

      s = gst_caps_get_structure (caps, 0);
      gst_structure_set_name (s, "application/x-rtp");

      if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
        gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);

      gst_rtsp_stream_set_pt_map (stream, pt, caps);
      gst_caps_unref (caps);
    }

    control = gst_sdp_media_get_attribute_val (sdp_media, "control");
    if (control)
      gst_rtsp_stream_set_control (stream, control);

  }

  return TRUE;
}

/**
 * gst_rtsp_media_handle_sdp:
 * @media: a #GstRTSPMedia
 * @sdp: (transfer none): a #GstSDPMessage
 *
 * Configure an SDP on @media for receiving streams
 *
 * Returns: TRUE on success.
 */
gboolean
gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPMediaClass *klass;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
  g_return_val_if_fail (sdp != NULL, FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);

  klass = GST_RTSP_MEDIA_GET_CLASS (media);

  if (!klass->handle_sdp)
    goto no_handle_sdp;

  res = klass->handle_sdp (media, sdp);

  g_rec_mutex_unlock (&priv->state_lock);

  return res;

  /* ERRORS */
no_handle_sdp:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_ERROR ("no handle_sdp function");
    g_critical ("no handle_sdp vmethod function set");
    return FALSE;
  }
}

static void
do_set_seqnum (GstRTSPStream * stream)
{
  guint16 seq_num;

  if (gst_rtsp_stream_is_sender (stream)) {
    seq_num = gst_rtsp_stream_get_current_seqnum (stream);
    gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
  }
}

static gboolean
enable_keyunit_expired (GstRTSPMedia * media)
{
  GST_DEBUG_OBJECT (media, "keyunit has expired");
  media->priv->keyunit_is_expired = TRUE;

  return G_SOURCE_REMOVE;
}

/* call with state_lock */
static gboolean
default_suspend (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;

  switch (priv->suspend_mode) {
    case GST_RTSP_SUSPEND_MODE_NONE:
      GST_DEBUG ("media %p no suspend", media);
      break;
    case GST_RTSP_SUSPEND_MODE_PAUSE:
      GST_DEBUG ("media %p suspend to PAUSED", media);
      ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
      if (ret == GST_STATE_CHANGE_FAILURE)
        goto state_failed;
      break;
    case GST_RTSP_SUSPEND_MODE_RESET:
      GST_DEBUG ("media %p suspend to NULL", media);
      ret = set_target_state (media, GST_STATE_NULL, TRUE);
      if (ret == GST_STATE_CHANGE_FAILURE)
        goto state_failed;
      /* Because payloader needs to set the sequence number as
       * monotonic, we need to preserve the sequence number
       * after pause. (otherwise going from pause to play,  which
       * is actually from NULL to PLAY will create a new sequence
       * number. */
      g_ptr_array_foreach (priv->streams, (GFunc) do_set_seqnum, NULL);
      break;
    default:
      break;
  }

  /* If we use any suspend mode that changes the state then we must update
   * expected_async_done, since we might not be doing an asyncronous state
   * change anymore. */
  if (ret != GST_STATE_CHANGE_FAILURE && ret != GST_STATE_CHANGE_ASYNC)
    priv->expected_async_done = FALSE;

  /* set expiration date on buffer in case of delayed PLAY request */
  if (priv->ensure_keyunit_on_start) {
    /* no need to install the timer if configured to trigger immediately */
    if (priv->ensure_keyunit_on_start_timeout == 0) {
      enable_keyunit_expired (media);
    } else {
      priv->keyunit_expiration_source =
          g_timeout_source_new (priv->ensure_keyunit_on_start_timeout);
      g_source_set_callback (priv->keyunit_expiration_source,
          G_SOURCE_FUNC (enable_keyunit_expired), (gpointer) media, NULL);
      g_source_attach (priv->keyunit_expiration_source, priv->thread->context);
    }
  }

  return TRUE;

  /* ERRORS */
state_failed:
  {
    GST_WARNING ("failed changing pipeline's state for media %p", media);
    return FALSE;
  }
}

/**
 * gst_rtsp_media_suspend:
 * @media: a #GstRTSPMedia
 *
 * Suspend @media. The state of the pipeline managed by @media is set to
 * GST_STATE_NULL but all streams are kept. @media can be prepared again
 * with gst_rtsp_media_unsuspend()
 *
 * @media must be prepared with gst_rtsp_media_prepare();
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_suspend (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPMediaClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  GST_FIXME ("suspend for dynamic pipelines needs fixing");

  /* this typically can happen for shared media. */
  if (priv->prepare_count > 1 &&
      priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED) {
    goto done;
  } else if (priv->prepare_count > 1) {
    goto prepared_by_other_client;
  }

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
    goto not_prepared;

  /* don't attempt to suspend when something is busy */
  if (priv->n_active > 0)
    goto done;

  klass = GST_RTSP_MEDIA_GET_CLASS (media);
  if (klass->suspend) {
    if (!klass->suspend (media))
      goto suspend_failed;
  }

  gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_SUSPENDED);

done:
  g_rec_mutex_unlock (&priv->state_lock);

  return TRUE;

  /* ERRORS */
prepared_by_other_client:
  {
    GST_WARNING ("media %p was prepared by other client", media);
    return FALSE;
  }
not_prepared:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_WARNING ("media %p was not prepared", media);
    return FALSE;
  }
suspend_failed:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
    GST_WARNING ("failed to suspend media %p", media);
    return FALSE;
  }
}

/* Call with state_lock */
static gboolean
ensure_new_keyunit (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gboolean preroll_ok;
  gboolean is_blocking = FALSE;

  /* nothing to be done without complete senders */
  if (get_num_complete_sender_streams (media) == 0) {
    GST_DEBUG_OBJECT (media, "no complete senders, skipping force keyunit");
    return TRUE;
  }

  is_blocking = media_streams_blocking (media);

  /* if we unsuspend before the keyunit is expired remove the timer so that
   * no future buffer is marked as expired */
  if (is_blocking && !priv->keyunit_is_expired) {
    GST_DEBUG_OBJECT (media, "using currently blocking keyunit");
    g_source_destroy (priv->keyunit_expiration_source);
    g_source_unref (priv->keyunit_expiration_source);
    priv->keyunit_expiration_source = NULL;

    return TRUE;
  }

  /* set the media to preparing, thus requiring a successful preroll before
   * completing unsuspend. */
  gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
  GST_DEBUG_OBJECT (media, "ensuring new keyunit, doing preroll");
  if (!start_preroll (media))
    goto start_failed;

  if (is_blocking) {
    /* if we end up here then the keyunit has expired and the timer callback
     * has been removed so reset the flag */
    priv->keyunit_is_expired = FALSE;

    /* install a probe that will drop the currently blocking keyunit on all
     * complete streams. */
    GST_DEBUG_OBJECT (media, "media is blocking. Installing drop probe");
    media_streams_install_drop_probe (media);
  }

  /* force the keyunit from src */
  GST_DEBUG_OBJECT (media, "sending force keyunit event");
  gst_element_send_event (priv->element,
      gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE,
          TRUE, 0));

  /* wait preroll */
  g_rec_mutex_unlock (&priv->state_lock);
  preroll_ok = wait_preroll (media);
  g_rec_mutex_lock (&priv->state_lock);

  if (!preroll_ok)
    goto preroll_failed;

  return TRUE;

start_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    return FALSE;
  }
preroll_failed:
  {
    GST_WARNING ("failed while waiting to preroll pipeline");
    return FALSE;
  }
}

/* call with state_lock */
static gboolean
default_unsuspend (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gboolean preroll_ok;

  switch (priv->suspend_mode) {
    case GST_RTSP_SUSPEND_MODE_NONE:
      if (!gst_rtsp_media_is_receive_only (media)
          && media_streams_blocking (media)) {
        g_rec_mutex_unlock (&priv->state_lock);
        if (gst_rtsp_media_get_status (media) == GST_RTSP_MEDIA_STATUS_ERROR) {
          g_rec_mutex_lock (&priv->state_lock);
          goto preroll_failed;
        }
        g_rec_mutex_lock (&priv->state_lock);
      }
      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
      break;
    case GST_RTSP_SUSPEND_MODE_PAUSE:
      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
      break;
    case GST_RTSP_SUSPEND_MODE_RESET:
    {
      gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
      /* at this point the media pipeline has been updated and contain all
       * specific transport parts: all active streams contain at least one sink
       * element and it's safe to unblock all blocked streams */
      media_streams_set_blocked (media, FALSE);
      if (!start_preroll (media))
        goto start_failed;

      g_rec_mutex_unlock (&priv->state_lock);
      preroll_ok = wait_preroll (media);
      g_rec_mutex_lock (&priv->state_lock);

      if (!preroll_ok)
        goto preroll_failed;
      break;
    }
    default:
      break;
  }

  if (gst_rtsp_media_get_ensure_keyunit_on_start (media)) {
    return ensure_new_keyunit (media);
  }

  return TRUE;

/* ERRORS */
start_failed:
  {
    GST_WARNING ("failed to preroll pipeline");
    return FALSE;
  }
preroll_failed:
  {
    GST_WARNING ("failed while waiting to preroll pipeline");
    return FALSE;
  }
}

static void
gst_rtsp_media_unblock_rtcp (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  priv = media->priv;
  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    gst_rtsp_stream_unblock_rtcp (stream);
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_unsuspend:
 * @media: a #GstRTSPMedia
 *
 * Unsuspend @media if it was in a suspended state. This method does nothing
 * when the media was not in the suspended state.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_unsuspend (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstRTSPMediaClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  g_rec_mutex_lock (&priv->state_lock);
  if (priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
    goto done;

  klass = GST_RTSP_MEDIA_GET_CLASS (media);
  if (klass->unsuspend) {
    if (!klass->unsuspend (media))
      goto unsuspend_failed;
  }

done:
  gst_rtsp_media_unblock_rtcp (media);
  g_rec_mutex_unlock (&priv->state_lock);

  return TRUE;

  /* ERRORS */
unsuspend_failed:
  {
    g_rec_mutex_unlock (&priv->state_lock);
    GST_WARNING ("failed to unsuspend media %p", media);
    gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
    return FALSE;
  }
}

/* must be called with state-lock */
static void
media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state)
{
  GstRTSPMediaPrivate *priv = media->priv;
  GstStateChangeReturn set_state_ret;
  priv->expected_async_done = FALSE;

  if (state == GST_STATE_NULL) {
    gst_rtsp_media_unprepare (media);
  } else {
    GST_INFO ("state %s media %p", gst_state_get_name (state), media);
    set_target_state (media, state, FALSE);

    if (state == GST_STATE_PLAYING) {
      /* make sure pads are not blocking anymore when going to PLAYING */
      media_streams_set_blocked (media, FALSE);
    }

    /* when we are buffering, don't update the state yet, this will be done
     * when buffering finishes */
    if (priv->buffering) {
      GST_INFO ("Buffering busy, delay state change");
    } else {
      if (state == GST_STATE_PAUSED) {
        set_state_ret = set_state (media, state);
        if (set_state_ret == GST_STATE_CHANGE_ASYNC)
          priv->expected_async_done = TRUE;
        /* and suspend after pause */
        gst_rtsp_media_suspend (media);
      } else {
        set_state (media, state);
      }
    }
  }
}

/**
 * gst_rtsp_media_set_pipeline_state:
 * @media: a #GstRTSPMedia
 * @state: the target state of the pipeline
 *
 * Set the state of the pipeline managed by @media to @state
 */
void
gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state)
{
  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  g_rec_mutex_lock (&media->priv->state_lock);
  media_set_pipeline_state_locked (media, state);
  g_rec_mutex_unlock (&media->priv->state_lock);
}

/**
 * gst_rtsp_media_set_state:
 * @media: a #GstRTSPMedia
 * @state: the target state of the media
 * @transports: (transfer none) (element-type GstRtspServer.RTSPStreamTransport):
 * a #GPtrArray of #GstRTSPStreamTransport pointers
 *
 * Set the state of @media to @state and for the transports in @transports.
 *
 * @media must be prepared with gst_rtsp_media_prepare();
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
    GPtrArray * transports)
{
  GstRTSPMediaPrivate *priv;
  gint i;
  gboolean activate, deactivate, do_state;
  gint old_active;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
  g_return_val_if_fail (transports != NULL, FALSE);

  priv = media->priv;

  g_rec_mutex_lock (&priv->state_lock);

  if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING
      && gst_rtsp_media_is_shared (media)) {
    g_rec_mutex_unlock (&priv->state_lock);
    gst_rtsp_media_get_status (media);
    g_rec_mutex_lock (&priv->state_lock);
  }
  if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR && state > GST_STATE_READY)
    goto error_status;
  if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
      priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED &&
      priv->status != GST_RTSP_MEDIA_STATUS_ERROR)
    goto not_prepared;

  /* NULL and READY are the same */
  if (state == GST_STATE_READY)
    state = GST_STATE_NULL;

  activate = deactivate = FALSE;

  GST_INFO ("going to state %s media %p, target state %s",
      gst_state_get_name (state), media,
      gst_state_get_name (priv->target_state));

  switch (state) {
    case GST_STATE_NULL:
      /* we're going from PLAYING or PAUSED to READY or NULL, deactivate */
      if (priv->target_state >= GST_STATE_PAUSED)
        deactivate = TRUE;
      break;
    case GST_STATE_PAUSED:
      /* we're going from PLAYING to PAUSED, deactivate */
      if (priv->target_state == GST_STATE_PLAYING)
        deactivate = TRUE;
      break;
    case GST_STATE_PLAYING:
      /* we're going to PLAYING, activate */
      activate = TRUE;
      break;
    default:
      break;
  }
  old_active = priv->n_active;

  GST_DEBUG ("%d transports, activate %d, deactivate %d", transports->len,
      activate, deactivate);
  for (i = 0; i < transports->len; i++) {
    GstRTSPStreamTransport *trans;

    /* we need a non-NULL entry in the array */
    trans = g_ptr_array_index (transports, i);
    if (trans == NULL)
      continue;

    if (activate) {
      if (gst_rtsp_stream_transport_set_active (trans, TRUE))
        priv->n_active++;
    } else if (deactivate) {
      if (gst_rtsp_stream_transport_set_active (trans, FALSE))
        priv->n_active--;
    }
  }

  if (activate)
    media_streams_set_blocked (media, FALSE);

  /* we just activated the first media, do the playing state change */
  if (old_active == 0 && activate)
    do_state = TRUE;
  /* if we have no more active media and prepare count is not indicate
   * that there are new session/sessions ongoing,
   * do the downward state changes */
  else if (priv->n_active == 0 && priv->prepare_count <= 1)
    do_state = TRUE;
  else
    do_state = FALSE;

  GST_INFO ("state %d active %d media %p do_state %d", state, priv->n_active,
      media, do_state);

  if (priv->target_state != state) {
    if (do_state) {
      media_set_pipeline_state_locked (media, state);
      g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
          NULL);
    }
  }

  /* remember where we are */
  if (state != GST_STATE_NULL && (state == GST_STATE_PAUSED ||
          old_active != priv->n_active)) {
    g_mutex_lock (&priv->lock);
    collect_media_stats (media);
    g_mutex_unlock (&priv->lock);
  }
  g_rec_mutex_unlock (&priv->state_lock);

  return TRUE;

  /* ERRORS */
not_prepared:
  {
    GST_WARNING ("media %p was not prepared", media);
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
error_status:
  {
    GST_WARNING ("media %p in error status while changing to state %d",
        media, state);
    g_rec_mutex_unlock (&priv->state_lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_media_set_transport_mode:
 * @media: a #GstRTSPMedia
 * @mode: the new value
 *
 * Sets if the media pipeline can work in PLAY or RECORD mode
 */
void
gst_rtsp_media_set_transport_mode (GstRTSPMedia * media,
    GstRTSPTransportMode mode)
{
  GstRTSPMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->transport_mode = mode;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_transport_mode:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media can be used for PLAY or RECORD methods.
 *
 * Returns: The transport mode.
 */
GstRTSPTransportMode
gst_rtsp_media_get_transport_mode (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstRTSPTransportMode res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->transport_mode;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_seekable:
 * @media: a #GstRTSPMedia
 *
 * Check if the pipeline for @media seek and up to what point in time,
 * it can seek.
 *
 * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning
 * and > 0 to indicate the longest duration between any two random access points.
 * %G_MAXINT64 means any value is possible.
 *
 * Since: 1.14
 */
GstClockTimeDiff
gst_rtsp_media_seekable (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  GstClockTimeDiff res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  /* Currently we are not able to seek on live streams,
   * and no stream is seekable only to the beginning */
  g_mutex_lock (&priv->lock);
  res = priv->seekable;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_media_complete_pipeline:
 * @media: a #GstRTSPMedia
 * @transports: (element-type GstRTSPTransport): a list of #GstRTSPTransport
 *
 * Add a receiver and sender parts to the pipeline based on the transport from
 * SETUP.
 *
 * Returns: %TRUE if the media pipeline has been sucessfully updated.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
  g_return_val_if_fail (transports, FALSE);

  GST_DEBUG_OBJECT (media, "complete pipeline");

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStreamTransport *transport;
    GstRTSPStream *stream;
    const GstRTSPTransport *rtsp_transport;

    transport = g_ptr_array_index (transports, i);
    if (!transport)
      continue;

    stream = gst_rtsp_stream_transport_get_stream (transport);
    if (!stream)
      continue;

    rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);

    if (!gst_rtsp_stream_complete_stream (stream, rtsp_transport)) {
      g_mutex_unlock (&priv->lock);
      return FALSE;
    }

    if (!gst_rtsp_stream_add_transport (stream, transport)) {
      g_mutex_unlock (&priv->lock);
      return FALSE;
    }

    update_stream_storage_size (media, stream, i);
  }

  priv->complete = TRUE;
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_media_is_receive_only:
 *
 * Returns: %TRUE if @media is receive-only, %FALSE otherwise.
 * Since: 1.18
 */
gboolean
gst_rtsp_media_is_receive_only (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gboolean receive_only;

  g_mutex_lock (&priv->lock);
  receive_only = is_receive_only (media);
  g_mutex_unlock (&priv->lock);

  return receive_only;
}

/**
 * gst_rtsp_media_has_completed_sender:
 *
 * See gst_rtsp_stream_is_complete(), gst_rtsp_stream_is_sender().
 *
 * Returns: whether @media has at least one complete sender stream.
 * Since: 1.18
 */
gboolean
gst_rtsp_media_has_completed_sender (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv = media->priv;
  gboolean sender = FALSE;
  guint i;

  g_mutex_lock (&priv->lock);
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
    if (gst_rtsp_stream_is_complete (stream))
      if (gst_rtsp_stream_is_sender (stream) ||
          !gst_rtsp_stream_is_receiver (stream)) {
        sender = TRUE;
        break;
      }
  }
  g_mutex_unlock (&priv->lock);

  return sender;
}

/**
 * gst_rtsp_media_set_rate_control:
 *
 * Define whether @media will follow the Rate-Control=no behaviour as specified
 * in the ONVIF replay spec.
 *
 * Since: 1.18
 */
void
gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled)
{
  GstRTSPMediaPrivate *priv;
  guint i;

  g_return_if_fail (GST_IS_RTSP_MEDIA (media));

  GST_LOG_OBJECT (media, "%s rate control", enabled ? "Enabling" : "Disabling");

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->do_rate_control = enabled;
  for (i = 0; i < priv->streams->len; i++) {
    GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);

    gst_rtsp_stream_set_rate_control (stream, enabled);

  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_media_get_rate_control:
 *
 * Returns: whether @media will follow the Rate-Control=no behaviour as specified
 * in the ONVIF replay spec.
 *
 * Since: 1.18
 */
gboolean
gst_rtsp_media_get_rate_control (GstRTSPMedia * media)
{
  GstRTSPMediaPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  res = priv->do_rate_control;
  g_mutex_unlock (&priv->lock);

  return res;
}
07070100000043000081A400000000000000000000000169D554BF000046FD000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-media.h   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp/rtsp.h>
#include <gst/net/gstnet.h>

#ifndef __GST_RTSP_MEDIA_H__
#define __GST_RTSP_MEDIA_H__

#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

/* types for the media */
#define GST_TYPE_RTSP_MEDIA              (gst_rtsp_media_get_type ())
#define GST_IS_RTSP_MEDIA(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA))
#define GST_IS_RTSP_MEDIA_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA))
#define GST_RTSP_MEDIA_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass))
#define GST_RTSP_MEDIA(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMedia))
#define GST_RTSP_MEDIA_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass))
#define GST_RTSP_MEDIA_CAST(obj)         ((GstRTSPMedia*)(obj))
#define GST_RTSP_MEDIA_CLASS_CAST(klass) ((GstRTSPMediaClass*)(klass))

typedef struct _GstRTSPMedia GstRTSPMedia;
typedef struct _GstRTSPMediaClass GstRTSPMediaClass;
typedef struct _GstRTSPMediaPrivate GstRTSPMediaPrivate;

/**
 * GstRTSPMediaStatus:
 * @GST_RTSP_MEDIA_STATUS_UNPREPARED: media pipeline not prerolled
 * @GST_RTSP_MEDIA_STATUS_UNPREPARING: media pipeline is busy doing a clean
 *                                     shutdown.
 * @GST_RTSP_MEDIA_STATUS_PREPARING: media pipeline is prerolling
 * @GST_RTSP_MEDIA_STATUS_PREPARED: media pipeline is prerolled
 * @GST_RTSP_MEDIA_STATUS_SUSPENDED: media is suspended
 * @GST_RTSP_MEDIA_STATUS_ERROR: media pipeline is in error
 *
 * The state of the media pipeline.
 */
typedef enum {
  GST_RTSP_MEDIA_STATUS_UNPREPARED  = 0,
  GST_RTSP_MEDIA_STATUS_UNPREPARING = 1,
  GST_RTSP_MEDIA_STATUS_PREPARING   = 2,
  GST_RTSP_MEDIA_STATUS_PREPARED    = 3,
  GST_RTSP_MEDIA_STATUS_SUSPENDED   = 4,
  GST_RTSP_MEDIA_STATUS_ERROR       = 5
} GstRTSPMediaStatus;

/**
 * GstRTSPSuspendMode:
 * @GST_RTSP_SUSPEND_MODE_NONE: Media is not suspended
 * @GST_RTSP_SUSPEND_MODE_PAUSE: Media is PAUSED in suspend
 * @GST_RTSP_SUSPEND_MODE_RESET: The media is set to NULL when suspended
 *
 * The suspend mode of the media pipeline. A media pipeline is suspended right
 * after creating the SDP and when the client performs a PAUSED request.
 */
typedef enum {
  GST_RTSP_SUSPEND_MODE_NONE   = 0,
  GST_RTSP_SUSPEND_MODE_PAUSE  = 1,
  GST_RTSP_SUSPEND_MODE_RESET  = 2
} GstRTSPSuspendMode;

/**
 * GstRTSPTransportMode:
 * @GST_RTSP_TRANSPORT_MODE_PLAY: Transport supports PLAY mode
 * @GST_RTSP_TRANSPORT_MODE_RECORD: Transport supports RECORD mode
 *
 * The supported modes of the media.
 */
typedef enum {
  GST_RTSP_TRANSPORT_MODE_PLAY    = 1,
  GST_RTSP_TRANSPORT_MODE_RECORD  = 2,
} GstRTSPTransportMode;

/**
 * GstRTSPPublishClockMode:
 * @GST_RTSP_PUBLISH_CLOCK_MODE_NONE: Publish nothing
 * @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK: Publish the clock but not the offset
 * @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET: Publish the clock and offset
 *
 * Whether the clock and possibly RTP/clock offset should be published according to RFC7273.
 */
typedef enum {
  GST_RTSP_PUBLISH_CLOCK_MODE_NONE,
  GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK,
  GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET
} GstRTSPPublishClockMode;

#define GST_TYPE_RTSP_TRANSPORT_MODE (gst_rtsp_transport_mode_get_type())
GST_RTSP_SERVER_API
GType gst_rtsp_transport_mode_get_type (void);

#define GST_TYPE_RTSP_SUSPEND_MODE (gst_rtsp_suspend_mode_get_type())
GST_RTSP_SERVER_API
GType gst_rtsp_suspend_mode_get_type (void);

#define GST_TYPE_RTSP_PUBLISH_CLOCK_MODE (gst_rtsp_publish_clock_mode_get_type())
GST_RTSP_SERVER_API
GType gst_rtsp_publish_clock_mode_get_type (void);

#include "rtsp-stream.h"
#include "rtsp-thread-pool.h"
#include "rtsp-permissions.h"
#include "rtsp-address-pool.h"
#include "rtsp-sdp.h"

/**
 * GstRTSPMedia:
 *
 * A class that contains the GStreamer element along with a list of
 * #GstRTSPStream objects that can produce data.
 *
 * This object is usually created from a #GstRTSPMediaFactory.
 */
struct _GstRTSPMedia {
  GObject            parent;

  /*< private >*/
  GstRTSPMediaPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPMediaClass:
 * @handle_message: handle a message
 * @prepare: the default implementation adds all elements and sets the
 *           pipeline's state to GST_STATE_PAUSED (or GST_STATE_PLAYING
 *           in case of NO_PREROLL elements).
 * @unprepare: the default implementation sets the pipeline's state
 *             to GST_STATE_NULL and removes all elements.
 * @suspend: the default implementation sets the pipeline's state to
 *           GST_STATE_NULL GST_STATE_PAUSED depending on the selected
 *           suspend mode.
 * @unsuspend: the default implementation reverts the suspend operation.
 *             The pipeline will be prerolled again if it's state was
 *             set to GST_STATE_NULL in suspend.
 * @convert_range: convert a range to the given unit
 * @query_position: query the current position in the pipeline
 * @query_stop: query when playback will stop
 *
 * The RTSP media class
 */
struct _GstRTSPMediaClass {
  GObjectClass  parent_class;

  /* vmethods */
  gboolean        (*handle_message)  (GstRTSPMedia *media, GstMessage *message);
  gboolean        (*prepare)         (GstRTSPMedia *media, GstRTSPThread *thread);
  gboolean        (*unprepare)       (GstRTSPMedia *media);
  gboolean        (*suspend)         (GstRTSPMedia *media);
  gboolean        (*unsuspend)       (GstRTSPMedia *media);
  gboolean        (*convert_range)   (GstRTSPMedia *media, GstRTSPTimeRange *range,
                                      GstRTSPRangeUnit unit);
  gboolean        (*query_position)  (GstRTSPMedia *media, gint64 *position);
  gboolean        (*query_stop)      (GstRTSPMedia *media, gint64 *stop);
  GstElement *    (*create_rtpbin)   (GstRTSPMedia *media);
  gboolean        (*setup_rtpbin)    (GstRTSPMedia *media, GstElement *rtpbin);
  gboolean        (*setup_sdp)       (GstRTSPMedia *media, GstSDPMessage *sdp, GstSDPInfo *info);

  /* signals */
  void            (*new_stream)      (GstRTSPMedia *media, GstRTSPStream * stream);
  void            (*removed_stream)  (GstRTSPMedia *media, GstRTSPStream * stream);

  void            (*prepared)        (GstRTSPMedia *media);
  void            (*unprepared)      (GstRTSPMedia *media);

  void            (*target_state)    (GstRTSPMedia *media, GstState state);
  void            (*new_state)       (GstRTSPMedia *media, GstState state);

  gboolean        (*handle_sdp)      (GstRTSPMedia *media, GstSDPMessage *sdp);

  /*< private >*/
  gpointer         _gst_reserved[GST_PADDING_LARGE-1];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_media_get_type         (void);

/* creating the media */

GST_RTSP_SERVER_API
GstRTSPMedia *        gst_rtsp_media_new              (GstElement *element) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstElement *          gst_rtsp_media_get_element      (GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_take_pipeline    (GstRTSPMedia *media, GstPipeline *pipeline);

GST_RTSP_SERVER_API
GstRTSPMediaStatus    gst_rtsp_media_get_status       (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_permissions  (GstRTSPMedia *media,
                                                       GstRTSPPermissions *permissions);

GST_RTSP_SERVER_API
GstRTSPPermissions *  gst_rtsp_media_get_permissions  (GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_shared       (GstRTSPMedia *media, gboolean shared);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_shared        (GstRTSPMedia *media);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_can_be_shared    (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia *media, gboolean stop_on_disconnect);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_stop_on_disconnect  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_transport_mode  (GstRTSPMedia *media, GstRTSPTransportMode mode);

GST_RTSP_SERVER_API
GstRTSPTransportMode  gst_rtsp_media_get_transport_mode  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_reusable     (GstRTSPMedia *media, gboolean reusable);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_reusable      (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_profiles     (GstRTSPMedia *media, GstRTSPProfile profiles);

GST_RTSP_SERVER_API
GstRTSPProfile        gst_rtsp_media_get_profiles     (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_protocols    (GstRTSPMedia *media, GstRTSPLowerTrans protocols);

GST_RTSP_SERVER_API
GstRTSPLowerTrans     gst_rtsp_media_get_protocols    (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_eos_shutdown (GstRTSPMedia *media, gboolean eos_shutdown);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_eos_shutdown  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_address_pool (GstRTSPMedia *media, GstRTSPAddressPool *pool);

GST_RTSP_SERVER_API
GstRTSPAddressPool *  gst_rtsp_media_get_address_pool (GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_multicast_iface (GstRTSPMedia *media, const gchar *multicast_iface);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_media_get_multicast_iface (GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_buffer_size  (GstRTSPMedia *media, guint size);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_get_buffer_size  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_ensure_keyunit_on_start (GstRTSPMedia* media,
                                                                  gboolean ensure_keyunit_on_start);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_get_ensure_keyunit_on_start (GstRTSPMedia* media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_ensure_keyunit_on_start_timeout (GstRTSPMedia* media,
                                                                          guint timeout);

GST_RTSP_SERVER_API
guint          gst_rtsp_media_get_ensure_keyunit_on_start_timeout (GstRTSPMedia* media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_retransmission_time  (GstRTSPMedia *media, GstClockTime time);

GST_RTSP_SERVER_API
GstClockTime          gst_rtsp_media_get_retransmission_time  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
                                                            gboolean do_retransmission);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_latency      (GstRTSPMedia *media, guint latency);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_get_latency      (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_use_time_provider (GstRTSPMedia *media, gboolean time_provider);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_time_provider  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
GstNetTimeProvider *  gst_rtsp_media_get_time_provider (GstRTSPMedia *media,
                                                        const gchar *address, guint16 port) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_clock         (GstRTSPMedia *media, GstClock * clock);

GST_RTSP_SERVER_API
void                    gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media, GstRTSPPublishClockMode mode);

GST_RTSP_SERVER_API
GstRTSPPublishClockMode gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media);

GST_RTSP_SERVER_API
gboolean                gst_rtsp_media_set_max_mcast_ttl  (GstRTSPMedia *media, guint ttl);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_get_max_mcast_ttl  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_bind_mcast_address  (GstRTSPMedia *media, gboolean bind_mcast_addr);
GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_bind_mcast_address  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_dscp_qos (GstRTSPMedia * media, gint dscp_qos);
GST_RTSP_SERVER_API
gint                  gst_rtsp_media_get_dscp_qos (GstRTSPMedia * media);

/* prepare the media for playback */

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_prepare          (GstRTSPMedia *media, GstRTSPThread *thread);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_unprepare        (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_suspend_mode (GstRTSPMedia *media, GstRTSPSuspendMode mode);

GST_RTSP_SERVER_API
GstRTSPSuspendMode    gst_rtsp_media_get_suspend_mode (GstRTSPMedia *media);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_suspend          (GstRTSPMedia *media);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_unsuspend        (GstRTSPMedia *media);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_setup_sdp        (GstRTSPMedia * media, GstSDPMessage * sdp,
                                                       GstSDPInfo * info);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);

/* creating streams */

GST_RTSP_SERVER_API
void                  gst_rtsp_media_collect_streams  (GstRTSPMedia *media);

GST_RTSP_SERVER_API
GstRTSPStream *       gst_rtsp_media_create_stream    (GstRTSPMedia *media,
                                                       GstElement *payloader,
                                                       GstPad *pad);

/* dealing with the media */

GST_RTSP_SERVER_API
void                  gst_rtsp_media_lock             (GstRTSPMedia *media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_unlock           (GstRTSPMedia *media);

GST_RTSP_SERVER_API
GstClock *            gst_rtsp_media_get_clock        (GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstClockTime          gst_rtsp_media_get_base_time    (GstRTSPMedia *media);

GST_RTSP_SERVER_API
guint                 gst_rtsp_media_n_streams        (GstRTSPMedia *media);

GST_RTSP_SERVER_API
GstRTSPStream *       gst_rtsp_media_get_stream       (GstRTSPMedia *media, guint idx);

GST_RTSP_SERVER_API
GstRTSPStream *       gst_rtsp_media_find_stream      (GstRTSPMedia *media, const gchar * control);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_seek             (GstRTSPMedia *media, GstRTSPTimeRange *range);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_seek_full        (GstRTSPMedia *media,
                                                       GstRTSPTimeRange *range,
                                                       GstSeekFlags flags);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_seek_trickmode   (GstRTSPMedia *media,
                                                       GstRTSPTimeRange *range,
                                                       GstSeekFlags flags,
                                                       gdouble rate,
                                                       GstClockTime trickmode_interval);

GST_RTSP_SERVER_API
GstClockTimeDiff      gst_rtsp_media_seekable         (GstRTSPMedia *media);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_media_get_range_string (GstRTSPMedia *media,
                                                       gboolean play,
                                                       GstRTSPRangeUnit unit) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_get_rates        (GstRTSPMedia * media,
                                                       gdouble * rate,
                                                       gdouble * applied_rate);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_set_state        (GstRTSPMedia *media, GstState state,
                                                       GPtrArray *transports);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media,
                                                         GstState state);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_is_receive_only (GstRTSPMedia * media);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_has_completed_sender (GstRTSPMedia * media);

GST_RTSP_SERVER_API
void                  gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_media_get_rate_control (GstRTSPMedia * media);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMedia, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_MEDIA_H__ */
   07070100000044000081A400000000000000000000000169D554BF0000292A000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-mount-points.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-mount-points
 * @short_description: Map a path to media
 * @see_also: #GstRTSPMediaFactory, #GstRTSPClient
 *
 * A #GstRTSPMountPoints object maintains a relation between paths
 * and #GstRTSPMediaFactory objects. This object is usually given to
 * #GstRTSPClient and used to find the media attached to a path.
 *
 * With gst_rtsp_mount_points_add_factory () and
 * gst_rtsp_mount_points_remove_factory(), factories can be added and
 * removed.
 *
 * With gst_rtsp_mount_points_match() you can find the #GstRTSPMediaFactory
 * object that completely matches the given path.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-mount-points.h"

typedef struct
{
  gchar *path;
  gint len;
  GstRTSPMediaFactory *factory;
} DataItem;

static DataItem *
data_item_new (gchar * path, gint len, GstRTSPMediaFactory * factory)
{
  DataItem *item;

  item = g_new (DataItem, 1);
  item->path = path;
  item->len = len;
  item->factory = factory;

  return item;
}

static void
data_item_free (gpointer data)
{
  DataItem *item = data;

  g_free (item->path);
  g_object_unref (item->factory);
  g_free (item);
}

static void
data_item_dump (gconstpointer a, gconstpointer prefix)
{
  const DataItem *item = a;

  GST_DEBUG ("%s%s %p", (gchar *) prefix, item->path, item->factory);
}

static gint
data_item_compare (gconstpointer a, gconstpointer b, gpointer user_data)
{
  const DataItem *item1 = a, *item2 = b;
  gint res;

  res = g_strcmp0 (item1->path, item2->path);

  return res;
}

struct _GstRTSPMountPointsPrivate
{
  GMutex lock;
  GSequence *mounts;            /* protected by lock */
  gboolean dirty;
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMountPoints, gst_rtsp_mount_points,
    G_TYPE_OBJECT);

GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug);
#define GST_CAT_DEFAULT rtsp_media_debug

static gchar *default_make_path (GstRTSPMountPoints * mounts,
    const GstRTSPUrl * url);
static void gst_rtsp_mount_points_finalize (GObject * obj);

static void
gst_rtsp_mount_points_class_init (GstRTSPMountPointsClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gst_rtsp_mount_points_finalize;

  klass->make_path = default_make_path;

  GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmountpoints", 0,
      "GstRTSPMountPoints");
}

static void
gst_rtsp_mount_points_init (GstRTSPMountPoints * mounts)
{
  GstRTSPMountPointsPrivate *priv;

  GST_DEBUG_OBJECT (mounts, "created");

  mounts->priv = priv = gst_rtsp_mount_points_get_instance_private (mounts);

  g_mutex_init (&priv->lock);
  priv->mounts = g_sequence_new (data_item_free);
  priv->dirty = FALSE;
}

static void
gst_rtsp_mount_points_finalize (GObject * obj)
{
  GstRTSPMountPoints *mounts = GST_RTSP_MOUNT_POINTS (obj);
  GstRTSPMountPointsPrivate *priv = mounts->priv;

  GST_DEBUG_OBJECT (mounts, "finalized");

  g_sequence_free (priv->mounts);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_mount_points_parent_class)->finalize (obj);
}

/**
 * gst_rtsp_mount_points_new:
 *
 * Make a new mount points object.
 *
 * Returns: (transfer full): a new #GstRTSPMountPoints
 */
GstRTSPMountPoints *
gst_rtsp_mount_points_new (void)
{
  GstRTSPMountPoints *result;

  result = g_object_new (GST_TYPE_RTSP_MOUNT_POINTS, NULL);

  return result;
}

static gchar *
default_make_path (GstRTSPMountPoints * mounts, const GstRTSPUrl * url)
{
  /* normalize rtsp://<IP>:<PORT> to rtsp://<IP>:<PORT>/ */
  return g_strdup (url->abspath[0] ? url->abspath : "/");
}

/**
 * gst_rtsp_mount_points_make_path:
 * @mounts: a #GstRTSPMountPoints
 * @url: a #GstRTSPUrl
 *
 * Make a path string from @url.
 *
 * Returns: (transfer full) (nullable): a path string for @url, g_free() after usage.
 */
gchar *
gst_rtsp_mount_points_make_path (GstRTSPMountPoints * mounts,
    const GstRTSPUrl * url)
{
  GstRTSPMountPointsClass *klass;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_MOUNT_POINTS (mounts), NULL);
  g_return_val_if_fail (url != NULL, NULL);

  klass = GST_RTSP_MOUNT_POINTS_GET_CLASS (mounts);

  if (klass->make_path)
    result = klass->make_path (mounts, url);
  else
    result = NULL;

  return result;
}

static gboolean
has_prefix (DataItem * str, DataItem * prefix)
{
  /* prefix needs to be smaller than str */
  if (str->len < prefix->len)
    return FALSE;

  /* special case when "/" is the entire prefix */
  if (prefix->len == 1 && prefix->path[0] == '/' && str->path[0] == '/')
    return TRUE;

  /* if str is larger, it there should be a / following the prefix */
  if (str->len > prefix->len && str->path[prefix->len] != '/')
    return FALSE;

  return strncmp (str->path, prefix->path, prefix->len) == 0;
}

/**
 * gst_rtsp_mount_points_match:
 * @mounts: a #GstRTSPMountPoints
 * @path: a mount point
 * @matched: (out) (allow-none): the amount of @path matched
 *
 * Find the factory in @mounts that has the longest match with @path.
 *
 * If @matched is %NULL, @path will match the factory exactly otherwise
 * the amount of characters that matched is returned in @matched.
 *
 * Returns: (transfer full): the #GstRTSPMediaFactory for @path.
 * g_object_unref() after usage.
 */
GstRTSPMediaFactory *
gst_rtsp_mount_points_match (GstRTSPMountPoints * mounts,
    const gchar * path, gint * matched)
{
  GstRTSPMountPointsPrivate *priv;
  GstRTSPMediaFactory *result = NULL;
  GSequenceIter *iter, *best;
  DataItem item, *ritem;

  g_return_val_if_fail (GST_IS_RTSP_MOUNT_POINTS (mounts), NULL);
  g_return_val_if_fail (path != NULL, NULL);

  priv = mounts->priv;

  item.path = (gchar *) path;
  item.len = strlen (path);

  GST_LOG ("Looking for mount point path %s", path);

  g_mutex_lock (&priv->lock);
  if (priv->dirty) {
    g_sequence_sort (priv->mounts, data_item_compare, mounts);
    g_sequence_foreach (priv->mounts, (GFunc) data_item_dump,
        (gpointer) "sort :");
    priv->dirty = FALSE;
  }

  /* find the location of the media in the hashtable we only use the absolute
   * path of the uri to find a media factory. If the factory depends on other
   * properties found in the url, this method should be overridden. */
  iter = g_sequence_get_begin_iter (priv->mounts);
  best = NULL;
  while (!g_sequence_iter_is_end (iter)) {
    ritem = g_sequence_get (iter);

    data_item_dump (ritem, "inspect: ");

    /* The sequence is sorted, so any prefix match is an improvement upon
     * the previous best match, as '/abc' will always be before '/abcd' */
    if (has_prefix (&item, ritem)) {
      if (best == NULL) {
        data_item_dump (ritem, "prefix: ");
      } else {
        data_item_dump (ritem, "new best: ");
      }
      best = iter;
    } else {
      /* if have a match and the current item doesn't prefix match the best we
       * found so far then we're moving away and can bail out of the loop */
      if (best != NULL && !has_prefix (ritem, g_sequence_get (best)))
        break;
    }

    iter = g_sequence_iter_next (iter);
  }
  if (best) {
    ritem = g_sequence_get (best);
    data_item_dump (ritem, "result: ");
    if (matched || ritem->len == item.len) {
      result = g_object_ref (ritem->factory);
      if (matched)
        *matched = ritem->len;
    }
  }
  g_mutex_unlock (&priv->lock);

  GST_INFO ("found media factory %p for path %s", result, path);

  return result;
}

static void
gst_rtsp_mount_points_remove_factory_unlocked (GstRTSPMountPoints * mounts,
    const gchar * path)
{
  GstRTSPMountPointsPrivate *priv = mounts->priv;
  DataItem item;
  GSequenceIter *iter;

  item.path = (gchar *) path;

  if (priv->dirty) {
    g_sequence_sort (priv->mounts, data_item_compare, mounts);
    priv->dirty = FALSE;
  }
  iter = g_sequence_lookup (priv->mounts, &item, data_item_compare, mounts);
  if (iter) {
    g_sequence_remove (iter);
    priv->dirty = TRUE;
  }
}

/**
 * gst_rtsp_mount_points_add_factory:
 * @mounts: a #GstRTSPMountPoints
 * @path: a mount point
 * @factory: (transfer full): a #GstRTSPMediaFactory
 *
 * Attach @factory to the mount point @path in @mounts.
 *
 * @path is either of the form (/node)+ or the root path '/'. (An empty path is
 * not allowed.) Any previous mount point will be freed.
 *
 * Ownership is taken of the reference on @factory so that @factory should not be
 * used after calling this function.
 */
void
gst_rtsp_mount_points_add_factory (GstRTSPMountPoints * mounts,
    const gchar * path, GstRTSPMediaFactory * factory)
{
  GstRTSPMountPointsPrivate *priv;
  DataItem *item;

  g_return_if_fail (GST_IS_RTSP_MOUNT_POINTS (mounts));
  g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
  g_return_if_fail (path != NULL && path[0] == '/');

  priv = mounts->priv;

  item = data_item_new (g_strdup (path), strlen (path), factory);

  GST_INFO ("adding media factory %p for path %s", factory, path);

  g_mutex_lock (&priv->lock);
  gst_rtsp_mount_points_remove_factory_unlocked (mounts, path);
  g_sequence_append (priv->mounts, item);
  priv->dirty = TRUE;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_mount_points_remove_factory:
 * @mounts: a #GstRTSPMountPoints
 * @path: a mount point
 *
 * Remove the #GstRTSPMediaFactory associated with @path in @mounts.
 */
void
gst_rtsp_mount_points_remove_factory (GstRTSPMountPoints * mounts,
    const gchar * path)
{
  GstRTSPMountPointsPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_MOUNT_POINTS (mounts));
  g_return_if_fail (path != NULL && path[0] == '/');

  priv = mounts->priv;

  GST_INFO ("removing media factory for path %s", path);

  g_mutex_lock (&priv->lock);
  gst_rtsp_mount_points_remove_factory_unlocked (mounts, path);
  g_mutex_unlock (&priv->lock);
}
  07070100000045000081A400000000000000000000000169D554BF00000FEE000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-mount-points.h    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include "rtsp-media-factory.h"

#ifndef __GST_RTSP_MOUNT_POINTS_H__
#define __GST_RTSP_MOUNT_POINTS_H__

G_BEGIN_DECLS

#define GST_TYPE_RTSP_MOUNT_POINTS              (gst_rtsp_mount_points_get_type ())
#define GST_IS_RTSP_MOUNT_POINTS(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MOUNT_POINTS))
#define GST_IS_RTSP_MOUNT_POINTS_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MOUNT_POINTS))
#define GST_RTSP_MOUNT_POINTS_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MOUNT_POINTS, GstRTSPMountPointsClass))
#define GST_RTSP_MOUNT_POINTS(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MOUNT_POINTS, GstRTSPMountPoints))
#define GST_RTSP_MOUNT_POINTS_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MOUNT_POINTS, GstRTSPMountPointsClass))
#define GST_RTSP_MOUNT_POINTS_CAST(obj)         ((GstRTSPMountPoints*)(obj))
#define GST_RTSP_MOUNT_POINTS_CLASS_CAST(klass) ((GstRTSPMountPointsClass*)(klass))

typedef struct _GstRTSPMountPoints GstRTSPMountPoints;
typedef struct _GstRTSPMountPointsClass GstRTSPMountPointsClass;
typedef struct _GstRTSPMountPointsPrivate GstRTSPMountPointsPrivate;

/**
 * GstRTSPMountPoints:
 *
 * Creates a #GstRTSPMediaFactory object for a given url.
 */
struct _GstRTSPMountPoints {
  GObject       parent;

  /*< private >*/
  GstRTSPMountPointsPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPMountPointsClass:
 * @make_path: make a path from the given url.
 *
 * The class for the media mounts object.
 */
struct _GstRTSPMountPointsClass {
  GObjectClass  parent_class;

  gchar * (*make_path) (GstRTSPMountPoints *mounts,
                        const GstRTSPUrl *url);

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_mount_points_get_type       (void);

/* creating a mount points */

GST_RTSP_SERVER_API
GstRTSPMountPoints *  gst_rtsp_mount_points_new            (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gchar *               gst_rtsp_mount_points_make_path      (GstRTSPMountPoints *mounts,
                                                            const GstRTSPUrl * url) G_GNUC_WARN_UNUSED_RESULT;
/* finding a media factory */

GST_RTSP_SERVER_API
GstRTSPMediaFactory * gst_rtsp_mount_points_match          (GstRTSPMountPoints *mounts,
                                                            const gchar *path,
                                                            gint * matched) G_GNUC_WARN_UNUSED_RESULT;
/* managing media to a mount point */

GST_RTSP_SERVER_API
void                  gst_rtsp_mount_points_add_factory    (GstRTSPMountPoints *mounts,
                                                            const gchar *path,
                                                            GstRTSPMediaFactory *factory);

GST_RTSP_SERVER_API
void                  gst_rtsp_mount_points_remove_factory (GstRTSPMountPoints *mounts,
                                                            const gchar *path);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMountPoints, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_MOUNT_POINTS_H__ */
  07070100000046000081A400000000000000000000000169D554BF000018FA000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-client.c    /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-onvif-client.h"
#include "rtsp-onvif-server.h"
#include "rtsp-onvif-media-factory.h"

G_DEFINE_TYPE (GstRTSPOnvifClient, gst_rtsp_onvif_client, GST_TYPE_RTSP_CLIENT);

static gchar *
gst_rtsp_onvif_client_check_requirements (GstRTSPClient * client,
    GstRTSPContext * ctx, gchar ** requirements)
{
  GstRTSPMountPoints *mount_points = NULL;
  GstRTSPMediaFactory *factory = NULL;
  gchar *path = NULL;
  gboolean has_backchannel = FALSE;
  gboolean has_replay = FALSE;
  GString *unsupported = g_string_new ("");

  while (*requirements) {
    if (strcmp (*requirements, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT) == 0) {
      has_backchannel = TRUE;
    } else if (strcmp (*requirements, GST_RTSP_ONVIF_REPLAY_REQUIREMENT) == 0) {
      has_replay = TRUE;
    } else {
      if (unsupported->len)
        g_string_append (unsupported, ", ");
      g_string_append (unsupported, *requirements);
    }
    requirements++;
  }

  if (unsupported->len)
    goto out;

  mount_points = gst_rtsp_client_get_mount_points (client);
  if (!(path = gst_rtsp_mount_points_make_path (mount_points, ctx->uri)))
    goto out;

  if (!(factory = gst_rtsp_mount_points_match (mount_points, path, NULL)))
    goto out;

  if (has_backchannel && !GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory)) {
    if (unsupported->len)
      g_string_append (unsupported, ", ");
    g_string_append (unsupported, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT);
  } else if (has_backchannel) {
    GstRTSPOnvifMediaFactory *onvif_factory =
        GST_RTSP_ONVIF_MEDIA_FACTORY (factory);

    if (!gst_rtsp_onvif_media_factory_has_backchannel_support (onvif_factory)) {
      if (unsupported->len)
        g_string_append (unsupported, ", ");
      g_string_append (unsupported, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT);
    }
  }

  if (has_replay && !GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory)) {
    if (unsupported->len)
      g_string_append (unsupported, ", ");
    g_string_append (unsupported, GST_RTSP_ONVIF_REPLAY_REQUIREMENT);
  } else if (has_replay) {
    GstRTSPOnvifMediaFactory *onvif_factory =
        GST_RTSP_ONVIF_MEDIA_FACTORY (factory);

    if (!gst_rtsp_onvif_media_factory_has_replay_support (onvif_factory)) {
      if (unsupported->len)
        g_string_append (unsupported, ", ");
      g_string_append (unsupported, GST_RTSP_ONVIF_REPLAY_REQUIREMENT);
    }
  }


out:
  if (path)
    g_free (path);
  if (factory)
    g_object_unref (factory);
  if (mount_points)
    g_object_unref (mount_points);

  return g_string_free (unsupported, FALSE);
}

static GstRTSPStatusCode
gst_rtsp_onvif_client_adjust_play_mode (GstRTSPClient * client,
    GstRTSPContext * ctx, GstRTSPTimeRange ** range, GstSeekFlags * flags,
    gdouble * rate, GstClockTime * trickmode_interval,
    gboolean * enable_rate_control)
{
  GstRTSPStatusCode ret = GST_RTSP_STS_BAD_REQUEST;
  gchar **split = NULL;
  gchar *str;

  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_FRAMES,
          &str, 0) == GST_RTSP_OK) {

    split = g_strsplit (str, "/", 2);

    if (!g_strcmp0 (split[0], "intra")) {
      if (split[1]) {
        guint64 interval;
        gchar *end;

        interval = g_ascii_strtoull (split[1], &end, 10);

        if (!end || *end != '\0') {
          GST_ERROR ("Unexpected interval value %s", split[1]);
          goto done;
        }

        *trickmode_interval = interval * GST_MSECOND;
      }
      *flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS;
    } else if (!g_strcmp0 (split[0], "predicted")) {
      if (split[1]) {
        GST_ERROR ("Predicted frames mode does not allow an interval (%s)",
            str);
        goto done;
      }
      *flags |= GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED;
    } else {
      GST_ERROR ("Invalid frames mode (%s)", str);
      goto done;
    }
  }

  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RATE_CONTROL,
          &str, 0) == GST_RTSP_OK) {
    if (!g_strcmp0 (str, "no")) {
      *enable_rate_control = FALSE;
    } else if (!g_strcmp0 (str, "yes")) {
      *enable_rate_control = TRUE;
    } else {
      GST_ERROR ("Invalid rate control header: %s", str);
      goto done;
    }
  }

  ret = GST_RTSP_STS_OK;

done:
  if (split)
    g_strfreev (split);
  return ret;
}

static GstRTSPStatusCode
gst_rtsp_onvif_client_adjust_play_response (GstRTSPClient * client,
    GstRTSPContext * ctx)
{
  GstRTSPStatusCode ret = GST_RTSP_STS_OK;
  gchar *str;

  if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RATE_CONTROL,
          &str, 0) == GST_RTSP_OK) {
    gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_RATE_CONTROL,
        gst_rtsp_media_get_rate_control (ctx->media) ? "yes" : "no");
  }

  return ret;
}

static void
gst_rtsp_onvif_client_class_init (GstRTSPOnvifClientClass * klass)
{
  GstRTSPClientClass *client_klass = (GstRTSPClientClass *) klass;

  client_klass->check_requirements = gst_rtsp_onvif_client_check_requirements;
  client_klass->adjust_play_mode = gst_rtsp_onvif_client_adjust_play_mode;
  client_klass->adjust_play_response =
      gst_rtsp_onvif_client_adjust_play_response;
}

static void
gst_rtsp_onvif_client_init (GstRTSPOnvifClient * client)
{
}

/**
 * gst_rtsp_onvif_client_new:
 *
 * Create a new #GstRTSPOnvifClient instance.
 *
 * Returns: (transfer full): a new #GstRTSPOnvifClient
 * Since: 1.18
 */
GstRTSPClient *
gst_rtsp_onvif_client_new (void)
{
  GstRTSPClient *result;

  result = g_object_new (GST_TYPE_RTSP_ONVIF_CLIENT, NULL);

  return result;
}
  07070100000047000081A400000000000000000000000169D554BF0000098B000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-client.h    /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_ONVIF_CLIENT_H__
#define __GST_RTSP_ONVIF_CLIENT_H__

#include <gst/gst.h>
#include "rtsp-client.h"

#define GST_TYPE_RTSP_ONVIF_CLIENT              (gst_rtsp_onvif_client_get_type ())
#define GST_IS_RTSP_ONVIF_CLIENT(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_ONVIF_CLIENT))
#define GST_IS_RTSP_ONVIF_CLIENT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_ONVIF_CLIENT))
#define GST_RTSP_ONVIF_CLIENT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_ONVIF_CLIENT, GstRTSPOnvifClientClass))
#define GST_RTSP_ONVIF_CLIENT(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_ONVIF_CLIENT, GstRTSPOnvifClient))
#define GST_RTSP_ONVIF_CLIENT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_ONVIF_CLIENT, GstRTSPOnvifClientClass))
#define GST_RTSP_ONVIF_CLIENT_CAST(obj)         ((GstRTSPOnvifClient*)(obj))
#define GST_RTSP_ONVIF_CLIENT_CLASS_CAST(klass) ((GstRTSPOnvifClientClass*)(klass))

typedef struct GstRTSPOnvifClientClass GstRTSPOnvifClientClass;
typedef struct GstRTSPOnvifClient GstRTSPOnvifClient;

/**
 * GstRTSPOnvifClient:
 *
 * Since: 1.14
 */
struct GstRTSPOnvifClientClass
{
  GstRTSPClientClass parent;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE];
};

struct GstRTSPOnvifClient
{
  GstRTSPClient parent;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType gst_rtsp_onvif_client_get_type (void);

GST_RTSP_SERVER_API
GstRTSPClient * gst_rtsp_onvif_client_new (void) G_GNUC_WARN_UNUSED_RESULT;

#endif /* __GST_RTSP_ONVIF_CLIENT_H__ */
 07070100000048000081A400000000000000000000000169D554BF00004134000000000000000000000000000000000000004200000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-media-factory.c /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:rtsp-onvif-media-factory
 * @short_description: A factory for ONVIF media pipelines
 * @see_also: #GstRTSPMediaFactory, #GstRTSPOnvifMedia
 *
 * The #GstRTSPOnvifMediaFactory is responsible for creating or recycling
 * #GstRTSPMedia objects based on the passed URL. Different to
 * #GstRTSPMediaFactory, this supports special ONVIF features and can create
 * #GstRTSPOnvifMedia in addition to normal #GstRTSPMedia.
 *
 * Special ONVIF features that are currently supported is a backchannel for
 * the client to send back media to the server in a normal PLAY media, see
 * gst_rtsp_onvif_media_factory_set_backchannel_launch() and
 * gst_rtsp_onvif_media_factory_set_backchannel_bandwidth().
 *
 * Since: 1.14
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-onvif-media-factory.h"
#include "rtsp-onvif-media.h"
#include "rtsp-onvif-server.h"

struct GstRTSPOnvifMediaFactoryPrivate
{
  GMutex lock;
  gchar *backchannel_launch;
  guint backchannel_bandwidth;
  gboolean has_replay_support;
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPOnvifMediaFactory,
    gst_rtsp_onvif_media_factory, GST_TYPE_RTSP_MEDIA_FACTORY);

/**
 * gst_rtsp_onvif_media_factory_requires_backchannel:
 * @factory: a #GstRTSPMediaFactory
 *
 * Checks whether the client request requires backchannel.
 *
 * Returns: %TRUE if the client request requires backchannel.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_factory_requires_backchannel (GstRTSPMediaFactory *
    factory, GstRTSPContext * ctx)
{
  GstRTSPMessage *msg = ctx->request;
  GstRTSPResult res;
  gint i;
  gchar *reqs = NULL;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), FALSE);

  i = 0;
  do {
    res = gst_rtsp_message_get_header (msg, GST_RTSP_HDR_REQUIRE, &reqs, i++);

    if (res == GST_RTSP_ENOTIMPL)
      break;

    if (strcmp (reqs, GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT) == 0)
      return TRUE;
  } while (TRUE);

  return FALSE;
}

static gchar *
gst_rtsp_onvif_media_factory_gen_key (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Only medias where no backchannel was requested can be shared */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx))
    return NULL;

  return
      GST_RTSP_MEDIA_FACTORY_CLASS
      (gst_rtsp_onvif_media_factory_parent_class)->gen_key (factory, url);
}

static GstRTSPMedia *
gst_rtsp_onvif_media_factory_construct (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstRTSPMedia *media;
  GstElement *element, *pipeline;
  GstRTSPMediaFactoryClass *klass;
  GType media_gtype;
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Mostly a copy of the default implementation but with backchannel support below,
   * unfortunately we can't re-use the default one because of how the virtual
   * method is define */

  /* Everything but play is unsupported */
  if (gst_rtsp_media_factory_get_transport_mode (factory) !=
      GST_RTSP_TRANSPORT_MODE_PLAY)
    return NULL;

  /* we only support onvif media here: otherwise a plain GstRTSPMediaFactory
   * could've been used as well */
  media_gtype = gst_rtsp_media_factory_get_media_gtype (factory);
  if (!g_type_is_a (media_gtype, GST_TYPE_RTSP_ONVIF_MEDIA))
    return NULL;

  klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory);

  if (!klass->create_pipeline)
    goto no_create;

  element = gst_rtsp_media_factory_create_element (factory, url);
  if (element == NULL)
    goto no_element;

  /* create a new empty media */
  media =
      g_object_new (media_gtype, "element", element,
      "transport-mode", GST_RTSP_TRANSPORT_MODE_PLAY, NULL);

  /* we need to call this prior to collecting streams */
  gst_rtsp_media_set_ensure_keyunit_on_start (media,
      gst_rtsp_media_factory_get_ensure_keyunit_on_start (factory));

  /* this adds the non-backchannel streams */
  gst_rtsp_media_collect_streams (media);

  GstRTSPOnvifMediaFactory *onvif_factory =
      GST_RTSP_ONVIF_MEDIA_FACTORY (factory);
  GstRTSPOnvifMediaFactoryClass *onvif_klass =
      GST_RTSP_ONVIF_MEDIA_FACTORY_GET_CLASS (factory);
  if (onvif_klass->create_backchannel_stream != NULL) {
    /* this creates and adds the backchannel stream */
    if (!onvif_klass->create_backchannel_stream (onvif_factory,
            GST_RTSP_ONVIF_MEDIA (media), ctx)) {
      g_object_unref (media);
      return NULL;
    }
  }

  pipeline = klass->create_pipeline (factory, media);
  if (pipeline == NULL)
    goto no_pipeline;

  gst_rtsp_onvif_media_set_backchannel_bandwidth (GST_RTSP_ONVIF_MEDIA (media),
      GST_RTSP_ONVIF_MEDIA_FACTORY (factory)->priv->backchannel_bandwidth);

  return media;

  /* ERRORS */
no_create:
  {
    g_critical ("no create_pipeline function");
    return NULL;
  }
no_element:
  {
    g_critical ("could not create element");
    return NULL;
  }
no_pipeline:
  {
    g_critical ("can't create pipeline");
    g_object_unref (media);
    return NULL;
  }
}

static GstElement *
gst_rtsp_onvif_media_factory_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstElement *element;
  GError *error = NULL;
  gchar *launch;
  GstRTSPContext *ctx = gst_rtsp_context_get_current ();

  /* Mostly a copy of the default implementation but with backchannel support below,
   * unfortunately we can't re-use the default one because of how the virtual
   * method is define */

  launch = gst_rtsp_media_factory_get_launch (factory);

  /* we need a parse syntax */
  if (launch == NULL)
    goto no_launch;

  /* parse the user provided launch line */
  element =
      gst_parse_launch_full (launch, NULL, GST_PARSE_FLAG_PLACE_IN_BIN, &error);
  if (element == NULL)
    goto parse_error;

  g_free (launch);

  if (error != NULL) {
    /* a recoverable error was encountered */
    GST_WARNING ("recoverable parsing error: %s", error->message);
    g_error_free (error);
  }

  /* add backchannel pipeline part, if requested */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx)) {
    GstRTSPOnvifMediaFactory *onvif_factory =
        GST_RTSP_ONVIF_MEDIA_FACTORY (factory);
    GstElement *backchannel_bin;
    GstElement *backchannel_depay;
    GstPad *depay_pad, *depay_ghostpad;

    launch =
        gst_rtsp_onvif_media_factory_get_backchannel_launch (onvif_factory);
    if (launch == NULL)
      goto no_launch_backchannel;

    backchannel_bin =
        gst_parse_bin_from_description_full (launch, FALSE, NULL,
        GST_PARSE_FLAG_PLACE_IN_BIN, &error);
    if (backchannel_bin == NULL)
      goto parse_error_backchannel;

    g_free (launch);

    if (error != NULL) {
      /* a recoverable error was encountered */
      GST_WARNING ("recoverable parsing error: %s", error->message);
      g_error_free (error);
    }

    gst_object_set_name (GST_OBJECT (backchannel_bin), "onvif-backchannel");

    backchannel_depay =
        gst_bin_get_by_name (GST_BIN (backchannel_bin), "depay_backchannel");
    if (!backchannel_depay) {
      gst_object_unref (backchannel_bin);
      goto wrongly_formatted_backchannel_bin;
    }

    depay_pad = gst_element_get_static_pad (backchannel_depay, "sink");
    if (!depay_pad) {
      gst_object_unref (backchannel_depay);
      gst_object_unref (backchannel_bin);
      goto wrongly_formatted_backchannel_bin;
    }

    depay_ghostpad = gst_ghost_pad_new ("sink", depay_pad);
    gst_object_unref (depay_pad);

    gst_element_add_pad (backchannel_bin, depay_ghostpad);      // Takes ownership of depay_ghostpad

    gst_bin_add (GST_BIN (element), backchannel_bin);
  }

  return element;

  /* ERRORS */
no_launch:
  {
    g_critical ("no launch line specified");
    g_free (launch);
    return NULL;
  }
parse_error:
  {
    g_critical ("could not parse launch syntax (%s): %s", launch,
        (error ? error->message : "unknown reason"));
    if (error)
      g_error_free (error);
    g_free (launch);
    return NULL;
  }
no_launch_backchannel:
  {
    g_critical ("no backchannel launch line specified");
    gst_object_unref (element);
    return NULL;
  }
parse_error_backchannel:
  {
    g_critical ("could not parse backchannel launch syntax (%s): %s", launch,
        (error ? error->message : "unknown reason"));
    if (error)
      g_error_free (error);
    g_free (launch);
    gst_object_unref (element);
    return NULL;
  }

wrongly_formatted_backchannel_bin:
  {
    g_critical ("invalidly formatted backchannel bin");

    gst_object_unref (element);
    return NULL;
  }
}

static gboolean
    gst_rtsp_onvif_media_factory_has_backchannel_support_default
    (GstRTSPOnvifMediaFactory * factory)
{
  /* No locking here, we just check if it's non-NULL */
  return factory->priv->backchannel_launch != NULL;
}


static gboolean
create_backchannel_stream_default (GstRTSPOnvifMediaFactory * factory,
    GstRTSPOnvifMedia * media, GstRTSPContext * ctx)
{
  /* this adds the backchannel stream */
  gboolean got_backchannel_stream =
      gst_rtsp_onvif_media_collect_backchannel (media);

  /* FIXME: This should not happen! We checked for that before */
  if (gst_rtsp_onvif_media_factory_requires_backchannel (GST_RTSP_MEDIA_FACTORY
          (factory), ctx) && !got_backchannel_stream) {
    GST_WARNING_OBJECT (factory,
        "Failed to collect backchannel, but factory requires backchannel");
    return FALSE;
  }

  return TRUE;
}

static void
gst_rtsp_onvif_media_factory_finalize (GObject * object)
{
  GstRTSPOnvifMediaFactory *factory = GST_RTSP_ONVIF_MEDIA_FACTORY (object);

  g_free (factory->priv->backchannel_launch);
  factory->priv->backchannel_launch = NULL;

  g_mutex_clear (&factory->priv->lock);

  G_OBJECT_CLASS (gst_rtsp_onvif_media_factory_parent_class)->finalize (object);
}

static void
gst_rtsp_onvif_media_factory_class_init (GstRTSPOnvifMediaFactoryClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstRTSPMediaFactoryClass *factory_klass = (GstRTSPMediaFactoryClass *) klass;

  gobject_class->finalize = gst_rtsp_onvif_media_factory_finalize;

  factory_klass->gen_key = gst_rtsp_onvif_media_factory_gen_key;
  factory_klass->construct = gst_rtsp_onvif_media_factory_construct;
  factory_klass->create_element = gst_rtsp_onvif_media_factory_create_element;

  klass->has_backchannel_support =
      gst_rtsp_onvif_media_factory_has_backchannel_support_default;
  klass->create_backchannel_stream = create_backchannel_stream_default;
}

static void
gst_rtsp_onvif_media_factory_init (GstRTSPOnvifMediaFactory * factory)
{
  factory->priv = gst_rtsp_onvif_media_factory_get_instance_private (factory);
  g_mutex_init (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_set_backchannel_launch:
 * @factory: a #GstRTSPMediaFactory
 * @launch: (nullable): the launch description
 *
 * The gst_parse_launch() line to use for constructing the ONVIF backchannel
 * pipeline in the default prepare vmethod if requested by the client.
 *
 * The pipeline description should return a GstBin as the toplevel element
 * which can be accomplished by enclosing the description with brackets '('
 * ')'.
 *
 * The description should return a pipeline with a single depayloader named
 * depay_backchannel. A caps query on the depayloader's sinkpad should return
 * all possible, complete RTP caps that are going to be supported. At least
 * the payload type, clock-rate and encoding-name need to be specified.
 *
 * Note: The pipeline part passed here must end in sinks that are not waiting
 * until pre-rolling before reaching the PAUSED state, i.e. setting
 * async=false on #GstBaseSink. Otherwise the whole media will not be able to
 * prepare.
 *
 * Since: 1.14
 */
void
gst_rtsp_onvif_media_factory_set_backchannel_launch (GstRTSPOnvifMediaFactory *
    factory, const gchar * launch)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory));

  g_mutex_lock (&factory->priv->lock);
  g_free (factory->priv->backchannel_launch);
  factory->priv->backchannel_launch = g_strdup (launch);
  g_mutex_unlock (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_get_backchannel_launch:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the gst_parse_launch() pipeline description that will be used in the
 * default prepare vmethod for generating the ONVIF backchannel pipeline.
 *
 * Returns: (transfer full) (nullable): the configured backchannel launch description. g_free() after
 * usage.
 *
 * Since: 1.14
 */
gchar *
gst_rtsp_onvif_media_factory_get_backchannel_launch (GstRTSPOnvifMediaFactory *
    factory)
{
  gchar *launch;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), NULL);

  g_mutex_lock (&factory->priv->lock);
  launch = g_strdup (factory->priv->backchannel_launch);
  g_mutex_unlock (&factory->priv->lock);

  return launch;
}

/**
 * gst_rtsp_onvif_media_factory_has_backchannel_support:
 * @factory: a #GstRTSPMediaFactory
 *
 * Returns %TRUE if an ONVIF backchannel is supported by the media factory.
 *
 * Returns: %TRUE if an ONVIF backchannel is supported by the media factory.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_factory_has_backchannel_support (GstRTSPOnvifMediaFactory *
    factory)
{
  GstRTSPOnvifMediaFactoryClass *klass;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), FALSE);

  klass = GST_RTSP_ONVIF_MEDIA_FACTORY_GET_CLASS (factory);

  if (klass->has_backchannel_support)
    return klass->has_backchannel_support (factory);

  return FALSE;
}

/**
 * gst_rtsp_onvif_media_factory_has_replay_support:
 *
 * Returns: %TRUE if ONVIF replay is supported by the media factory.
 *
 * Since: 1.18
 */
gboolean
gst_rtsp_onvif_media_factory_has_replay_support (GstRTSPOnvifMediaFactory *
    factory)
{
  gboolean has_replay_support;

  g_mutex_lock (&factory->priv->lock);
  has_replay_support = factory->priv->has_replay_support;
  g_mutex_unlock (&factory->priv->lock);

  return has_replay_support;
}

/**
 * gst_rtsp_onvif_media_factory_set_replay_support:
 *
 * Set to %TRUE if ONVIF replay is supported by the media factory.
 *
 * Since: 1.18
 */
void
gst_rtsp_onvif_media_factory_set_replay_support (GstRTSPOnvifMediaFactory *
    factory, gboolean has_replay_support)
{
  g_mutex_lock (&factory->priv->lock);
  factory->priv->has_replay_support = has_replay_support;
  g_mutex_unlock (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_set_backchannel_bandwidth:
 * @factory: a #GstRTSPMediaFactory
 * @bandwidth: the bandwidth in bits per second
 *
 * Set the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Since: 1.14
 */
void
gst_rtsp_onvif_media_factory_set_backchannel_bandwidth (GstRTSPOnvifMediaFactory
    * factory, guint bandwidth)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory));

  g_mutex_lock (&factory->priv->lock);
  factory->priv->backchannel_bandwidth = bandwidth;
  g_mutex_unlock (&factory->priv->lock);
}

/**
 * gst_rtsp_onvif_media_factory_get_backchannel_bandwidth:
 * @factory: a #GstRTSPMediaFactory
 *
 * Get the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Returns: the configured/supported backchannel bandwidth.
 *
 * Since: 1.14
 */
guint
gst_rtsp_onvif_media_factory_get_backchannel_bandwidth (GstRTSPOnvifMediaFactory
    * factory)
{
  guint bandwidth;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA_FACTORY (factory), 0);

  g_mutex_lock (&factory->priv->lock);
  bandwidth = factory->priv->backchannel_bandwidth;
  g_mutex_unlock (&factory->priv->lock);

  return bandwidth;
}

/**
 * gst_rtsp_onvif_media_factory_new:
 *
 * Create a new #GstRTSPOnvifMediaFactory
 *
 * Returns: A new #GstRTSPOnvifMediaFactory
 *
 * Since: 1.14
 */
GstRTSPMediaFactory *
gst_rtsp_onvif_media_factory_new (void)
{
  return g_object_new (GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY, NULL);
}
07070100000049000081A400000000000000000000000169D554BF00001328000000000000000000000000000000000000004200000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-media-factory.h /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_ONVIF_MEDIA_FACTORY_H__
#define __GST_RTSP_ONVIF_MEDIA_FACTORY_H__

#include <gst/gst.h>
#include "rtsp-media-factory.h"
#include "rtsp-onvif-media.h"

#define GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY              (gst_rtsp_onvif_media_factory_get_type ())
#define GST_IS_RTSP_ONVIF_MEDIA_FACTORY(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY))
#define GST_IS_RTSP_ONVIF_MEDIA_FACTORY_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY))
#define GST_RTSP_ONVIF_MEDIA_FACTORY_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY, GstRTSPOnvifMediaFactoryClass))
#define GST_RTSP_ONVIF_MEDIA_FACTORY(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY, GstRTSPOnvifMediaFactory))
#define GST_RTSP_ONVIF_MEDIA_FACTORY_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_ONVIF_MEDIA_FACTORY, GstRTSPOnvifMediaFactoryClass))
#define GST_RTSP_ONVIF_MEDIA_FACTORY_CAST(obj)         ((GstRTSPOnvifMediaFactory*)(obj))
#define GST_RTSP_ONVIF_MEDIA_FACTORY_CLASS_CAST(klass) ((GstRTSPOnvifMediaFactoryClass*)(klass))

typedef struct GstRTSPOnvifMediaFactoryClass GstRTSPOnvifMediaFactoryClass;
typedef struct GstRTSPOnvifMediaFactory GstRTSPOnvifMediaFactory;
typedef struct GstRTSPOnvifMediaFactoryPrivate GstRTSPOnvifMediaFactoryPrivate;

/**
 * GstRTSPOnvifMediaFactory:
 *
 * Since: 1.14
 */
struct GstRTSPOnvifMediaFactoryClass
{
  GstRTSPMediaFactoryClass parent;
  gboolean (*has_backchannel_support) (GstRTSPOnvifMediaFactory * factory);

  /**
   * GstRTSPOnvifMediaFactoryClass::create_backchannel_stream:
   * @factory: a #GstRTSPOnvifMediaFactory
   * @media: a #GstRTSPOnvifMedia
   * @ctx: a #GstRTSPContext
   *
   * Called by the factory from #GstRTSPMediaFactoryClass::construct(). The default implementation
   * creates the * #GstRTSPStream for the backchannel receiver by calling
   * gst_rtsp_onvif_media_collect_backchannel (media). Implementations
   * that want to create the backchannel later should return TRUE here
   * and call gst_rtsp_onvif_media_collect_backchannel() later, but must
   * do so before the media finishes preparing.
   *
   * Returns: TRUE on success. FALSE on a fatal error.
   *
   * Since: 1.26
   */
  gboolean (*create_backchannel_stream) (GstRTSPOnvifMediaFactory * factory, GstRTSPOnvifMedia *media, GstRTSPContext *ctx);

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE-1];
};

struct GstRTSPOnvifMediaFactory
{
  GstRTSPMediaFactory parent;
  GstRTSPOnvifMediaFactoryPrivate *priv;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType gst_rtsp_onvif_media_factory_get_type (void);

GST_RTSP_SERVER_API
GstRTSPMediaFactory *gst_rtsp_onvif_media_factory_new (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void gst_rtsp_onvif_media_factory_set_backchannel_launch (GstRTSPOnvifMediaFactory *
    factory, const gchar * launch);
GST_RTSP_SERVER_API
gchar * gst_rtsp_onvif_media_factory_get_backchannel_launch (GstRTSPOnvifMediaFactory * factory) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean gst_rtsp_onvif_media_factory_has_backchannel_support (GstRTSPOnvifMediaFactory * factory);

GST_RTSP_SERVER_API
gboolean gst_rtsp_onvif_media_factory_has_replay_support (GstRTSPOnvifMediaFactory * factory);

GST_RTSP_SERVER_API
void gst_rtsp_onvif_media_factory_set_replay_support (GstRTSPOnvifMediaFactory * factory, gboolean has_replay_support);

GST_RTSP_SERVER_API
void gst_rtsp_onvif_media_factory_set_backchannel_bandwidth (GstRTSPOnvifMediaFactory * factory, guint bandwidth);
GST_RTSP_SERVER_API
guint gst_rtsp_onvif_media_factory_get_backchannel_bandwidth (GstRTSPOnvifMediaFactory * factory);

GST_RTSP_SERVER_API
gboolean gst_rtsp_onvif_media_factory_requires_backchannel (GstRTSPMediaFactory * factory, GstRTSPContext * ctx);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPOnvifMediaFactory, gst_object_unref)
#endif

#endif /* __GST_RTSP_ONVIF_MEDIA_FACTORY_H__ */
0707010000004A000081A400000000000000000000000169D554BF00002BED000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-media.c /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:rtsp-onvif-media
 * @short_description: The ONVIF media pipeline
 * @see_also: #GstRTSPMedia, #GstRTSPOnvifMediaFactory, #GstRTSPStream, #GstRTSPSession,
 *     #GstRTSPSessionMedia
 *
 * a #GstRTSPOnvifMedia contains the complete GStreamer pipeline to manage the
 * streaming to the clients. The actual data transfer is done by the
 * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
 *
 * On top of #GstRTSPMedia this subclass adds special ONVIF features.
 * Special ONVIF features that are currently supported is a backchannel for
 * the client to send back media to the server in a normal PLAY media. To
 * handle the ONVIF backchannel, a #GstRTSPOnvifMediaFactory and
 * #GstRTSPOnvifServer has to be used.
 *
 * Since: 1.14
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rtsp-onvif-media.h"
#include "rtsp-latency-bin.h"
#include "rtsp-server-internal.h"

GST_DEBUG_CATEGORY_STATIC (rtsp_onvif_media_debug);
#define GST_CAT_DEFAULT rtsp_onvif_media_debug

struct GstRTSPOnvifMediaPrivate
{
  GMutex lock;
  guint backchannel_bandwidth;
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPOnvifMedia, gst_rtsp_onvif_media,
    GST_TYPE_RTSP_MEDIA);

static gboolean
gst_rtsp_onvif_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
    GstSDPInfo * info)
{
  guint i, n_streams;
  gchar *rangestr;
  gboolean res;

  /* Mostly a copy of gst_rtsp_sdp_from_media() which handles the backchannel
   * stream separately and adds sendonly/recvonly attributes to each media
   */

  n_streams = gst_rtsp_media_n_streams (media);

  rangestr = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  if (rangestr == NULL)
    goto not_prepared;

  gst_sdp_message_add_attribute (sdp, "range", rangestr);
  g_free (rangestr);

  res = TRUE;
  for (i = 0; res && (i < n_streams); i++) {
    GstRTSPStream *stream;
    GstCaps *caps = NULL;
    GstRTSPProfile profiles;
    guint mask;
    GstPad *sinkpad = NULL;
    guint n_caps, j;

    /* Mostly a copy of gst_rtsp_sdp_from_stream() which handles the
     * backchannel stream separately */

    stream = gst_rtsp_media_get_stream (media, i);

    if ((sinkpad = gst_rtsp_stream_get_sinkpad (stream))) {
      caps = gst_pad_query_caps (sinkpad, NULL);
    } else {
      caps = gst_rtsp_stream_get_caps (stream);
    }

    if (caps == NULL) {
      GST_ERROR ("stream %p has no caps", stream);
      res = FALSE;
      if (sinkpad)
        gst_object_unref (sinkpad);
      break;
    } else if (!sinkpad && !gst_caps_is_fixed (caps)) {
      GST_ERROR ("stream %p has unfixed caps", stream);
      res = FALSE;
      gst_caps_unref (caps);
      break;
    }

    n_caps = gst_caps_get_size (caps);
    if (n_caps == 0) {
      GST_ERROR ("media caps for stream %p are %" GST_PTR_FORMAT, stream, caps);
      res = FALSE;
      gst_caps_unref (caps);
      break;
    }

    for (j = 0; res && j < n_caps; j++) {
      GstStructure *s = gst_caps_get_structure (caps, j);
      GstCaps *media_caps = gst_caps_new_full (gst_structure_copy (s), NULL);

      if (!gst_caps_is_fixed (media_caps)) {
        GST_ERROR ("media caps for stream %p are not all fixed", stream);
        res = FALSE;
        gst_caps_unref (media_caps);
        break;
      }

      /* make a new media for each profile */
      profiles = gst_rtsp_stream_get_profiles (stream);
      mask = 1;
      res = TRUE;
      while (res && (profiles >= mask)) {
        GstRTSPProfile prof = profiles & mask;

        if (prof) {
          res = gst_rtsp_sdp_make_media (sdp, info, stream, media_caps, prof);
          if (res) {
            GstSDPMedia *smedia =
                &g_array_index (sdp->medias, GstSDPMedia, sdp->medias->len - 1);
            gchar *x_onvif_track, *media_str;

            media_str =
                g_ascii_strup (gst_structure_get_string (s, "media"), -1);
            x_onvif_track =
                g_strdup_printf ("%s%03d", media_str, sdp->medias->len - 1);
            gst_sdp_media_add_attribute (smedia, "x-onvif-track",
                x_onvif_track);
            g_free (x_onvif_track);
            g_free (media_str);

            if (sinkpad) {
              GstRTSPOnvifMedia *onvif_media = GST_RTSP_ONVIF_MEDIA (media);

              gst_sdp_media_add_attribute (smedia, "sendonly", "");
              if (onvif_media->priv->backchannel_bandwidth > 0)
                gst_sdp_media_add_bandwidth (smedia, GST_SDP_BWTYPE_AS,
                    onvif_media->priv->backchannel_bandwidth);
            } else {
              gst_sdp_media_add_attribute (smedia, "recvonly", "");
            }
          }
        }

        mask <<= 1;
      }

      if (sinkpad) {
        GstStructure *s = gst_caps_get_structure (media_caps, 0);
        gint pt = -1;

        if (!gst_structure_get_int (s, "payload", &pt) || pt < 0) {
          GST_ERROR ("stream %p has no payload type", stream);
          res = FALSE;
          gst_caps_unref (media_caps);
          gst_object_unref (sinkpad);
          break;
        }

        gst_rtsp_stream_set_pt_map (stream, pt, media_caps);
      }

      gst_caps_unref (media_caps);
    }

    gst_caps_unref (caps);
    if (sinkpad)
      gst_object_unref (sinkpad);
  }

  {
    GstNetTimeProvider *provider;

    if ((provider =
            gst_rtsp_media_get_time_provider (media, info->server_ip, 0))) {
      GstClock *clock;
      gchar *address, *str;
      gint port;

      g_object_get (provider, "clock", &clock, "address", &address, "port",
          &port, NULL);

      str = g_strdup_printf ("GstNetTimeProvider %s %s:%d %" G_GUINT64_FORMAT,
          g_type_name (G_TYPE_FROM_INSTANCE (clock)), address, port,
          gst_clock_get_time (clock));

      gst_sdp_message_add_attribute (sdp, "x-gst-clock", str);
      g_free (str);
      gst_object_unref (clock);
      g_free (address);
      gst_object_unref (provider);
    }
  }

  return res;

  /* ERRORS */
not_prepared:
  {
    GST_ERROR ("media %p is not prepared", media);
    return FALSE;
  }
}

static void
gst_rtsp_onvif_media_finalize (GObject * object)
{
  GstRTSPOnvifMedia *media = GST_RTSP_ONVIF_MEDIA (object);

  g_mutex_clear (&media->priv->lock);

  G_OBJECT_CLASS (gst_rtsp_onvif_media_parent_class)->finalize (object);
}

static void
gst_rtsp_onvif_media_class_init (GstRTSPOnvifMediaClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstRTSPMediaClass *media_class = (GstRTSPMediaClass *) klass;

  gobject_class->finalize = gst_rtsp_onvif_media_finalize;

  media_class->setup_sdp = gst_rtsp_onvif_media_setup_sdp;

  GST_DEBUG_CATEGORY_INIT (rtsp_onvif_media_debug, "rtsponvifmedia", 0,
      "GstRTSPOnvifMedia");
}

static void
gst_rtsp_onvif_media_init (GstRTSPOnvifMedia * media)
{
  media->priv = gst_rtsp_onvif_media_get_instance_private (media);
  g_mutex_init (&media->priv->lock);
}

/**
 * gst_rtsp_onvif_media_collect_backchannel:
 * @media: a #GstRTSPOnvifMedia
 *
 * Find the ONVIF backchannel depayloader element. It should be named
 * 'depay_backchannel', be placed in a bin called 'onvif-backchannel'
 * and return all supported RTP caps on a caps query. Complete RTP caps with
 * at least the payload type, clock-rate and encoding-name are required.
 *
 * A new #GstRTSPStream is created for the backchannel if found.
 *
 * Returns: %TRUE if a backchannel stream could be found and created
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_onvif_media_collect_backchannel (GstRTSPOnvifMedia * media)
{
  GstElement *element, *backchannel_bin = NULL;
  GstElement *latency_bin;
  GstPad *pad = NULL;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media), FALSE);

  element = gst_rtsp_media_get_element (GST_RTSP_MEDIA (media));
  if (!element)
    return ret;

  GST_LOG_OBJECT (media, "Looking for backchannel bin onvif-backchannel");

  backchannel_bin =
      gst_bin_get_by_name (GST_BIN (element), "onvif-backchannel");
  if (!backchannel_bin) {
    GST_ERROR_OBJECT (media,
        "onvif-backchannel bin not found in media pipeline");
    goto out;
  }

  /* We don't want the backchannel element, which is a receiver, to affect
   * latency on the complete pipeline. That's why we remove it from the
   * pipeline and add it to a @GstRTSPLatencyBin which will prevent it from
   * messing up pipelines latency. The extra reference is needed so that it
   * is not freed in case the pipeline holds the the only ref to it.
   *
   * TODO: a more generic solution should be implemented in
   * gst_rtsp_media_collect_streams() where all receivers are encapsulated
   * in a @GstRTSPLatencyBin in cases when there are senders too. */
  gst_object_ref (backchannel_bin);
  gst_bin_remove (GST_BIN (element), backchannel_bin);

  latency_bin = gst_rtsp_latency_bin_new (backchannel_bin);
  g_assert (latency_bin);

  gst_bin_add (GST_BIN (element), latency_bin);

  pad = gst_element_get_static_pad (latency_bin, "sink");
  if (!pad)
    goto out;

  GST_LOG_OBJECT (media, "Creating backchannel stream");
  gst_rtsp_media_create_and_join_stream (GST_RTSP_MEDIA (media), latency_bin,
      pad);
  ret = TRUE;

out:
  if (pad)
    gst_object_unref (pad);
  if (backchannel_bin)
    gst_object_unref (backchannel_bin);
  gst_object_unref (element);

  return ret;
}

/**
 * gst_rtsp_onvif_media_set_backchannel_bandwidth:
 * @media: a #GstRTSPMedia
 * @bandwidth: the bandwidth in bits per second
 *
 * Set the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Since: 1.14
 */
void
gst_rtsp_onvif_media_set_backchannel_bandwidth (GstRTSPOnvifMedia * media,
    guint bandwidth)
{
  g_return_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media));

  g_mutex_lock (&media->priv->lock);
  media->priv->backchannel_bandwidth = bandwidth;
  g_mutex_unlock (&media->priv->lock);
}

/**
 * gst_rtsp_onvif_media_get_backchannel_bandwidth:
 * @media: a #GstRTSPMedia
 *
 * Get the configured/supported bandwidth of the ONVIF backchannel pipeline in
 * bits per second.
 *
 * Returns: the configured/supported backchannel bandwidth.
 *
 * Since: 1.14
 */
guint
gst_rtsp_onvif_media_get_backchannel_bandwidth (GstRTSPOnvifMedia * media)
{
  guint bandwidth;

  g_return_val_if_fail (GST_IS_RTSP_ONVIF_MEDIA (media), 0);

  g_mutex_lock (&media->priv->lock);
  bandwidth = media->priv->backchannel_bandwidth;
  g_mutex_unlock (&media->priv->lock);

  return bandwidth;
}
   0707010000004B000081A400000000000000000000000169D554BF00000AAD000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-media.h /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_ONVIF_MEDIA_H__
#define __GST_RTSP_ONVIF_MEDIA_H__

#include <gst/gst.h>
#include "rtsp-media.h"

#define GST_TYPE_RTSP_ONVIF_MEDIA              (gst_rtsp_onvif_media_get_type ())
#define GST_IS_RTSP_ONVIF_MEDIA(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_ONVIF_MEDIA))
#define GST_IS_RTSP_ONVIF_MEDIA_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_ONVIF_MEDIA))
#define GST_RTSP_ONVIF_MEDIA_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_ONVIF_MEDIA, GstRTSPOnvifMediaClass))
#define GST_RTSP_ONVIF_MEDIA(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_ONVIF_MEDIA, GstRTSPOnvifMedia))
#define GST_RTSP_ONVIF_MEDIA_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_ONVIF_MEDIA, GstRTSPOnvifMediaClass))
#define GST_RTSP_ONVIF_MEDIA_CAST(obj)         ((GstRTSPOnvifMedia*)(obj))
#define GST_RTSP_ONVIF_MEDIA_CLASS_CAST(klass) ((GstRTSPOnvifMediaClass*)(klass))

typedef struct GstRTSPOnvifMediaClass GstRTSPOnvifMediaClass;
typedef struct GstRTSPOnvifMedia GstRTSPOnvifMedia;
typedef struct GstRTSPOnvifMediaPrivate GstRTSPOnvifMediaPrivate;

/**
 * GstRTSPOnvifMedia:
 *
 * Since: 1.14
 */
struct GstRTSPOnvifMediaClass
{
  GstRTSPMediaClass parent;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE];
};

struct GstRTSPOnvifMedia
{
  GstRTSPMedia parent;
  GstRTSPOnvifMediaPrivate *priv;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType gst_rtsp_onvif_media_get_type (void);
GST_RTSP_SERVER_API
gboolean gst_rtsp_onvif_media_collect_backchannel (GstRTSPOnvifMedia * media);

GST_RTSP_SERVER_API
void gst_rtsp_onvif_media_set_backchannel_bandwidth (GstRTSPOnvifMedia * media, guint bandwidth);
GST_RTSP_SERVER_API
guint gst_rtsp_onvif_media_get_backchannel_bandwidth (GstRTSPOnvifMedia * media);

#endif /* __GST_RTSP_ONVIF_MEDIA_H__ */
   0707010000004C000081A400000000000000000000000169D554BF00000CCF000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-server.c    /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-onvif-server
 * @short_description: The main server object
 * @see_also: #GstRTSPOnvifMediaFactory, #GstRTSPClient
 *
 * The server object is the object listening for connections on a port and
 * creating #GstRTSPOnvifClient objects to handle those connections.
 *
 * The only different to #GstRTSPServer is that #GstRTSPOnvifServer creates
 * #GstRTSPOnvifClient that have special handling for ONVIF specific features,
 * like a backchannel that allows clients to send back media to the server.
 *
 * Since: 1.14
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rtsp-context.h"
#include "rtsp-onvif-server.h"
#include "rtsp-onvif-client.h"

G_DEFINE_TYPE (GstRTSPOnvifServer, gst_rtsp_onvif_server, GST_TYPE_RTSP_SERVER);

static GstRTSPClient *
gst_rtsp_onvif_server_create_client (GstRTSPServer * server)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPAuth *auth;
  GstRTSPThreadPool *thread_pool;

  /* a new client connected, create a session to handle the client. */
  client = g_object_new (GST_TYPE_RTSP_ONVIF_CLIENT, NULL);

  /* set the session pool that this client should use */
  session_pool = gst_rtsp_server_get_session_pool (server);
  gst_rtsp_client_set_session_pool (client, session_pool);
  g_object_unref (session_pool);
  /* set the mount points that this client should use */
  mount_points = gst_rtsp_server_get_mount_points (server);
  gst_rtsp_client_set_mount_points (client, mount_points);
  g_object_unref (mount_points);
  /* set authentication manager */
  auth = gst_rtsp_server_get_auth (server);
  gst_rtsp_client_set_auth (client, auth);
  if (auth)
    g_object_unref (auth);
  /* set threadpool */
  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_client_set_thread_pool (client, thread_pool);
  g_object_unref (thread_pool);

  return client;
}

/**
 * gst_rtsp_onvif_server_new:
 *
 * Create a new #GstRTSPOnvifServer instance.
 *
 * Returns: (transfer full): a new #GstRTSPOnvifServer
 */
GstRTSPServer *
gst_rtsp_onvif_server_new (void)
{
  return g_object_new (GST_TYPE_RTSP_ONVIF_SERVER, NULL);
}

static void
gst_rtsp_onvif_server_class_init (GstRTSPOnvifServerClass * klass)
{
  GstRTSPServerClass *server_klass = (GstRTSPServerClass *) klass;

  server_klass->create_client = gst_rtsp_onvif_server_create_client;
}

static void
gst_rtsp_onvif_server_init (GstRTSPOnvifServer * server)
{
}
 0707010000004D000081A400000000000000000000000169D554BF00000A7F000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-onvif-server.h    /* GStreamer
 * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_ONVIF_SERVER_H__
#define __GST_RTSP_ONVIF_SERVER_H__

#include <gst/gst.h>
#include "rtsp-server-object.h"

#define GST_TYPE_RTSP_ONVIF_SERVER              (gst_rtsp_onvif_server_get_type ())
#define GST_IS_RTSP_ONVIF_SERVER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_ONVIF_SERVER))
#define GST_IS_RTSP_ONVIF_SERVER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_ONVIF_SERVER))
#define GST_RTSP_ONVIF_SERVER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_ONVIF_SERVER, GstRTSPOnvifServerClass))
#define GST_RTSP_ONVIF_SERVER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_ONVIF_SERVER, GstRTSPOnvifServer))
#define GST_RTSP_ONVIF_SERVER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_ONVIF_SERVER, GstRTSPOnvifServerClass))
#define GST_RTSP_ONVIF_SERVER_CAST(obj)         ((GstRTSPOnvifServer*)(obj))
#define GST_RTSP_ONVIF_SERVER_CLASS_CAST(klass) ((GstRTSPOnvifServerClass*)(klass))

typedef struct GstRTSPOnvifServerClass GstRTSPOnvifServerClass;
typedef struct GstRTSPOnvifServer GstRTSPOnvifServer;

/**
 * GstRTSPOnvifServer:
 *
 * Since: 1.14
 */
struct GstRTSPOnvifServerClass
{
  GstRTSPServerClass parent;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE];
};

struct GstRTSPOnvifServer
{
  GstRTSPServer parent;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType gst_rtsp_onvif_server_get_type (void);
GST_RTSP_SERVER_API
GstRTSPServer *gst_rtsp_onvif_server_new (void) G_GNUC_WARN_UNUSED_RESULT;

#define GST_RTSP_ONVIF_BACKCHANNEL_REQUIREMENT "www.onvif.org/ver20/backchannel"
#define GST_RTSP_ONVIF_REPLAY_REQUIREMENT "onvif-replay"

#include "rtsp-onvif-client.h"
#include "rtsp-onvif-media-factory.h"
#include "rtsp-onvif-media.h"

#endif /* __GST_RTSP_ONVIF_SERVER_H__ */
 0707010000004E000081A400000000000000000000000169D554BF000008D1000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-params.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-params
 * @short_description: Param get and set implementation
 * @see_also: #GstRTSPClient
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-params.h"

/**
 * gst_rtsp_params_set:
 * @client: a #GstRTSPClient
 * @ctx: (transfer none): a #GstRTSPContext
 *
 * Set parameters (not implemented yet)
 *
 * Returns: a #GstRTSPResult
 */
GstRTSPResult
gst_rtsp_params_set (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPStatusCode code;

  /* FIXME, actually parse the request based on the mime type and try to repond
   * with a list of the parameters */
  code = GST_RTSP_STS_PARAMETER_NOT_UNDERSTOOD;

  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  return GST_RTSP_OK;
}

/**
 * gst_rtsp_params_get:
 * @client: a #GstRTSPClient
 * @ctx: (transfer none): a #GstRTSPContext
 *
 * Get parameters (not implemented yet)
 *
 * Returns: a #GstRTSPResult
 */
GstRTSPResult
gst_rtsp_params_get (GstRTSPClient * client, GstRTSPContext * ctx)
{
  GstRTSPStatusCode code;

  /* FIXME, actually parse the request based on the mime type and try to repond
   * with a list of the parameters */
  code = GST_RTSP_STS_PARAMETER_NOT_UNDERSTOOD;

  gst_rtsp_message_init_response (ctx->response, code,
      gst_rtsp_status_as_text (code), ctx->request);

  return GST_RTSP_OK;
}
   0707010000004F000081A400000000000000000000000169D554BF00000522000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-params.h  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp/gstrtspurl.h>
#include <gst/rtsp/gstrtspmessage.h>

#ifndef __GST_RTSP_PARAMS_H__
#define __GST_RTSP_PARAMS_H__

#include "rtsp-client.h"
#include "rtsp-session.h"

G_BEGIN_DECLS

GST_RTSP_SERVER_API
GstRTSPResult    gst_rtsp_params_set      (GstRTSPClient * client, GstRTSPContext * ctx);

GST_RTSP_SERVER_API
GstRTSPResult    gst_rtsp_params_get      (GstRTSPClient * client, GstRTSPContext * ctx);

G_END_DECLS

#endif /* __GST_RTSP_PARAMS_H__ */
  07070100000050000081A400000000000000000000000169D554BF000029DF000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-permissions.c /* GStreamer
 * Copyright (C) 2013 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-permissions
 * @short_description: Roles and associated permissions
 * @see_also: #GstRTSPToken, #GstRTSPAuth
 *
 * The #GstRTSPPermissions object contains an array of roles and associated
 * permissions. The roles are represented with a string and the permissions with
 * a generic #GstStructure.
 *
 * The permissions are deliberately kept generic. The possible values of the
 * roles and #GstStructure keys and values are only determined by the #GstRTSPAuth
 * object that performs the checks on the permissions and the current
 * #GstRTSPToken.
 *
 * As a convenience function, gst_rtsp_permissions_is_allowed() can be used to
 * check if the permissions contains a role that contains the boolean value
 * %TRUE for the the given key.
 *
 * Last reviewed on 2013-07-15 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-permissions.h"

typedef struct _GstRTSPPermissionsImpl
{
  GstRTSPPermissions permissions;

  /* Roles, array of GstStructure */
  GPtrArray *roles;
} GstRTSPPermissionsImpl;

static void
free_structure (GstStructure * structure)
{
  gst_structure_set_parent_refcount (structure, NULL);
  gst_structure_free (structure);
}

//GST_DEBUG_CATEGORY_STATIC (rtsp_permissions_debug);
//#define GST_CAT_DEFAULT rtsp_permissions_debug

GST_DEFINE_MINI_OBJECT_TYPE (GstRTSPPermissions, gst_rtsp_permissions);

static void gst_rtsp_permissions_init (GstRTSPPermissionsImpl * permissions);

static void
_gst_rtsp_permissions_free (GstRTSPPermissions * permissions)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;

  g_ptr_array_free (impl->roles, TRUE);

  g_free (permissions);
}

static GstRTSPPermissions *
_gst_rtsp_permissions_copy (GstRTSPPermissionsImpl * permissions)
{
  GstRTSPPermissionsImpl *copy;
  guint i;

  copy = (GstRTSPPermissionsImpl *) gst_rtsp_permissions_new ();

  for (i = 0; i < permissions->roles->len; i++) {
    GstStructure *entry = g_ptr_array_index (permissions->roles, i);
    GstStructure *entry_copy = gst_structure_copy (entry);

    gst_structure_set_parent_refcount (entry_copy,
        &copy->permissions.mini_object.refcount);
    g_ptr_array_add (copy->roles, entry_copy);
  }

  return GST_RTSP_PERMISSIONS (copy);
}

static void
gst_rtsp_permissions_init (GstRTSPPermissionsImpl * permissions)
{
  gst_mini_object_init (GST_MINI_OBJECT_CAST (permissions), 0,
      GST_TYPE_RTSP_PERMISSIONS,
      (GstMiniObjectCopyFunction) _gst_rtsp_permissions_copy, NULL,
      (GstMiniObjectFreeFunction) _gst_rtsp_permissions_free);

  permissions->roles =
      g_ptr_array_new_with_free_func ((GDestroyNotify) free_structure);
}

static void
add_role_from_structure (GstRTSPPermissionsImpl * impl,
    GstStructure * structure)
{
  guint i, len;
  const gchar *role = gst_structure_get_name (structure);

  len = impl->roles->len;
  for (i = 0; i < len; i++) {
    GstStructure *entry = g_ptr_array_index (impl->roles, i);

    if (gst_structure_has_name (entry, role)) {
      g_ptr_array_remove_index_fast (impl->roles, i);
      break;
    }
  }

  gst_structure_set_parent_refcount (structure,
      &impl->permissions.mini_object.refcount);
  g_ptr_array_add (impl->roles, structure);
}

/**
 * gst_rtsp_permissions_new:
 *
 * Create a new empty Authorization permissions.
 *
 * Returns: (transfer full): a new empty authorization permissions.
 */
GstRTSPPermissions *
gst_rtsp_permissions_new (void)
{
  GstRTSPPermissionsImpl *permissions;

  permissions = g_new0 (GstRTSPPermissionsImpl, 1);
  gst_rtsp_permissions_init (permissions);

  return GST_RTSP_PERMISSIONS (permissions);
}

/**
 * gst_rtsp_permissions_add_permission_for_role:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 * @permission: the permission
 * @allowed: whether the role has this permission or not
 *
 * Add a new @permission for @role to @permissions with the access in @allowed.
 *
 * Since: 1.14
 */
void
gst_rtsp_permissions_add_permission_for_role (GstRTSPPermissions * permissions,
    const gchar * role, const gchar * permission, gboolean allowed)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;
  guint i, len;

  g_return_if_fail (GST_IS_RTSP_PERMISSIONS (permissions));
  g_return_if_fail (gst_mini_object_is_writable (&permissions->mini_object));
  g_return_if_fail (role != NULL);
  g_return_if_fail (permission != NULL);

  len = impl->roles->len;
  for (i = 0; i < len; i++) {
    GstStructure *entry = g_ptr_array_index (impl->roles, i);

    if (gst_structure_has_name (entry, role)) {
      gst_structure_set (entry, permission, G_TYPE_BOOLEAN, allowed, NULL);
      return;
    }
  }

  gst_rtsp_permissions_add_role (permissions, role,
      permission, G_TYPE_BOOLEAN, allowed, NULL);
}

/**
 * gst_rtsp_permissions_add_role_empty: (rename-to gst_rtsp_permissions_add_role)
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 *
 * Add a new @role to @permissions without any permissions. You can add
 * permissions for the role with gst_rtsp_permissions_add_permission_for_role().
 *
 * Since: 1.14
 */
void
gst_rtsp_permissions_add_role_empty (GstRTSPPermissions * permissions,
    const gchar * role)
{
  gst_rtsp_permissions_add_role (permissions, role, NULL);
}

/**
 * gst_rtsp_permissions_add_role:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 * @fieldname: the first field name
 * @...: additional arguments
 *
 * Add a new @role to @permissions with the given variables. The fields
 * are the same layout as gst_structure_new().
 */
void
gst_rtsp_permissions_add_role (GstRTSPPermissions * permissions,
    const gchar * role, const gchar * fieldname, ...)
{
  va_list var_args;

  va_start (var_args, fieldname);
  gst_rtsp_permissions_add_role_valist (permissions, role, fieldname, var_args);
  va_end (var_args);
}

/**
 * gst_rtsp_permissions_add_role_valist:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 * @fieldname: the first field name
 * @var_args: additional fields to add
 *
 * Add a new @role to @permissions with the given variables. Structure fields
 * are set according to the varargs in a manner similar to gst_structure_new().
 */
void
gst_rtsp_permissions_add_role_valist (GstRTSPPermissions * permissions,
    const gchar * role, const gchar * fieldname, va_list var_args)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;
  GstStructure *structure;

  g_return_if_fail (GST_IS_RTSP_PERMISSIONS (permissions));
  g_return_if_fail (gst_mini_object_is_writable (&permissions->mini_object));
  g_return_if_fail (role != NULL);

  structure = gst_structure_new_valist (role, fieldname, var_args);
  g_return_if_fail (structure != NULL);

  add_role_from_structure (impl, structure);
}

/**
 * gst_rtsp_permissions_add_role_from_structure:
 *
 * Add a new role to @permissions based on @structure, for example
 * given a role named `tester`, which should be granted a permission named
 * `permission1`, the structure could be created with:
 *
 * ```
 * gst_structure_new ("tester", "permission1", G_TYPE_BOOLEAN, TRUE, NULL);
 * ```
 *
 * Since: 1.14
 */
void
gst_rtsp_permissions_add_role_from_structure (GstRTSPPermissions * permissions,
    GstStructure * structure)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;
  GstStructure *copy;

  g_return_if_fail (GST_IS_RTSP_PERMISSIONS (permissions));
  g_return_if_fail (GST_IS_STRUCTURE (structure));

  copy = gst_structure_copy (structure);

  add_role_from_structure (impl, copy);
}

/**
 * gst_rtsp_permissions_remove_role:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 *
 * Remove all permissions for @role in @permissions.
 */
void
gst_rtsp_permissions_remove_role (GstRTSPPermissions * permissions,
    const gchar * role)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;
  guint i, len;

  g_return_if_fail (GST_IS_RTSP_PERMISSIONS (permissions));
  g_return_if_fail (gst_mini_object_is_writable (&permissions->mini_object));
  g_return_if_fail (role != NULL);

  len = impl->roles->len;
  for (i = 0; i < len; i++) {
    GstStructure *entry = g_ptr_array_index (impl->roles, i);

    if (gst_structure_has_name (entry, role)) {
      g_ptr_array_remove_index_fast (impl->roles, i);
      break;
    }
  }
}

/**
 * gst_rtsp_permissions_get_role:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 *
 * Get all permissions for @role in @permissions.
 *
 * Returns: (transfer none): the structure with permissions for @role. It
 * remains valid for as long as @permissions is valid.
 */
const GstStructure *
gst_rtsp_permissions_get_role (GstRTSPPermissions * permissions,
    const gchar * role)
{
  GstRTSPPermissionsImpl *impl = (GstRTSPPermissionsImpl *) permissions;
  guint i, len;

  g_return_val_if_fail (GST_IS_RTSP_PERMISSIONS (permissions), NULL);
  g_return_val_if_fail (role != NULL, NULL);

  len = impl->roles->len;
  for (i = 0; i < len; i++) {
    GstStructure *entry = g_ptr_array_index (impl->roles, i);

    if (gst_structure_has_name (entry, role))
      return entry;
  }
  return NULL;
}

/**
 * gst_rtsp_permissions_is_allowed:
 * @permissions: a #GstRTSPPermissions
 * @role: a role
 * @permission: a permission
 *
 * Check if @role in @permissions is given permission for @permission.
 *
 * Returns: %TRUE if @role is allowed @permission.
 */
gboolean
gst_rtsp_permissions_is_allowed (GstRTSPPermissions * permissions,
    const gchar * role, const gchar * permission)
{
  const GstStructure *str;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_PERMISSIONS (permissions), FALSE);
  g_return_val_if_fail (role != NULL, FALSE);
  g_return_val_if_fail (permission != NULL, FALSE);

  str = gst_rtsp_permissions_get_role (permissions, role);
  if (str == NULL)
    return FALSE;

  if (!gst_structure_get_boolean (str, permission, &result))
    result = FALSE;

  return result;
}
 07070100000051000081A400000000000000000000000169D554BF0000124C000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-permissions.h /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#ifndef __GST_RTSP_PERMISSIONS_H__
#define __GST_RTSP_PERMISSIONS_H__

#include "rtsp-server-prelude.h"

typedef struct _GstRTSPPermissions GstRTSPPermissions;

G_BEGIN_DECLS

GST_RTSP_SERVER_API
GType gst_rtsp_permissions_get_type (void);

#define GST_TYPE_RTSP_PERMISSIONS        (gst_rtsp_permissions_get_type ())
#define GST_IS_RTSP_PERMISSIONS(obj)     (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_RTSP_PERMISSIONS))
#define GST_RTSP_PERMISSIONS_CAST(obj)   ((GstRTSPPermissions*)(obj))
#define GST_RTSP_PERMISSIONS(obj)        (GST_RTSP_PERMISSIONS_CAST(obj))

/**
 * GstRTSPPermissions:
 *
 * The opaque permissions structure. It is used to define the permissions
 * of objects in different roles.
 */
struct _GstRTSPPermissions {
  GstMiniObject mini_object;
};

/* refcounting */
/**
 * gst_rtsp_permissions_ref:
 * @permissions: The permissions to refcount
 *
 * Increase the refcount of this permissions.
 *
 * Returns: (transfer full): @permissions (for convenience when doing assignments)
 */
static inline GstRTSPPermissions *
gst_rtsp_permissions_ref (GstRTSPPermissions * permissions)
{
  return (GstRTSPPermissions *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (permissions));
}

/**
 * gst_rtsp_permissions_unref:
 * @permissions: (transfer full): the permissions to refcount
 *
 * Decrease the refcount of an permissions, freeing it if the refcount reaches 0.
 */
static inline void
gst_rtsp_permissions_unref (GstRTSPPermissions * permissions)
{
  gst_mini_object_unref (GST_MINI_OBJECT_CAST (permissions));
}


GST_RTSP_SERVER_API
GstRTSPPermissions *  gst_rtsp_permissions_new             (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_add_role        (GstRTSPPermissions *permissions,
                                                            const gchar *role,
                                                            const gchar *fieldname, ...);

GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_add_role_valist (GstRTSPPermissions *permissions,
                                                            const gchar *role,
                                                            const gchar *fieldname,
                                                            va_list var_args);

GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_add_role_empty  (GstRTSPPermissions * permissions,
                                                            const gchar * role);

GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_add_role_from_structure (GstRTSPPermissions * permissions,
                                                            GstStructure *structure);
GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_add_permission_for_role (GstRTSPPermissions * permissions,
                                                            const gchar * role,
                                                            const gchar * permission,
                                                            gboolean      allowed);

GST_RTSP_SERVER_API
void                  gst_rtsp_permissions_remove_role     (GstRTSPPermissions *permissions,
                                                            const gchar *role);

GST_RTSP_SERVER_API
const GstStructure *  gst_rtsp_permissions_get_role        (GstRTSPPermissions *permissions,
                                                            const gchar *role);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_permissions_is_allowed      (GstRTSPPermissions *permissions,
                                                            const gchar *role, const gchar *permission);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPPermissions, gst_rtsp_permissions_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_PERMISSIONS_H__ */
07070100000052000081A400000000000000000000000169D554BF00004606000000000000000000000000000000000000003200000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-sdp.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#define GLIB_DISABLE_DEPRECATION_WARNINGS

/**
 * SECTION:rtsp-sdp
 * @short_description: Make SDP messages
 * @see_also: #GstRTSPMedia
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include <gst/net/net.h>
#include <gst/sdp/gstmikey.h>

#include "rtsp-sdp.h"

static gboolean
get_info_from_tags (GstPad * pad, GstEvent ** event, gpointer user_data)
{
  GstSDPMedia *media = (GstSDPMedia *) user_data;

  if (GST_EVENT_TYPE (*event) == GST_EVENT_TAG) {
    GstTagList *tags;
    guint bitrate = 0;

    gst_event_parse_tag (*event, &tags);

    if (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_STREAM)
      return TRUE;

    if (!gst_tag_list_get_uint (tags, GST_TAG_MAXIMUM_BITRATE,
            &bitrate) || bitrate == 0)
      if (!gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &bitrate) ||
          bitrate == 0)
        return TRUE;

    /* set bandwidth (kbits/s) */
    gst_sdp_media_add_bandwidth (media, GST_SDP_BWTYPE_AS, bitrate / 1000);

    return FALSE;

  }

  return TRUE;
}

static void
update_sdp_from_tags (GstRTSPStream * stream, GstSDPMedia * stream_media)
{
  GstPad *src_pad;

  src_pad = gst_rtsp_stream_get_srcpad (stream);
  if (!src_pad)
    return;

  gst_pad_sticky_events_foreach (src_pad, get_info_from_tags, stream_media);

  gst_object_unref (src_pad);
}

static guint
get_roc_from_stats (GstStructure * stats, guint ssrc)
{
  const GValue *va, *v;
  guint i, len;
  /* initialize roc to something different than 0, so if we don't get
     the proper ROC from the encoder, streaming should fail initially. */
  guint roc = -1;

  va = gst_structure_get_value (stats, "streams");
  if (!va || !G_VALUE_HOLDS (va, GST_TYPE_ARRAY)) {
    GST_WARNING ("stats doesn't have a valid 'streams' field");
    return 0;
  }

  len = gst_value_array_get_size (va);

  /* look if there's any SSRC that matches. */
  for (i = 0; i < len; i++) {
    GstStructure *stream;
    v = gst_value_array_get_value (va, i);
    if (v && (stream = g_value_get_boxed (v))) {
      guint stream_ssrc;
      gst_structure_get_uint (stream, "ssrc", &stream_ssrc);
      if (stream_ssrc == ssrc) {
        gst_structure_get_uint (stream, "roc", &roc);
        break;
      }
    }
  }

  return roc;
}

static gboolean
mikey_add_crypto_sessions (GstRTSPStream * stream, GstMIKEYMessage * msg)
{
  guint i;
  GObject *session;
  GstElement *encoder;
  GValueArray *sources;
  gboolean roc_found;

  encoder = gst_rtsp_stream_get_srtp_encoder (stream);
  if (encoder == NULL) {
    GST_ERROR ("unable to get SRTP encoder from stream %p", stream);
    return FALSE;
  }

  session = gst_rtsp_stream_get_rtpsession (stream);
  if (session == NULL) {
    GST_ERROR ("unable to get RTP session from stream %p", stream);
    gst_object_unref (encoder);
    return FALSE;
  }

  roc_found = FALSE;
  g_object_get (session, "sources", &sources, NULL);
  for (i = 0; sources && (i < sources->n_values); i++) {
    GValue *val;
    GObject *source;
    guint32 ssrc;
    gboolean is_sender;

    val = g_value_array_get_nth (sources, i);
    source = (GObject *) g_value_get_object (val);

    g_object_get (source, "ssrc", &ssrc, "is-sender", &is_sender, NULL);

    if (is_sender) {
      guint32 roc = -1;
      GstStructure *stats;

      g_object_get (encoder, "stats", &stats, NULL);

      if (stats) {
        roc = get_roc_from_stats (stats, ssrc);
        gst_structure_free (stats);
      }

      roc_found = !!(roc != -1);
      if (!roc_found) {
        GST_ERROR ("unable to obtain ROC for stream %p with SSRC %u",
            stream, ssrc);
        goto cleanup;
      }

      GST_INFO ("stream %p with SSRC %u has a ROC of %u", stream, ssrc, roc);

      gst_mikey_message_add_cs_srtp (msg, 0, ssrc, roc);
    }
  }

cleanup:
  {
    g_value_array_free (sources);

    gst_object_unref (encoder);
    g_object_unref (session);
    return roc_found;
  }
}

/**
 * gst_rtsp_sdp_make_media:
 * @sdp: a #GstRTSPMessage
 * @info: a #GstSDPInfo
 * @stream: a #GstRTSPStream
 * @caps: a #GstCaps
 * @profile: a #GstRTSPProfile
 *
 * Creates a #GstSDPMedia from the parameters and stores it in @sdp.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_sdp_make_media (GstSDPMessage * sdp, GstSDPInfo * info,
    GstRTSPStream * stream, GstCaps * caps, GstRTSPProfile profile)
{
  GstSDPMedia *smedia;
  gchar *tmp;
  GstRTSPLowerTrans ltrans;
  GSocketFamily family;
  const gchar *addrtype, *proto;
  gchar *address;
  guint ttl;
  GstClockTime rtx_time;
  gchar *base64;
  GstMIKEYMessage *mikey_msg;

  gst_sdp_media_new (&smedia);

  if (gst_sdp_media_set_media_from_caps (caps, smedia) != GST_SDP_OK) {
    goto caps_error;
  }

  gst_sdp_media_set_port_info (smedia, 0, 1);

  switch (profile) {
    case GST_RTSP_PROFILE_AVP:
      proto = "RTP/AVP";
      break;
    case GST_RTSP_PROFILE_AVPF:
      proto = "RTP/AVPF";
      break;
    case GST_RTSP_PROFILE_SAVP:
      proto = "RTP/SAVP";
      break;
    case GST_RTSP_PROFILE_SAVPF:
      proto = "RTP/SAVPF";
      break;
    default:
      proto = "udp";
      break;
  }
  gst_sdp_media_set_proto (smedia, proto);

  if (info->is_ipv6) {
    addrtype = "IP6";
    family = G_SOCKET_FAMILY_IPV6;
  } else {
    addrtype = "IP4";
    family = G_SOCKET_FAMILY_IPV4;
  }

  ltrans = gst_rtsp_stream_get_protocols (stream);
  if (ltrans == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
    GstRTSPAddress *addr;

    addr = gst_rtsp_stream_get_multicast_address (stream, family);
    if (addr == NULL)
      goto no_multicast;

    address = g_strdup (addr->address);
    ttl = addr->ttl;
    gst_rtsp_address_free (addr);
  } else {
    ttl = 16;
    if (info->is_ipv6)
      address = g_strdup ("::");
    else
      address = g_strdup ("0.0.0.0");
  }

  /* for the c= line */
  gst_sdp_media_add_connection (smedia, "IN", addrtype, address, ttl, 1);
  g_free (address);

  /* the config uri */
  tmp = gst_rtsp_stream_get_control (stream);
  gst_sdp_media_add_attribute (smedia, "control", tmp);
  g_free (tmp);

  /* check for srtp */
  mikey_msg = gst_mikey_message_new_from_caps (caps);
  if (mikey_msg) {
    /* add policy '0' for all sending SSRC */
    if (!mikey_add_crypto_sessions (stream, mikey_msg)) {
      gst_mikey_message_unref (mikey_msg);
      goto crypto_sessions_error;
    }

    base64 = gst_mikey_message_base64_encode (mikey_msg);
    if (base64) {
      tmp = g_strdup_printf ("mikey %s", base64);
      g_free (base64);
      gst_sdp_media_add_attribute (smedia, "key-mgmt", tmp);
      g_free (tmp);
    }

    gst_mikey_message_unref (mikey_msg);
  }

  /* RFC 7273 clock signalling */
  if (gst_rtsp_stream_is_sender (stream)) {
    GstBin *joined_bin = gst_rtsp_stream_get_joined_bin (stream);
    GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (joined_bin));
    gchar *ts_refclk = NULL;
    gchar *mediaclk = NULL;
    guint rtptime, clock_rate;
    GstClockTime running_time, base_time, clock_time;
    GstRTSPPublishClockMode publish_clock_mode =
        gst_rtsp_stream_get_publish_clock_mode (stream);

    if (!gst_rtsp_stream_get_rtpinfo (stream, &rtptime, NULL, &clock_rate,
            &running_time))
      goto clock_signalling_cleanup;
    base_time = gst_element_get_base_time (GST_ELEMENT_CAST (joined_bin));
    g_assert (base_time != GST_CLOCK_TIME_NONE);
    clock_time = running_time + base_time;

    if (publish_clock_mode != GST_RTSP_PUBLISH_CLOCK_MODE_NONE && clock) {
      if (GST_IS_NTP_CLOCK (clock) || GST_IS_PTP_CLOCK (clock)) {
        if (publish_clock_mode == GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
          guint32 mediaclk_offset;

          /* Calculate RTP time at the clock's epoch. That's the direct offset */
          clock_time =
              gst_util_uint64_scale (clock_time, clock_rate, GST_SECOND);

          clock_time &= 0xffffffff;
          mediaclk_offset =
              G_GUINT64_CONSTANT (0xffffffff) + rtptime - clock_time;
          mediaclk = g_strdup_printf ("direct=%u", (guint32) mediaclk_offset);
        }

        if (GST_IS_NTP_CLOCK (clock)) {
          gchar *ntp_address;
          guint ntp_port;

          g_object_get (clock, "address", &ntp_address, "port", &ntp_port,
              NULL);

          if (ntp_port == 123)
            ts_refclk = g_strdup_printf ("ntp=%s", ntp_address);
          else
            ts_refclk = g_strdup_printf ("ntp=%s:%u", ntp_address, ntp_port);

          g_free (ntp_address);
        } else {
          guint64 ptp_clock_id;
          guint ptp_domain;

          g_object_get (clock, "grandmaster-clock-id", &ptp_clock_id, "domain",
              &ptp_domain, NULL);

          if (ptp_domain != 0)
            ts_refclk =
                g_strdup_printf
                ("ptp=IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%u",
                (guint) (ptp_clock_id >> 56) & 0xff,
                (guint) (ptp_clock_id >> 48) & 0xff,
                (guint) (ptp_clock_id >> 40) & 0xff,
                (guint) (ptp_clock_id >> 32) & 0xff,
                (guint) (ptp_clock_id >> 24) & 0xff,
                (guint) (ptp_clock_id >> 16) & 0xff,
                (guint) (ptp_clock_id >> 8) & 0xff,
                (guint) (ptp_clock_id >> 0) & 0xff, ptp_domain);
          else
            ts_refclk =
                g_strdup_printf
                ("ptp=IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X",
                (guint) (ptp_clock_id >> 56) & 0xff,
                (guint) (ptp_clock_id >> 48) & 0xff,
                (guint) (ptp_clock_id >> 40) & 0xff,
                (guint) (ptp_clock_id >> 32) & 0xff,
                (guint) (ptp_clock_id >> 24) & 0xff,
                (guint) (ptp_clock_id >> 16) & 0xff,
                (guint) (ptp_clock_id >> 8) & 0xff,
                (guint) (ptp_clock_id >> 0) & 0xff);
        }
      }
    }
  clock_signalling_cleanup:
    if (clock)
      gst_object_unref (clock);

    if (!ts_refclk)
      ts_refclk = g_strdup ("local");
    if (!mediaclk)
      mediaclk = g_strdup ("sender");

    gst_sdp_media_add_attribute (smedia, "ts-refclk", ts_refclk);
    gst_sdp_media_add_attribute (smedia, "mediaclk", mediaclk);
    g_free (ts_refclk);
    g_free (mediaclk);
    gst_object_unref (joined_bin);
  }

  update_sdp_from_tags (stream, smedia);

  if (profile == GST_RTSP_PROFILE_AVPF || profile == GST_RTSP_PROFILE_SAVPF) {
    if ((rtx_time = gst_rtsp_stream_get_retransmission_time (stream))) {
      /* ssrc multiplexed retransmit functionality */
      guint rtx_pt = gst_rtsp_stream_get_retransmission_pt (stream);

      if (rtx_pt == 0) {
        g_warning ("failed to find an available dynamic payload type. "
            "Not adding retransmission");
      } else {
        gchar *tmp;
        GstStructure *s;
        gint caps_pt, caps_rate;

        s = gst_caps_get_structure (caps, 0);
        if (s == NULL)
          goto no_caps_info;

        /* get payload type and clock rate */
        gst_structure_get_int (s, "payload", &caps_pt);
        gst_structure_get_int (s, "clock-rate", &caps_rate);

        tmp = g_strdup_printf ("%d", rtx_pt);
        gst_sdp_media_add_format (smedia, tmp);
        g_free (tmp);

        tmp = g_strdup_printf ("%d rtx/%d", rtx_pt, caps_rate);
        gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
        g_free (tmp);

        tmp =
            g_strdup_printf ("%d apt=%d;rtx-time=%" G_GINT64_FORMAT, rtx_pt,
            caps_pt, GST_TIME_AS_MSECONDS (rtx_time));
        gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
        g_free (tmp);
      }
    }

    if (gst_rtsp_stream_get_ulpfec_percentage (stream)) {
      guint ulpfec_pt = gst_rtsp_stream_get_ulpfec_pt (stream);

      if (ulpfec_pt == 0) {
        g_warning ("failed to find an available dynamic payload type. "
            "Not adding ulpfec");
      } else {
        gchar *tmp;
        GstStructure *s;
        gint caps_pt, caps_rate;

        s = gst_caps_get_structure (caps, 0);
        if (s == NULL)
          goto no_caps_info;

        /* get payload type and clock rate */
        gst_structure_get_int (s, "payload", &caps_pt);
        gst_structure_get_int (s, "clock-rate", &caps_rate);

        tmp = g_strdup_printf ("%d", ulpfec_pt);
        gst_sdp_media_add_format (smedia, tmp);
        g_free (tmp);

        tmp = g_strdup_printf ("%d ulpfec/%d", ulpfec_pt, caps_rate);
        gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
        g_free (tmp);

        tmp = g_strdup_printf ("%d apt=%d", ulpfec_pt, caps_pt);
        gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
        g_free (tmp);
      }
    }
  }

  /* RFC5576 Source-specific media attributes */
  {
    GObject *session;
    guint ssrc;
    GstStructure *sdes;
    const gchar *cname;
    gchar *ssrc_cname;

    session = gst_rtsp_stream_get_rtpsession (stream);
    if (session) {
      g_object_get (session, "sdes", &sdes, NULL);

      cname = gst_structure_get_string (sdes, "cname");
      gst_rtsp_stream_get_ssrc (stream, &ssrc);

      if (cname) {
        ssrc_cname = g_strdup_printf ("%u cname:%s", ssrc, cname);
        gst_sdp_media_add_attribute (smedia, "ssrc", ssrc_cname);
        g_free (ssrc_cname);
      } else {
        GST_ERROR ("unable to get CNAME for stream %p", stream);
      }
      gst_structure_free (sdes);
      g_object_unref (session);
    } else {
      GST_ERROR ("unable to get RTP session from stream %p", stream);
    }
  }

  gst_sdp_message_add_media (sdp, smedia);
  gst_sdp_media_free (smedia);

  return TRUE;

  /* ERRORS */
caps_error:
  {
    gst_sdp_media_free (smedia);
    GST_ERROR ("unable to set media from caps for stream %d",
        gst_rtsp_stream_get_index (stream));
    return FALSE;
  }
no_multicast:
  {
    gst_sdp_media_free (smedia);
    GST_ERROR ("stream %d has no multicast address",
        gst_rtsp_stream_get_index (stream));
    return FALSE;
  }
no_caps_info:
  {
    gst_sdp_media_free (smedia);
    GST_ERROR ("caps for stream %d have no structure",
        gst_rtsp_stream_get_index (stream));
    return FALSE;
  }
crypto_sessions_error:
  {
    gst_sdp_media_free (smedia);
    GST_ERROR ("unable to add MIKEY crypto sessions for stream %d",
        gst_rtsp_stream_get_index (stream));
    return FALSE;
  }
}

/**
 * gst_rtsp_sdp_from_media:
 * @sdp: a #GstSDPMessage
 * @info: (transfer none): a #GstSDPInfo
 * @media: (transfer none): a #GstRTSPMedia
 *
 * Add @media specific info to @sdp. @info is used to configure the connection
 * information in the SDP.
 *
 * Returns: TRUE on success.
 */
gboolean
gst_rtsp_sdp_from_media (GstSDPMessage * sdp, GstSDPInfo * info,
    GstRTSPMedia * media)
{
  guint i, n_streams;
  gchar *rangestr;
  gboolean res;

  n_streams = gst_rtsp_media_n_streams (media);

  rangestr = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  if (rangestr == NULL)
    goto not_prepared;

  gst_sdp_message_add_attribute (sdp, "range", rangestr);
  g_free (rangestr);

  res = TRUE;
  for (i = 0; res && (i < n_streams); i++) {
    GstRTSPStream *stream;

    stream = gst_rtsp_media_get_stream (media, i);
    res = gst_rtsp_sdp_from_stream (sdp, info, stream);
    if (!res) {
      GST_ERROR ("could not get SDP from stream %p", stream);
      goto sdp_error;
    }
  }

  {
    GstNetTimeProvider *provider;

    if ((provider =
            gst_rtsp_media_get_time_provider (media, info->server_ip, 0))) {
      GstClock *clock;
      gchar *address, *str;
      gint port;

      g_object_get (provider, "clock", &clock, "address", &address, "port",
          &port, NULL);

      str = g_strdup_printf ("GstNetTimeProvider %s %s:%d %" G_GUINT64_FORMAT,
          g_type_name (G_TYPE_FROM_INSTANCE (clock)), address, port,
          gst_clock_get_time (clock));

      gst_sdp_message_add_attribute (sdp, "x-gst-clock", str);
      g_free (str);
      gst_object_unref (clock);
      g_free (address);
      gst_object_unref (provider);
    }
  }

  return res;

  /* ERRORS */
not_prepared:
  {
    GST_ERROR ("media %p is not prepared", media);
    return FALSE;
  }
sdp_error:
  {
    GST_ERROR ("could not get SDP from media %p", media);
    return FALSE;
  }
}

/**
 * gst_rtsp_sdp_from_stream:
 * @sdp: a #GstSDPMessage
 * @info: (transfer none): a #GstSDPInfo
 * @stream: (transfer none): a #GstRTSPStream
 *
 * Add info from @stream to @sdp.
 *
 * Returns: TRUE on success.
 */
gboolean
gst_rtsp_sdp_from_stream (GstSDPMessage * sdp, GstSDPInfo * info,
    GstRTSPStream * stream)
{
  GstCaps *caps;
  GstRTSPProfile profiles;
  guint mask;
  gboolean res;

  caps = gst_rtsp_stream_get_caps (stream);

  if (caps == NULL) {
    GST_ERROR ("stream %p has no caps", stream);
    return FALSE;
  }

  /* make a new media for each profile */
  profiles = gst_rtsp_stream_get_profiles (stream);
  mask = 1;
  res = TRUE;
  while (res && (profiles >= mask)) {
    GstRTSPProfile prof = profiles & mask;

    if (prof)
      res = gst_rtsp_sdp_make_media (sdp, info, stream, caps, prof);

    mask <<= 1;
  }
  gst_caps_unref (caps);

  return res;
}
  07070100000053000081A400000000000000000000000169D554BF00000601000000000000000000000000000000000000003200000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-sdp.h /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/sdp/gstsdpmessage.h>

#include "rtsp-media.h"

#ifndef __GST_RTSP_SDP_H__
#define __GST_RTSP_SDP_H__

G_BEGIN_DECLS

typedef struct {
  gboolean is_ipv6;
  const gchar *server_ip;
} GstSDPInfo;

/* creating SDP */

GST_RTSP_SERVER_API
gboolean            gst_rtsp_sdp_from_media  (GstSDPMessage *sdp, GstSDPInfo *info, GstRTSPMedia * media);

GST_RTSP_SERVER_API
gboolean            gst_rtsp_sdp_from_stream (GstSDPMessage * sdp, GstSDPInfo * info, GstRTSPStream *stream);

GST_RTSP_SERVER_API
gboolean
gst_rtsp_sdp_make_media (GstSDPMessage * sdp, GstSDPInfo * info, GstRTSPStream * stream, GstCaps * caps, GstRTSPProfile profile);

G_END_DECLS

#endif /* __GST_RTSP_SDP_H__ */
   07070100000054000081A400000000000000000000000169D554BF00000E81000000000000000000000000000000000000003E00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-server-internal.h /* GStreamer
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_SERVER_INTERNAL_H__
#define __GST_RTSP_SERVER_INTERNAL_H__

#include <glib.h>

G_BEGIN_DECLS

#include "rtsp-stream-transport.h"

/* Internal GstRTSPStreamTransport interface */

typedef gboolean (*GstRTSPBackPressureFunc) (guint8 channel, gpointer user_data);

gboolean                 gst_rtsp_stream_transport_backlog_push  (GstRTSPStreamTransport *trans,
                                                                  GstBuffer *buffer,
                                                                  GstBufferList *buffer_list,
                                                                  gboolean is_rtp);

gboolean                 gst_rtsp_stream_transport_backlog_pop   (GstRTSPStreamTransport *trans,
                                                                  GstBuffer **buffer,
                                                                  GstBufferList **buffer_list,
                                                                  gboolean *is_rtp);

gboolean                 gst_rtsp_stream_transport_backlog_peek_is_rtp (GstRTSPStreamTransport * trans);

gboolean                 gst_rtsp_stream_transport_backlog_is_empty (GstRTSPStreamTransport *trans);

void                     gst_rtsp_stream_transport_clear_backlog (GstRTSPStreamTransport * trans);

void                     gst_rtsp_stream_transport_lock_backlog  (GstRTSPStreamTransport * trans);

void                     gst_rtsp_stream_transport_unlock_backlog (GstRTSPStreamTransport * trans);

void                     gst_rtsp_stream_transport_set_back_pressure_callback (GstRTSPStreamTransport *trans,
                                                                  GstRTSPBackPressureFunc back_pressure_func,
                                                                  gpointer user_data,
                                                                  GDestroyNotify  notify);

gboolean                 gst_rtsp_stream_transport_check_back_pressure (GstRTSPStreamTransport *trans,
                                                                  gboolean is_rtp);

gboolean                 gst_rtsp_stream_is_tcp_receiver (GstRTSPStream * stream);

void                     gst_rtsp_media_set_enable_rtcp (GstRTSPMedia *media, gboolean enable);
void                     gst_rtsp_stream_set_enable_rtcp (GstRTSPStream *stream, gboolean enable);

void                     gst_rtsp_stream_set_drop_delta_units (GstRTSPStream * stream, gboolean drop);

gboolean                 gst_rtsp_stream_install_drop_probe (GstRTSPStream * stream);

GstRTSPStream *          gst_rtsp_media_create_and_join_stream (GstRTSPMedia * media,
                                                                GstElement * payloader,
                                                                GstPad * pad);

G_END_DECLS

#endif /* __GST_RTSP_SERVER_INTERNAL_H__ */
   07070100000055000081A400000000000000000000000169D554BF000020C5000000000000000000000000000000000000003C00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-server-object.h   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_SERVER_OBJECT_H__
#define __GST_RTSP_SERVER_OBJECT_H__

#include <gst/gst.h>

G_BEGIN_DECLS

typedef struct _GstRTSPServer GstRTSPServer;
typedef struct _GstRTSPServerClass GstRTSPServerClass;
typedef struct _GstRTSPServerPrivate GstRTSPServerPrivate;

#include "rtsp-server-prelude.h"
#include "rtsp-session-pool.h"
#include "rtsp-session.h"
#include "rtsp-media.h"
#include "rtsp-stream.h"
#include "rtsp-stream-transport.h"
#include "rtsp-address-pool.h"
#include "rtsp-thread-pool.h"
#include "rtsp-client.h"
#include "rtsp-context.h"
#include "rtsp-mount-points.h"
#include "rtsp-media-factory.h"
#include "rtsp-permissions.h"
#include "rtsp-auth.h"
#include "rtsp-token.h"
#include "rtsp-session-media.h"
#include "rtsp-sdp.h"
#include "rtsp-media-factory-uri.h"
#include "rtsp-params.h"

#define GST_TYPE_RTSP_SERVER              (gst_rtsp_server_get_type ())
#define GST_IS_RTSP_SERVER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SERVER))
#define GST_IS_RTSP_SERVER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SERVER))
#define GST_RTSP_SERVER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServerClass))
#define GST_RTSP_SERVER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServer))
#define GST_RTSP_SERVER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SERVER, GstRTSPServerClass))
#define GST_RTSP_SERVER_CAST(obj)         ((GstRTSPServer*)(obj))
#define GST_RTSP_SERVER_CLASS_CAST(klass) ((GstRTSPServerClass*)(klass))

/**
 * GstRTSPServer:
 *
 * This object listens on a port, creates and manages the clients connected to
 * it.
 */
struct _GstRTSPServer {
  GObject      parent;

  /*< private >*/
  GstRTSPServerPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPServerClass:
 * @create_client: Create, configure a new GstRTSPClient
 *          object that handles the new connection on @socket. The default
 *          implementation will create a GstRTSPClient and will configure the
 *          mount-points, auth, session-pool and thread-pool on the client.
 * @client_connected: emitted when a new client connected.
 *
 * The RTSP server class structure
 */
struct _GstRTSPServerClass {
  GObjectClass  parent_class;

  GstRTSPClient * (*create_client)      (GstRTSPServer *server);

  /* signals */
  void            (*client_connected)   (GstRTSPServer *server, GstRTSPClient *client);

  /*< private >*/
  gpointer         _gst_reserved[GST_PADDING_LARGE];
};

GST_RTSP_SERVER_API
GType                 gst_rtsp_server_get_type             (void);

GST_RTSP_SERVER_API
GstRTSPServer *       gst_rtsp_server_new                  (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_address          (GstRTSPServer *server, const gchar *address);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_server_get_address          (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_service          (GstRTSPServer *server, const gchar *service);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_server_get_service          (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_backlog          (GstRTSPServer *server, gint backlog);

GST_RTSP_SERVER_API
gint                  gst_rtsp_server_get_backlog          (GstRTSPServer *server);

GST_RTSP_SERVER_API
int                   gst_rtsp_server_get_bound_port       (GstRTSPServer *server);

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_session_pool     (GstRTSPServer *server, GstRTSPSessionPool *pool);

GST_RTSP_SERVER_API
GstRTSPSessionPool *  gst_rtsp_server_get_session_pool     (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_mount_points     (GstRTSPServer *server, GstRTSPMountPoints *mounts);

GST_RTSP_SERVER_API
GstRTSPMountPoints *  gst_rtsp_server_get_mount_points     (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_content_length_limit (GstRTSPServer * server, guint limit);

GST_RTSP_SERVER_API
guint                 gst_rtsp_server_get_content_length_limit (GstRTSPServer * server);

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_auth             (GstRTSPServer *server, GstRTSPAuth *auth);

GST_RTSP_SERVER_API
GstRTSPAuth *         gst_rtsp_server_get_auth             (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                  gst_rtsp_server_set_thread_pool      (GstRTSPServer *server, GstRTSPThreadPool *pool);

GST_RTSP_SERVER_API
GstRTSPThreadPool *   gst_rtsp_server_get_thread_pool      (GstRTSPServer *server) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean              gst_rtsp_server_transfer_connection  (GstRTSPServer * server, GSocket *socket,
                                                            const gchar * ip, gint port,
                                                            const gchar *initial_buffer);

GST_RTSP_SERVER_API
gboolean              gst_rtsp_server_io_func              (GSocket *socket, GIOCondition condition,
                                                            GstRTSPServer *server);

GST_RTSP_SERVER_API
GSocket *             gst_rtsp_server_create_socket        (GstRTSPServer *server,
                                                            GCancellable  *cancellable,
                                                            GError **error) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GSource *             gst_rtsp_server_create_source        (GstRTSPServer *server,
                                                            GCancellable * cancellable,
                                                            GError **error) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
guint                 gst_rtsp_server_attach               (GstRTSPServer *server,
                                                            GMainContext *context);

/**
 * GstRTSPServerClientFilterFunc:
 * @server: a #GstRTSPServer object
 * @client: a #GstRTSPClient in @server
 * @user_data: user data that has been given to gst_rtsp_server_client_filter()
 *
 * This function will be called by the gst_rtsp_server_client_filter(). An
 * implementation should return a value of #GstRTSPFilterResult.
 *
 * When this function returns #GST_RTSP_FILTER_REMOVE, @client will be removed
 * from @server.
 *
 * A return value of #GST_RTSP_FILTER_KEEP will leave @client untouched in
 * @server.
 *
 * A value of #GST_RTSP_FILTER_REF will add @client to the result #GList of
 * gst_rtsp_server_client_filter().
 *
 * Returns: a #GstRTSPFilterResult.
 */
typedef GstRTSPFilterResult (*GstRTSPServerClientFilterFunc)  (GstRTSPServer *server,
                                                               GstRTSPClient *client,
                                                               gpointer user_data);

GST_RTSP_SERVER_API
GList *                gst_rtsp_server_client_filter    (GstRTSPServer *server,
                                                         GstRTSPServerClientFilterFunc func,
                                                         gpointer user_data) G_GNUC_WARN_UNUSED_RESULT;

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPServer, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_SERVER_OBJECT_H__ */
   07070100000056000081A400000000000000000000000169D554BF00000668000000000000000000000000000000000000003D00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-server-prelude.h  /* GStreamer RtspServer Library
 * Copyright (C) 2018 GStreamer developers
 *
 * rtspserver-prelude.h: prelude include header for gst-rtspserver library
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_SERVER_PRELUDE_H__
#define __GST_RTSP_SERVER_PRELUDE_H__

#include <gst/gst.h>

#ifndef GST_RTSP_SERVER_API
# ifdef BUILDING_GST_RTSP_SERVER
#  define GST_RTSP_SERVER_API GST_API_EXPORT         /* from config.h */
# else
#  define GST_RTSP_SERVER_API GST_API_IMPORT
# endif
#endif

/* Do *not* use these defines outside of rtsp-server. Use G_DEPRECATED instead. */
#ifdef GST_DISABLE_DEPRECATED
#define GST_RTSP_SERVER_DEPRECATED GST_RTSP_SERVER_API
#define GST_RTSP_SERVER_DEPRECATED_FOR(f) GST_RTSP_SERVER_API
#else
#define GST_RTSP_SERVER_DEPRECATED G_DEPRECATED GST_RTSP_SERVER_API
#define GST_RTSP_SERVER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) GST_RTSP_SERVER_API
#endif

#endif /* __GST_RTSP_SERVER_PRELUDE_H__ */
07070100000057000081A400000000000000000000000169D554BF0000A25A000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-server.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-server
 * @short_description: The main server object
 * @see_also: #GstRTSPClient, #GstRTSPThreadPool
 *
 * The server object is the object listening for connections on a port and
 * creating #GstRTSPClient objects to handle those connections.
 *
 * The server will listen on the address set with gst_rtsp_server_set_address()
 * and the port or service configured with gst_rtsp_server_set_service().
 * Use gst_rtsp_server_set_backlog() to configure the amount of pending requests
 * that the server will keep. By default the server listens on the current
 * network (0.0.0.0) and port 8554.
 *
 * The server will require an SSL connection when a TLS certificate has been
 * set in the auth object with gst_rtsp_auth_set_tls_certificate().
 *
 * To start the server, use gst_rtsp_server_attach() to attach it to a
 * #GMainContext. For more control, gst_rtsp_server_create_source() and
 * gst_rtsp_server_create_socket() can be used to get a #GSource and #GSocket
 * respectively.
 *
 * gst_rtsp_server_transfer_connection() can be used to transfer an existing
 * socket to the RTSP server, for example from an HTTP server.
 *
 * Once the server socket is attached to a mainloop, it will start accepting
 * connections. When a new connection is received, a new #GstRTSPClient object
 * is created to handle the connection. The new client will be configured with
 * the server #GstRTSPAuth, #GstRTSPMountPoints, #GstRTSPSessionPool and
 * #GstRTSPThreadPool.
 *
 * The server uses the configured #GstRTSPThreadPool object to handle the
 * remainder of the communication with this client.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>

#include "rtsp-context.h"
#include "rtsp-server-object.h"
#include "rtsp-client.h"

#define GST_RTSP_SERVER_GET_LOCK(server)  (&(GST_RTSP_SERVER_CAST(server)->priv->lock))
#define GST_RTSP_SERVER_LOCK(server)      (g_mutex_lock(GST_RTSP_SERVER_GET_LOCK(server)))
#define GST_RTSP_SERVER_UNLOCK(server)    (g_mutex_unlock(GST_RTSP_SERVER_GET_LOCK(server)))

struct _GstRTSPServerPrivate
{
  GMutex lock;                  /* protects everything in this struct */

  /* server information */
  gchar *address;
  gchar *service;
  gint backlog;

  GSocket *socket;

  /* sessions on this server */
  GstRTSPSessionPool *session_pool;

  /* mount points for this server */
  GstRTSPMountPoints *mount_points;

  /* request size limit */
  guint content_length_limit;

  /* authentication manager */
  GstRTSPAuth *auth;

  /* resource manager */
  GstRTSPThreadPool *thread_pool;

  /* the clients that are connected */
  GList *clients;
  guint clients_cookie;
};

#define DEFAULT_ADDRESS         "0.0.0.0"
#define DEFAULT_BOUND_PORT      -1
/* #define DEFAULT_ADDRESS         "::0" */
#define DEFAULT_SERVICE         "8554"
#define DEFAULT_BACKLOG         5

/* Define to use the SO_LINGER option so that the server sockets can be resused
 * sooner. Disabled for now because it is not very well implemented by various
 * OSes and it causes clients to fail to read the TEARDOWN response. */
#undef USE_SOLINGER

enum
{
  PROP_0,
  PROP_ADDRESS,
  PROP_SERVICE,
  PROP_BOUND_PORT,
  PROP_BACKLOG,

  PROP_SESSION_POOL,
  PROP_MOUNT_POINTS,
  PROP_CONTENT_LENGTH_LIMIT,
  PROP_LAST
};

enum
{
  SIGNAL_CLIENT_CONNECTED,
  SIGNAL_LAST
};

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPServer, gst_rtsp_server, G_TYPE_OBJECT);

GST_DEBUG_CATEGORY_STATIC (rtsp_server_debug);
#define GST_CAT_DEFAULT rtsp_server_debug

typedef struct _ClientContext ClientContext;

static guint gst_rtsp_server_signals[SIGNAL_LAST] = { 0 };

static void gst_rtsp_server_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_server_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_server_finalize (GObject * object);

static GstRTSPClient *default_create_client (GstRTSPServer * server);

static void
gst_rtsp_server_class_init (GstRTSPServerClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_server_get_property;
  gobject_class->set_property = gst_rtsp_server_set_property;
  gobject_class->finalize = gst_rtsp_server_finalize;

  /**
   * GstRTSPServer::address:
   *
   * The address of the server. This is the address where the server will
   * listen on.
   */
  g_object_class_install_property (gobject_class, PROP_ADDRESS,
      g_param_spec_string ("address", "Address",
          "The address the server uses to listen on", DEFAULT_ADDRESS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPServer::service:
   *
   * The service of the server. This is either a string with the service name or
   * a port number (as a string) the server will listen on.
   */
  g_object_class_install_property (gobject_class, PROP_SERVICE,
      g_param_spec_string ("service", "Service",
          "The service or port number the server uses to listen on",
          DEFAULT_SERVICE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPServer::bound-port:
   *
   * The actual port the server is listening on. Can be used to retrieve the
   * port number when the server is started on port 0, which means bind to a
   * random port. Set to -1 if the server has not been bound yet.
   */
  g_object_class_install_property (gobject_class, PROP_BOUND_PORT,
      g_param_spec_int ("bound-port", "Bound port",
          "The port number the server is listening on",
          -1, G_MAXUINT16, DEFAULT_BOUND_PORT,
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPServer::backlog:
   *
   * The backlog argument defines the maximum length to which the queue of
   * pending connections for the server may grow. If a connection request arrives
   * when the queue is full, the client may receive an error with an indication of
   * ECONNREFUSED or, if the underlying protocol supports retransmission, the
   * request may be ignored so that a later reattempt at connection succeeds.
   */
  g_object_class_install_property (gobject_class, PROP_BACKLOG,
      g_param_spec_int ("backlog", "Backlog",
          "The maximum length to which the queue "
          "of pending connections may grow", 0, G_MAXINT, DEFAULT_BACKLOG,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPServer::session-pool:
   *
   * The session pool of the server. By default each server has a separate
   * session pool but sessions can be shared between servers by setting the same
   * session pool on multiple servers.
   */
  g_object_class_install_property (gobject_class, PROP_SESSION_POOL,
      g_param_spec_object ("session-pool", "Session Pool",
          "The session pool to use for client session",
          GST_TYPE_RTSP_SESSION_POOL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPServer::mount-points:
   *
   * The mount points to use for this server. By default the server has no
   * mount points and thus cannot map urls to media streams.
   */
  g_object_class_install_property (gobject_class, PROP_MOUNT_POINTS,
      g_param_spec_object ("mount-points", "Mount Points",
          "The mount points to use for client session",
          GST_TYPE_RTSP_MOUNT_POINTS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * RTSPServer::content-length-limit:
   *
   * Define an appropriate request size limit and reject requests exceeding the
   * limit.
   *
   * Since: 1.18
   */
  g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH_LIMIT,
      g_param_spec_uint ("content-length-limit", "Limitation of Content-Length",
          "Limitation of Content-Length",
          0, G_MAXUINT, G_MAXUINT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_rtsp_server_signals[SIGNAL_CLIENT_CONNECTED] =
      g_signal_new ("client-connected", G_TYPE_FROM_CLASS (gobject_class),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPServerClass, client_connected),
      NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CLIENT);

  klass->create_client = default_create_client;

  GST_DEBUG_CATEGORY_INIT (rtsp_server_debug, "rtspserver", 0, "GstRTSPServer");
}

static void
gst_rtsp_server_init (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv = gst_rtsp_server_get_instance_private (server);

  server->priv = priv;

  g_mutex_init (&priv->lock);
  priv->address = g_strdup (DEFAULT_ADDRESS);
  priv->service = g_strdup (DEFAULT_SERVICE);
  priv->socket = NULL;
  priv->backlog = DEFAULT_BACKLOG;
  priv->session_pool = gst_rtsp_session_pool_new ();
  priv->mount_points = gst_rtsp_mount_points_new ();
  priv->content_length_limit = G_MAXUINT;
  priv->thread_pool = gst_rtsp_thread_pool_new ();
}

static void
gst_rtsp_server_finalize (GObject * object)
{
  GstRTSPServer *server = GST_RTSP_SERVER (object);
  GstRTSPServerPrivate *priv = server->priv;

  GST_DEBUG_OBJECT (server, "finalize server");

  g_free (priv->address);
  g_free (priv->service);

  if (priv->socket)
    g_object_unref (priv->socket);

  if (priv->session_pool)
    g_object_unref (priv->session_pool);
  if (priv->mount_points)
    g_object_unref (priv->mount_points);
  if (priv->thread_pool)
    g_object_unref (priv->thread_pool);

  if (priv->auth)
    g_object_unref (priv->auth);

  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_server_parent_class)->finalize (object);
}

/**
 * gst_rtsp_server_new:
 *
 * Create a new #GstRTSPServer instance.
 *
 * Returns: (transfer full): a new #GstRTSPServer
 */
GstRTSPServer *
gst_rtsp_server_new (void)
{
  GstRTSPServer *result;

  result = g_object_new (GST_TYPE_RTSP_SERVER, NULL);

  return result;
}

/**
 * gst_rtsp_server_set_address:
 * @server: a #GstRTSPServer
 * @address: the address
 *
 * Configure @server to accept connections on the given address.
 *
 * This function must be called before the server is bound.
 */
void
gst_rtsp_server_set_address (GstRTSPServer * server, const gchar * address)
{
  GstRTSPServerPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));
  g_return_if_fail (address != NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  g_free (priv->address);
  priv->address = g_strdup (address);
  GST_RTSP_SERVER_UNLOCK (server);
}

/**
 * gst_rtsp_server_get_address:
 * @server: a #GstRTSPServer
 *
 * Get the address on which the server will accept connections.
 *
 * Returns: (transfer full) (nullable): the server address. g_free() after usage.
 */
gchar *
gst_rtsp_server_get_address (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  result = g_strdup (priv->address);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_get_bound_port:
 * @server: a #GstRTSPServer
 *
 * Get the port number where the server was bound to.
 *
 * Returns: the port number
 */
int
gst_rtsp_server_get_bound_port (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  GSocketAddress *address;
  int result = -1;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), result);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  if (priv->socket == NULL)
    goto out;

  address = g_socket_get_local_address (priv->socket, NULL);
  result = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
  g_object_unref (address);

out:
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_service:
 * @server: a #GstRTSPServer
 * @service: the service
 *
 * Configure @server to accept connections on the given service.
 * @service should be a string containing the service name (see services(5)) or
 * a string containing a port number between 1 and 65535.
 *
 * When @service is set to "0", the server will listen on a random free
 * port. The actual used port can be retrieved with
 * gst_rtsp_server_get_bound_port().
 *
 * This function must be called before the server is bound.
 */
void
gst_rtsp_server_set_service (GstRTSPServer * server, const gchar * service)
{
  GstRTSPServerPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));
  g_return_if_fail (service != NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  g_free (priv->service);
  priv->service = g_strdup (service);
  GST_RTSP_SERVER_UNLOCK (server);
}

/**
 * gst_rtsp_server_get_service:
 * @server: a #GstRTSPServer
 *
 * Get the service on which the server will accept connections.
 *
 * Returns: (transfer full): the service. use g_free() after usage.
 */
gchar *
gst_rtsp_server_get_service (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  result = g_strdup (priv->service);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_backlog:
 * @server: a #GstRTSPServer
 * @backlog: the backlog
 *
 * configure the maximum amount of requests that may be queued for the
 * server.
 *
 * This function must be called before the server is bound.
 */
void
gst_rtsp_server_set_backlog (GstRTSPServer * server, gint backlog)
{
  GstRTSPServerPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  priv->backlog = backlog;
  GST_RTSP_SERVER_UNLOCK (server);
}

/**
 * gst_rtsp_server_get_backlog:
 * @server: a #GstRTSPServer
 *
 * The maximum amount of queued requests for the server.
 *
 * Returns: the server backlog.
 */
gint
gst_rtsp_server_get_backlog (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  gint result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), -1);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  result = priv->backlog;
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_session_pool:
 * @server: a #GstRTSPServer
 * @pool: (transfer none) (nullable): a #GstRTSPSessionPool
 *
 * configure @pool to be used as the session pool of @server.
 */
void
gst_rtsp_server_set_session_pool (GstRTSPServer * server,
    GstRTSPSessionPool * pool)
{
  GstRTSPServerPrivate *priv;
  GstRTSPSessionPool *old;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  if (pool)
    g_object_ref (pool);

  GST_RTSP_SERVER_LOCK (server);
  old = priv->session_pool;
  priv->session_pool = pool;
  GST_RTSP_SERVER_UNLOCK (server);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_server_get_session_pool:
 * @server: a #GstRTSPServer
 *
 * Get the #GstRTSPSessionPool used as the session pool of @server.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPSessionPool used for sessions. g_object_unref() after
 * usage.
 */
GstRTSPSessionPool *
gst_rtsp_server_get_session_pool (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  GstRTSPSessionPool *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  if ((result = priv->session_pool))
    g_object_ref (result);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_mount_points:
 * @server: a #GstRTSPServer
 * @mounts: (transfer none) (nullable): a #GstRTSPMountPoints
 *
 * configure @mounts to be used as the mount points of @server.
 */
void
gst_rtsp_server_set_mount_points (GstRTSPServer * server,
    GstRTSPMountPoints * mounts)
{
  GstRTSPServerPrivate *priv;
  GstRTSPMountPoints *old;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  if (mounts)
    g_object_ref (mounts);

  GST_RTSP_SERVER_LOCK (server);
  old = priv->mount_points;
  priv->mount_points = mounts;
  GST_RTSP_SERVER_UNLOCK (server);

  if (old)
    g_object_unref (old);
}


/**
 * gst_rtsp_server_get_mount_points:
 * @server: a #GstRTSPServer
 *
 * Get the #GstRTSPMountPoints used as the mount points of @server.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPMountPoints of @server. g_object_unref() after
 * usage.
 */
GstRTSPMountPoints *
gst_rtsp_server_get_mount_points (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  GstRTSPMountPoints *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  if ((result = priv->mount_points))
    g_object_ref (result);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_content_length_limit
 * @server: a #GstRTSPServer
 * Configure @server to use the specified Content-Length limit.
 *
 * Define an appropriate request size limit and reject requests exceeding the
 * limit.
 *
 * Since: 1.18
 */
void
gst_rtsp_server_set_content_length_limit (GstRTSPServer * server, guint limit)
{
  GstRTSPServerPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  priv->content_length_limit = limit;
  GST_RTSP_SERVER_UNLOCK (server);
}

/**
 * gst_rtsp_server_get_content_length_limit:
 * @server: a #GstRTSPServer
 *
 * Get the Content-Length limit of @server.
 *
 * Returns: the Content-Length limit.
 *
 * Since: 1.18
 */
guint
gst_rtsp_server_get_content_length_limit (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), G_MAXUINT);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  result = priv->content_length_limit;
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_auth:
 * @server: a #GstRTSPServer
 * @auth: (transfer none) (nullable): a #GstRTSPAuth
 *
 * configure @auth to be used as the authentication manager of @server.
 */
void
gst_rtsp_server_set_auth (GstRTSPServer * server, GstRTSPAuth * auth)
{
  GstRTSPServerPrivate *priv;
  GstRTSPAuth *old;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  if (auth)
    g_object_ref (auth);

  GST_RTSP_SERVER_LOCK (server);
  old = priv->auth;
  priv->auth = auth;
  GST_RTSP_SERVER_UNLOCK (server);

  if (old)
    g_object_unref (old);
}


/**
 * gst_rtsp_server_get_auth:
 * @server: a #GstRTSPServer
 *
 * Get the #GstRTSPAuth used as the authentication manager of @server.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAuth of @server. g_object_unref() after
 * usage.
 */
GstRTSPAuth *
gst_rtsp_server_get_auth (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  GstRTSPAuth *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  if ((result = priv->auth))
    g_object_ref (result);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

/**
 * gst_rtsp_server_set_thread_pool:
 * @server: a #GstRTSPServer
 * @pool: (transfer none) (nullable): a #GstRTSPThreadPool
 *
 * configure @pool to be used as the thread pool of @server.
 */
void
gst_rtsp_server_set_thread_pool (GstRTSPServer * server,
    GstRTSPThreadPool * pool)
{
  GstRTSPServerPrivate *priv;
  GstRTSPThreadPool *old;

  g_return_if_fail (GST_IS_RTSP_SERVER (server));

  priv = server->priv;

  if (pool)
    g_object_ref (pool);

  GST_RTSP_SERVER_LOCK (server);
  old = priv->thread_pool;
  priv->thread_pool = pool;
  GST_RTSP_SERVER_UNLOCK (server);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_server_get_thread_pool:
 * @server: a #GstRTSPServer
 *
 * Get the #GstRTSPThreadPool used as the thread pool of @server.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPThreadPool of @server. g_object_unref() after
 * usage.
 */
GstRTSPThreadPool *
gst_rtsp_server_get_thread_pool (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv;
  GstRTSPThreadPool *result;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  if ((result = priv->thread_pool))
    g_object_ref (result);
  GST_RTSP_SERVER_UNLOCK (server);

  return result;
}

static void
gst_rtsp_server_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPServer *server = GST_RTSP_SERVER (object);

  switch (propid) {
    case PROP_ADDRESS:
      g_value_take_string (value, gst_rtsp_server_get_address (server));
      break;
    case PROP_SERVICE:
      g_value_take_string (value, gst_rtsp_server_get_service (server));
      break;
    case PROP_BOUND_PORT:
      g_value_set_int (value, gst_rtsp_server_get_bound_port (server));
      break;
    case PROP_BACKLOG:
      g_value_set_int (value, gst_rtsp_server_get_backlog (server));
      break;
    case PROP_SESSION_POOL:
      g_value_take_object (value, gst_rtsp_server_get_session_pool (server));
      break;
    case PROP_MOUNT_POINTS:
      g_value_take_object (value, gst_rtsp_server_get_mount_points (server));
      break;
    case PROP_CONTENT_LENGTH_LIMIT:
      g_value_set_uint (value,
          gst_rtsp_server_get_content_length_limit (server));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_server_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPServer *server = GST_RTSP_SERVER (object);

  switch (propid) {
    case PROP_ADDRESS:
      gst_rtsp_server_set_address (server, g_value_get_string (value));
      break;
    case PROP_SERVICE:
      gst_rtsp_server_set_service (server, g_value_get_string (value));
      break;
    case PROP_BACKLOG:
      gst_rtsp_server_set_backlog (server, g_value_get_int (value));
      break;
    case PROP_SESSION_POOL:
      gst_rtsp_server_set_session_pool (server, g_value_get_object (value));
      break;
    case PROP_MOUNT_POINTS:
      gst_rtsp_server_set_mount_points (server, g_value_get_object (value));
      break;
    case PROP_CONTENT_LENGTH_LIMIT:
      gst_rtsp_server_set_content_length_limit (server,
          g_value_get_uint (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_server_create_socket:
 * @server: a #GstRTSPServer
 * @cancellable: (allow-none): a #GCancellable
 * @error: a #GError
 *
 * Create a #GSocket for @server. The socket will listen on the
 * configured service.
 *
 * Returns: (transfer full): the #GSocket for @server or %NULL when an error
 * occurred.
 */
GSocket *
gst_rtsp_server_create_socket (GstRTSPServer * server,
    GCancellable * cancellable, GError ** error)
{
  GstRTSPServerPrivate *priv;
  GSocketConnectable *conn;
  GSocketAddressEnumerator *enumerator;
  GSocket *socket = NULL;
#ifdef USE_SOLINGER
  struct linger linger;
#endif
  GError *sock_error = NULL;
  GError *bind_error = NULL;
  guint16 port;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  GST_RTSP_SERVER_LOCK (server);
  GST_DEBUG_OBJECT (server, "getting address info of %s/%s", priv->address,
      priv->service);

  /* resolve the server IP address */
  port = atoi (priv->service);
  if (port != 0 || !strcmp (priv->service, "0"))
    conn = g_network_address_new (priv->address, port);
  else
    conn = g_network_service_new (priv->service, "tcp", priv->address);

  enumerator = g_socket_connectable_enumerate (conn);
  g_object_unref (conn);

  /* create server socket, we loop through all the addresses until we manage to
   * create a socket and bind. */
  while (TRUE) {
    GSocketAddress *sockaddr;

    sockaddr =
        g_socket_address_enumerator_next (enumerator, cancellable, error);
    if (!sockaddr) {
      if (!*error)
        GST_DEBUG_OBJECT (server, "no more addresses %s",
            *error ? (*error)->message : "");
      else
        GST_DEBUG_OBJECT (server, "failed to retrieve next address %s",
            (*error)->message);
      break;
    }

    /* only keep the first error */
    socket = g_socket_new (g_socket_address_get_family (sockaddr),
        G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP,
        sock_error ? NULL : &sock_error);

    if (socket == NULL) {
      GST_DEBUG_OBJECT (server, "failed to make socket (%s), try next",
          sock_error->message);
      g_object_unref (sockaddr);
      continue;
    }

    if (g_socket_bind (socket, sockaddr, TRUE, bind_error ? NULL : &bind_error)) {
      /* ask what port the socket has been bound to */
      if (port == 0 || !strcmp (priv->service, "0")) {
        GError *addr_error = NULL;

        g_object_unref (sockaddr);
        sockaddr = g_socket_get_local_address (socket, &addr_error);

        if (addr_error != NULL) {
          GST_DEBUG_OBJECT (server,
              "failed to get the local address of a bound socket %s",
              addr_error->message);
          g_clear_error (&addr_error);
          break;
        }
        port =
            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));

        if (port != 0) {
          g_free (priv->service);
          priv->service = g_strdup_printf ("%d", port);
        } else {
          GST_DEBUG_OBJECT (server, "failed to get the port of a bound socket");
        }
      }
      g_object_unref (sockaddr);
      break;
    }

    GST_DEBUG_OBJECT (server, "failed to bind socket (%s), try next",
        bind_error->message);
    g_object_unref (sockaddr);
    g_object_unref (socket);
    socket = NULL;
  }
  g_object_unref (enumerator);

  if (socket == NULL)
    goto no_socket;

  g_clear_error (&sock_error);
  g_clear_error (&bind_error);

  GST_DEBUG_OBJECT (server, "opened sending server socket");

  /* keep connection alive; avoids SIGPIPE during write */
  g_socket_set_keepalive (socket, TRUE);

#if 0
#ifdef USE_SOLINGER
  /* make sure socket is reset 5 seconds after close. This ensure that we can
   * reuse the socket quickly while still having a chance to send data to the
   * client. */
  linger.l_onoff = 1;
  linger.l_linger = 5;
  if (setsockopt (sockfd, SOL_SOCKET, SO_LINGER,
          (void *) &linger, sizeof (linger)) < 0)
    goto linger_failed;
#endif
#endif

  /* set the server socket to nonblocking */
  g_socket_set_blocking (socket, FALSE);

  /* set listen backlog */
  g_socket_set_listen_backlog (socket, priv->backlog);

  if (!g_socket_listen (socket, error))
    goto listen_failed;

  GST_DEBUG_OBJECT (server, "listening on server socket %p with queue of %d",
      socket, priv->backlog);

  GST_RTSP_SERVER_UNLOCK (server);

  return socket;

  /* ERRORS */
no_socket:
  {
    GST_ERROR_OBJECT (server, "failed to create socket");
    goto close_error;
  }
#if 0
#ifdef USE_SOLINGER
linger_failed:
  {
    GST_ERROR_OBJECT (server, "failed to no linger socket: %s",
        g_strerror (errno));
    goto close_error;
  }
#endif
#endif
listen_failed:
  {
    GST_ERROR_OBJECT (server, "failed to listen on socket: %s",
        (*error)->message);
    goto close_error;
  }
close_error:
  {
    if (socket)
      g_object_unref (socket);

    if (sock_error) {
      if (error == NULL)
        g_propagate_error (error, sock_error);
      else
        g_error_free (sock_error);
    }
    if (bind_error) {
      if ((error == NULL) || (*error == NULL))
        g_propagate_error (error, bind_error);
      else
        g_error_free (bind_error);
    }
    GST_RTSP_SERVER_UNLOCK (server);
    return NULL;
  }
}

struct _ClientContext
{
  GstRTSPServer *server;
  GstRTSPThread *thread;
  GstRTSPClient *client;
};

static gboolean
free_client_context (ClientContext * ctx)
{
  GST_DEBUG ("free context %p", ctx);

  GST_RTSP_SERVER_LOCK (ctx->server);
  if (ctx->thread)
    gst_rtsp_thread_stop (ctx->thread);
  GST_RTSP_SERVER_UNLOCK (ctx->server);

  g_object_unref (ctx->client);
  g_object_unref (ctx->server);
  g_free (ctx);

  return G_SOURCE_REMOVE;
}

static void
unmanage_client (GstRTSPClient * client, ClientContext * ctx)
{
  GstRTSPServer *server = ctx->server;
  GstRTSPServerPrivate *priv = server->priv;

  GST_DEBUG_OBJECT (server, "unmanage client %p", client);

  GST_RTSP_SERVER_LOCK (server);
  priv->clients = g_list_remove (priv->clients, ctx);
  priv->clients_cookie++;
  GST_RTSP_SERVER_UNLOCK (server);

  if (ctx->thread) {
    GSource *src;

    src = g_idle_source_new ();
    g_source_set_callback (src, (GSourceFunc) free_client_context, ctx, NULL);
    g_source_attach (src, ctx->thread->context);
    g_source_unref (src);
  } else {
    free_client_context (ctx);
  }
}

/* add the client context to the active list of clients, takes ownership
 * of client */
static void
manage_client (GstRTSPServer * server, GstRTSPClient * client)
{
  ClientContext *cctx;
  GstRTSPServerPrivate *priv = server->priv;
  GMainContext *mainctx = NULL;
  GstRTSPContext ctx = { NULL };

  GST_DEBUG_OBJECT (server, "manage client %p", client);

  g_signal_emit (server, gst_rtsp_server_signals[SIGNAL_CLIENT_CONNECTED], 0,
      client);

  cctx = g_new0 (ClientContext, 1);
  cctx->server = g_object_ref (server);
  cctx->client = client;

  GST_RTSP_SERVER_LOCK (server);

  ctx.server = server;
  ctx.client = client;

  cctx->thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
      GST_RTSP_THREAD_TYPE_CLIENT, &ctx);
  if (cctx->thread)
    mainctx = cctx->thread->context;
  else {
    GSource *source;
    /* find the context to add the watch */
    if ((source = g_main_current_source ()))
      mainctx = g_source_get_context (source);
  }

  g_signal_connect (client, "closed", (GCallback) unmanage_client, cctx);
  priv->clients = g_list_prepend (priv->clients, cctx);
  priv->clients_cookie++;

  gst_rtsp_client_attach (client, mainctx);

  GST_RTSP_SERVER_UNLOCK (server);
}

static GstRTSPClient *
default_create_client (GstRTSPServer * server)
{
  GstRTSPClient *client;
  GstRTSPServerPrivate *priv = server->priv;

  /* a new client connected, create a session to handle the client. */
  client = gst_rtsp_client_new ();

  /* set the session pool that this client should use */
  GST_RTSP_SERVER_LOCK (server);
  gst_rtsp_client_set_session_pool (client, priv->session_pool);
  /* set the mount points that this client should use */
  gst_rtsp_client_set_mount_points (client, priv->mount_points);
  /* Set content-length limit */
  gst_rtsp_client_set_content_length_limit (GST_RTSP_CLIENT (client),
      priv->content_length_limit);
  /* set authentication manager */
  gst_rtsp_client_set_auth (client, priv->auth);
  /* set threadpool */
  gst_rtsp_client_set_thread_pool (client, priv->thread_pool);
  GST_RTSP_SERVER_UNLOCK (server);

  return client;
}

/**
 * gst_rtsp_server_transfer_connection:
 * @server: a #GstRTSPServer
 * @socket: (transfer full): a network socket
 * @ip: the IP address of the remote client
 * @port: the port used by the other end
 * @initial_buffer: (nullable): any initial data that was already read from the socket
 *
 * Take an existing network socket and use it for an RTSP connection. This
 * is used when transferring a socket from an HTTP server which should be used
 * as an RTSP over HTTP tunnel. The @initial_buffer contains any remaining data
 * that the HTTP server read from the socket while parsing the HTTP header.
 *
 * Returns: TRUE if all was ok, FALSE if an error occurred.
 */
gboolean
gst_rtsp_server_transfer_connection (GstRTSPServer * server, GSocket * socket,
    const gchar * ip, gint port, const gchar * initial_buffer)
{
  GstRTSPClient *client = NULL;
  GstRTSPServerClass *klass;
  GstRTSPConnection *conn;
  GstRTSPResult res;

  klass = GST_RTSP_SERVER_GET_CLASS (server);

  if (klass->create_client)
    client = klass->create_client (server);
  if (client == NULL)
    goto client_failed;

  GST_RTSP_CHECK (gst_rtsp_connection_create_from_socket (socket, ip, port,
          initial_buffer, &conn), no_connection);
  g_object_unref (socket);

  /* set connection on the client now */
  gst_rtsp_client_set_connection (client, conn);

  /* manage the client connection */
  manage_client (server, client);

  return TRUE;

  /* ERRORS */
client_failed:
  {
    GST_ERROR_OBJECT (server, "failed to create a client");
    g_object_unref (socket);
    return FALSE;
  }
no_connection:
  {
    gchar *str = gst_rtsp_strresult (res);
    GST_ERROR ("could not create connection from socket %p: %s", socket, str);
    g_free (str);
    g_object_unref (socket);
    g_object_unref (client);
    return FALSE;
  }
}

/**
 * gst_rtsp_server_io_func:
 * @socket: a #GSocket
 * @condition: the condition on @source
 * @server: (transfer none): a #GstRTSPServer
 *
 * A default #GSocketSourceFunc that creates a new #GstRTSPClient to accept and handle a
 * new connection on @socket or @server.
 *
 * Returns: TRUE if the source could be connected, FALSE if an error occurred.
 */
gboolean
gst_rtsp_server_io_func (GSocket * socket, GIOCondition condition,
    GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv = server->priv;
  GstRTSPClient *client = NULL;
  GstRTSPServerClass *klass;
  GstRTSPResult res;
  GstRTSPConnection *conn = NULL;
  GstRTSPContext ctx = { NULL };

  if (condition & G_IO_IN) {
    /* a new client connected. */
    GST_RTSP_CHECK (gst_rtsp_connection_accept (socket, &conn, NULL),
        accept_failed);

    ctx.server = server;
    ctx.conn = conn;
    ctx.auth = priv->auth;
    gst_rtsp_context_push_current (&ctx);

    if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_CONNECT))
      goto connection_refused;

    klass = GST_RTSP_SERVER_GET_CLASS (server);
    /* a new client connected, create a client object to handle the client. */
    if (klass->create_client)
      client = klass->create_client (server);
    if (client == NULL)
      goto client_failed;

    /* set connection on the client now */
    gst_rtsp_client_set_connection (client, conn);

    /* manage the client connection */
    manage_client (server, client);
  } else {
    GST_WARNING_OBJECT (server, "received unknown event %08x", condition);
    goto exit_no_ctx;
  }
exit:
  gst_rtsp_context_pop_current (&ctx);
exit_no_ctx:

  return G_SOURCE_CONTINUE;

  /* ERRORS */
accept_failed:
  {
    gchar *str = gst_rtsp_strresult (res);
    GST_ERROR_OBJECT (server, "Could not accept client on socket %p: %s",
        socket, str);
    g_free (str);
    /* We haven't pushed the context yet, so just return */
    goto exit_no_ctx;
  }
connection_refused:
  {
    GST_ERROR_OBJECT (server, "connection refused");
    gst_rtsp_connection_free (conn);
    goto exit;
  }
client_failed:
  {
    GST_ERROR_OBJECT (server, "failed to create a client");
    gst_rtsp_connection_free (conn);
    goto exit;
  }
}

static void
watch_destroyed (GstRTSPServer * server)
{
  GstRTSPServerPrivate *priv = server->priv;

  GST_DEBUG_OBJECT (server, "source destroyed");

  g_object_unref (priv->socket);
  priv->socket = NULL;
  g_object_unref (server);
}

/**
 * gst_rtsp_server_create_source:
 * @server: a #GstRTSPServer
 * @cancellable: (nullable): a #GCancellable or %NULL.
 * @error: a #GError
 *
 * Create a #GSource for @server. The new source will have a default
 * #GSocketSourceFunc of gst_rtsp_server_io_func().
 *
 * @cancellable if not %NULL can be used to cancel the source, which will cause
 * the source to trigger, reporting the current condition (which is likely 0
 * unless cancellation happened at the same time as a condition change). You can
 * check for this in the callback using g_cancellable_is_cancelled().
 *
 * This takes a reference on @server until @source is destroyed.
 *
 * Returns: (transfer full): the #GSource for @server or %NULL when an error
 * occurred. Free with g_source_unref ()
 */
GSource *
gst_rtsp_server_create_source (GstRTSPServer * server,
    GCancellable * cancellable, GError ** error)
{
  GstRTSPServerPrivate *priv;
  GSocket *socket, *old;
  GSource *source;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  socket = gst_rtsp_server_create_socket (server, NULL, error);
  if (socket == NULL)
    goto no_socket;

  GST_RTSP_SERVER_LOCK (server);
  old = priv->socket;
  priv->socket = g_object_ref (socket);
  GST_RTSP_SERVER_UNLOCK (server);

  if (old)
    g_object_unref (old);

  /* create a watch for reads (new connections) and possible errors */
  source = g_socket_create_source (socket, G_IO_IN |
      G_IO_ERR | G_IO_HUP | G_IO_NVAL, cancellable);
  g_object_unref (socket);

  /* configure the callback */
  g_source_set_callback (source,
      (GSourceFunc) gst_rtsp_server_io_func, g_object_ref (server),
      (GDestroyNotify) watch_destroyed);

  return source;

no_socket:
  {
    GST_ERROR_OBJECT (server, "failed to create socket");
    return NULL;
  }
}

/**
 * gst_rtsp_server_attach:
 * @server: a #GstRTSPServer
 * @context: (nullable): a #GMainContext
 *
 * Attaches @server to @context. When the mainloop for @context is run, the
 * server will be dispatched. When @context is %NULL, the default context will be
 * used).
 *
 * This function should be called when the server properties and urls are fully
 * configured and the server is ready to start.
 *
 * This takes a reference on @server until the source is destroyed. Note that
 * if @context is not the default main context as returned by
 * g_main_context_default() (or %NULL), g_source_remove() cannot be used to
 * destroy the source. In that case it is recommended to use
 * gst_rtsp_server_create_source() and attach it to @context manually.
 *
 * Returns: the ID (greater than 0) for the source within the GMainContext.
 */
guint
gst_rtsp_server_attach (GstRTSPServer * server, GMainContext * context)
{
  guint res;
  GSource *source;
  GError *error = NULL;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), 0);

  source = gst_rtsp_server_create_source (server, NULL, &error);
  if (source == NULL)
    goto no_source;

  res = g_source_attach (source, context);
  g_source_unref (source);

  return res;

  /* ERRORS */
no_source:
  {
    GST_ERROR_OBJECT (server, "failed to create watch: %s", error->message);
    g_error_free (error);
    return 0;
  }
}

/**
 * gst_rtsp_server_client_filter:
 * @server: a #GstRTSPServer
 * @func: (scope call) (nullable): a callback
 * @user_data: user data passed to @func
 *
 * Call @func for each client managed by @server. The result value of @func
 * determines what happens to the client. @func will be called with @server
 * locked so no further actions on @server can be performed from @func.
 *
 * If @func returns #GST_RTSP_FILTER_REMOVE, the client will be removed from
 * @server.
 *
 * If @func returns #GST_RTSP_FILTER_KEEP, the client will remain in @server.
 *
 * If @func returns #GST_RTSP_FILTER_REF, the client will remain in @server but
 * will also be added with an additional ref to the result #GList of this
 * function..
 *
 * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each client.
 *
 * Returns: (element-type GstRTSPClient) (transfer full): a #GList with all
 * clients for which @func returned #GST_RTSP_FILTER_REF. After usage, each
 * element in the #GList should be unreffed before the list is freed.
 */
GList *
gst_rtsp_server_client_filter (GstRTSPServer * server,
    GstRTSPServerClientFilterFunc func, gpointer user_data)
{
  GstRTSPServerPrivate *priv;
  GList *result, *walk, *next;
  GHashTable *visited;
  guint cookie;

  g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);

  priv = server->priv;

  result = NULL;
  if (func)
    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  GST_RTSP_SERVER_LOCK (server);
restart:
  cookie = priv->clients_cookie;
  for (walk = priv->clients; walk; walk = next) {
    ClientContext *cctx = walk->data;
    GstRTSPClient *client = cctx->client;
    GstRTSPFilterResult res;
    gboolean changed;

    next = g_list_next (walk);

    if (func) {
      /* only visit each media once */
      if (g_hash_table_contains (visited, client))
        continue;

      g_hash_table_add (visited, g_object_ref (client));
      GST_RTSP_SERVER_UNLOCK (server);

      res = func (server, client, user_data);

      GST_RTSP_SERVER_LOCK (server);
    } else
      res = GST_RTSP_FILTER_REF;

    changed = (cookie != priv->clients_cookie);

    switch (res) {
      case GST_RTSP_FILTER_REMOVE:
        GST_RTSP_SERVER_UNLOCK (server);

        gst_rtsp_client_close (client);

        GST_RTSP_SERVER_LOCK (server);
        changed |= (cookie != priv->clients_cookie);
        break;
      case GST_RTSP_FILTER_REF:
        result = g_list_prepend (result, g_object_ref (client));
        break;
      case GST_RTSP_FILTER_KEEP:
      default:
        break;
    }
    if (changed)
      goto restart;
  }
  GST_RTSP_SERVER_UNLOCK (server);

  if (func)
    g_hash_table_unref (visited);

  return result;
}
  07070100000058000081A400000000000000000000000169D554BF000006A3000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-server.h  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __GST_RTSP_SERVER_H__
#define __GST_RTSP_SERVER_H__

#include <gst/gst.h>

G_BEGIN_DECLS

#include "rtsp-server-prelude.h"
#include "rtsp-server-object.h"
#include "rtsp-session-pool.h"
#include "rtsp-session.h"
#include "rtsp-media.h"
#include "rtsp-stream.h"
#include "rtsp-stream-transport.h"
#include "rtsp-address-pool.h"
#include "rtsp-thread-pool.h"
#include "rtsp-client.h"
#include "rtsp-context.h"
#include "rtsp-server.h"
#include "rtsp-mount-points.h"
#include "rtsp-media-factory.h"
#include "rtsp-permissions.h"
#include "rtsp-auth.h"
#include "rtsp-token.h"
#include "rtsp-session-media.h"
#include "rtsp-sdp.h"
#include "rtsp-media-factory-uri.h"
#include "rtsp-params.h"

#include "rtsp-onvif-client.h"
#include "rtsp-onvif-media-factory.h"
#include "rtsp-onvif-media.h"
#include "rtsp-onvif-server.h"

G_END_DECLS

#endif /* __GST_RTSP_SERVER_H__ */
 07070100000059000081A400000000000000000000000169D554BF00003913000000000000000000000000000000000000003C00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session-media.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-session-media
 * @short_description: Media managed in a session
 * @see_also: #GstRTSPMedia, #GstRTSPSession
 *
 * The #GstRTSPSessionMedia object manages a #GstRTSPMedia with a given path.
 *
 * With gst_rtsp_session_media_get_transport() and
 * gst_rtsp_session_media_set_transport() the transports of a #GstRTSPStream of
 * the managed #GstRTSPMedia can be retrieved and configured.
 *
 * Use gst_rtsp_session_media_set_state() to control the media state and
 * transports.
 *
 * Last reviewed on 2013-07-16 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-session.h"

struct _GstRTSPSessionMediaPrivate
{
  GMutex lock;
  gchar *path;                  /* unmutable */
  gint path_len;                /* unmutable */
  GstRTSPMedia *media;          /* unmutable */
  GstRTSPState state;           /* protected by lock */
  guint counter;                /* protected by lock */

  GPtrArray *transports;        /* protected by lock */
};

enum
{
  PROP_0,
  PROP_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_session_media_debug);
#define GST_CAT_DEFAULT rtsp_session_media_debug

static void gst_rtsp_session_media_finalize (GObject * obj);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPSessionMedia, gst_rtsp_session_media,
    G_TYPE_OBJECT);

static void
gst_rtsp_session_media_class_init (GstRTSPSessionMediaClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gst_rtsp_session_media_finalize;

  GST_DEBUG_CATEGORY_INIT (rtsp_session_media_debug, "rtspsessionmedia", 0,
      "GstRTSPSessionMedia");
}

static void
gst_rtsp_session_media_init (GstRTSPSessionMedia * media)
{
  GstRTSPSessionMediaPrivate *priv;

  media->priv = priv = gst_rtsp_session_media_get_instance_private (media);

  g_mutex_init (&priv->lock);
  priv->state = GST_RTSP_STATE_INIT;
}

static void
gst_rtsp_session_media_finalize (GObject * obj)
{
  GstRTSPSessionMedia *media;
  GstRTSPSessionMediaPrivate *priv;

  media = GST_RTSP_SESSION_MEDIA (obj);
  priv = media->priv;

  GST_INFO ("free session media %p", media);

  gst_rtsp_session_media_set_state (media, GST_STATE_NULL);

  gst_rtsp_media_unprepare (priv->media);

  g_ptr_array_unref (priv->transports);

  g_free (priv->path);
  g_object_unref (priv->media);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_session_media_parent_class)->finalize (obj);
}

static void
free_session_media (gpointer data)
{
  if (data)
    g_object_unref (data);
}

/**
 * gst_rtsp_session_media_new:
 * @path: the path
 * @media: (transfer full): the #GstRTSPMedia
 *
 * Create a new #GstRTSPSessionMedia that manages the streams
 * in @media for @path. @media should be prepared.
 *
 * Ownership is taken of @media.
 *
 * Returns: (transfer full): a new #GstRTSPSessionMedia.
 */
GstRTSPSessionMedia *
gst_rtsp_session_media_new (const gchar * path, GstRTSPMedia * media)
{
  GstRTSPSessionMediaPrivate *priv;
  GstRTSPSessionMedia *result;
  guint n_streams;
  GstRTSPMediaStatus status GST_UNUSED_CHECKS;

  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

#ifndef G_DISABLE_CHECKS
  status = gst_rtsp_media_get_status (media);
  g_return_val_if_fail (status == GST_RTSP_MEDIA_STATUS_PREPARED || status ==
      GST_RTSP_MEDIA_STATUS_SUSPENDED, NULL);
#endif

  result = g_object_new (GST_TYPE_RTSP_SESSION_MEDIA, NULL);
  priv = result->priv;

  priv->path = g_strdup (path);
  priv->path_len = strlen (path);
  priv->media = media;

  /* prealloc the streams now, filled with NULL */
  n_streams = gst_rtsp_media_n_streams (media);
  priv->transports = g_ptr_array_new_full (n_streams, free_session_media);
  g_ptr_array_set_size (priv->transports, n_streams);

  return result;
}

/**
 * gst_rtsp_session_media_matches:
 * @media: a #GstRTSPSessionMedia
 * @path: a path
 * @matched: (out): the amount of matched characters of @path
 *
 * Check if the path of @media matches @path. It @path matches, the amount of
 * matched characters is returned in @matched.
 *
 * Returns: %TRUE when @path matches the path of @media.
 */
gboolean
gst_rtsp_session_media_matches (GstRTSPSessionMedia * media,
    const gchar * path, gint * matched)
{
  GstRTSPSessionMediaPrivate *priv;
  gint len;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), FALSE);
  g_return_val_if_fail (path != NULL, FALSE);
  g_return_val_if_fail (matched != NULL, FALSE);

  priv = media->priv;
  len = strlen (path);

  /* path needs to be smaller than the media path */
  if (len < priv->path_len)
    return FALSE;

  /* special case when "/" is the entire path */
  if (priv->path_len == 1 && priv->path[0] == '/' && path[0] == '/') {
    *matched = 1;
    return TRUE;
  }

  /* if media path is larger, it there should be a / following the path */
  if (len > priv->path_len && path[priv->path_len] != '/')
    return FALSE;

  *matched = priv->path_len;

  return strncmp (path, priv->path, priv->path_len) == 0;
}

/**
 * gst_rtsp_session_media_get_media:
 * @media: a #GstRTSPSessionMedia
 *
 * Get the #GstRTSPMedia that was used when constructing @media
 *
 * Returns: (transfer none) (nullable): the #GstRTSPMedia of @media.
 * Remains valid as long as @media is valid.
 */
GstRTSPMedia *
gst_rtsp_session_media_get_media (GstRTSPSessionMedia * media)
{
  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), NULL);

  return media->priv->media;
}

/**
 * gst_rtsp_session_media_get_base_time:
 * @media: a #GstRTSPSessionMedia
 *
 * Get the base_time of the #GstRTSPMedia in @media
 *
 * Returns: the base_time of the media.
 */
GstClockTime
gst_rtsp_session_media_get_base_time (GstRTSPSessionMedia * media)
{
  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), GST_CLOCK_TIME_NONE);

  return gst_rtsp_media_get_base_time (media->priv->media);
}

/**
 * gst_rtsp_session_media_get_rtpinfo:
 * @media: a #GstRTSPSessionMedia
 *
 * Retrieve the RTP-Info header string for all streams in @media
 * with configured transports.
 *
 * Returns: (transfer full) (nullable): The RTP-Info as a string or
 * %NULL when no RTP-Info could be generated, g_free() after usage.
 */
gchar *
gst_rtsp_session_media_get_rtpinfo (GstRTSPSessionMedia * media)
{
  GstRTSPSessionMediaPrivate *priv;
  GString *rtpinfo = NULL;
  GstRTSPStreamTransport *transport;
  GstRTSPStream *stream;
  guint i, n_streams;
  GstClockTime earliest = GST_CLOCK_TIME_NONE;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), NULL);

  priv = media->priv;
  g_mutex_lock (&priv->lock);

  if (gst_rtsp_media_get_status (priv->media) != GST_RTSP_MEDIA_STATUS_PREPARED)
    goto not_prepared;

  n_streams = priv->transports->len;

  /* first step, take lowest running-time from all streams */
  GST_LOG_OBJECT (media, "determining start time among %d transports",
      n_streams);

  for (i = 0; i < n_streams; i++) {
    GstClockTime running_time;

    transport = g_ptr_array_index (priv->transports, i);
    if (transport == NULL) {
      GST_DEBUG_OBJECT (media, "ignoring unconfigured transport %d", i);
      continue;
    }

    stream = gst_rtsp_stream_transport_get_stream (transport);
    if (!gst_rtsp_stream_is_sender (stream))
      continue;
    if (!gst_rtsp_stream_get_rtpinfo (stream, NULL, NULL, NULL, &running_time))
      continue;

    GST_LOG_OBJECT (media, "running time of %d stream: %" GST_TIME_FORMAT, i,
        GST_TIME_ARGS (running_time));

    if (!GST_CLOCK_TIME_IS_VALID (earliest)) {
      earliest = running_time;
    } else {
      earliest = MIN (earliest, running_time);
    }
  }

  GST_LOG_OBJECT (media, "media start time: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (earliest));

  /* next step, scale all rtptime of all streams to lowest running-time */
  GST_LOG_OBJECT (media, "collecting RTP info for %d transports", n_streams);

  for (i = 0; i < n_streams; i++) {
    gchar *stream_rtpinfo;

    transport = g_ptr_array_index (priv->transports, i);
    if (transport == NULL) {
      GST_DEBUG_OBJECT (media, "ignoring unconfigured transport %d", i);
      continue;
    }

    stream_rtpinfo =
        gst_rtsp_stream_transport_get_rtpinfo (transport, earliest);
    if (stream_rtpinfo == NULL) {
      GST_DEBUG_OBJECT (media, "ignoring unknown RTPInfo %d", i);
      continue;
    }

    if (rtpinfo == NULL)
      rtpinfo = g_string_new ("");
    else
      g_string_append (rtpinfo, ", ");

    g_string_append (rtpinfo, stream_rtpinfo);
    g_free (stream_rtpinfo);
  }
  g_mutex_unlock (&priv->lock);

  if (rtpinfo == NULL) {
    GST_WARNING_OBJECT (media, "RTP info is empty");
    return NULL;
  }
  return g_string_free (rtpinfo, FALSE);

  /* ERRORS */
not_prepared:
  {
    g_mutex_unlock (&priv->lock);
    GST_ERROR_OBJECT (media, "media was not prepared");
    return NULL;
  }
}

/**
 * gst_rtsp_session_media_set_transport:
 * @media: a #GstRTSPSessionMedia
 * @stream: a #GstRTSPStream
 * @tr: (transfer full): a #GstRTSPTransport
 *
 * Configure the transport for @stream to @tr in @media.
 *
 * Returns: (transfer none): the new or updated #GstRTSPStreamTransport for @stream.
 */
GstRTSPStreamTransport *
gst_rtsp_session_media_set_transport (GstRTSPSessionMedia * media,
    GstRTSPStream * stream, GstRTSPTransport * tr)
{
  GstRTSPSessionMediaPrivate *priv;
  GstRTSPStreamTransport *result;
  guint idx;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), NULL);
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (tr != NULL, NULL);
  priv = media->priv;
  idx = gst_rtsp_stream_get_index (stream);
  g_return_val_if_fail (idx < priv->transports->len, NULL);

  g_mutex_lock (&priv->lock);
  result = g_ptr_array_index (priv->transports, idx);
  if (result == NULL) {
    result = gst_rtsp_stream_transport_new (stream, tr);
    g_ptr_array_index (priv->transports, idx) = result;
    g_mutex_unlock (&priv->lock);
  } else {
    gst_rtsp_stream_transport_set_transport (result, tr);
    g_mutex_unlock (&priv->lock);
  }

  return result;
}

/**
 * gst_rtsp_session_media_get_transport:
 * @media: a #GstRTSPSessionMedia
 * @idx: the stream index
 *
 * Get a previously created #GstRTSPStreamTransport for the stream at @idx.
 *
 * Returns: (transfer none) (nullable): a #GstRTSPStreamTransport that is
 * valid until the session of @media is unreffed.
 */
GstRTSPStreamTransport *
gst_rtsp_session_media_get_transport (GstRTSPSessionMedia * media, guint idx)
{
  GstRTSPSessionMediaPrivate *priv;
  GstRTSPStreamTransport *result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), NULL);
  priv = media->priv;
  g_return_val_if_fail (idx < priv->transports->len, NULL);

  g_mutex_lock (&priv->lock);
  result = g_ptr_array_index (priv->transports, idx);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_session_media_get_transports:
 * @media: a #GstRTSPSessionMedia
 *
 * Get a list of all available #GstRTSPStreamTransport in this session.
 *
 * Returns: (transfer full) (element-type GstRTSPStreamTransport): a
 * list of #GstRTSPStreamTransport, g_ptr_array_unref () after usage.
 *
 * Since: 1.14
 */
GPtrArray *
gst_rtsp_session_media_get_transports (GstRTSPSessionMedia * media)
{
  GstRTSPSessionMediaPrivate *priv;
  GPtrArray *result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), NULL);
  priv = media->priv;

  g_mutex_lock (&priv->lock);
  result = g_ptr_array_ref (priv->transports);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_session_media_alloc_channels:
 * @media: a #GstRTSPSessionMedia
 * @range: (out): a #GstRTSPRange
 *
 * Fill @range with the next available min and max channels for
 * interleaved transport.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_session_media_alloc_channels (GstRTSPSessionMedia * media,
    GstRTSPRange * range)
{
  GstRTSPSessionMediaPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  range->min = priv->counter++;
  range->max = priv->counter++;
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_session_media_set_state:
 * @media: a #GstRTSPSessionMedia
 * @state: the new state
 *
 * Tell the media object @media to change to @state.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_session_media_set_state (GstRTSPSessionMedia * media, GstState state)
{
  GstRTSPSessionMediaPrivate *priv;
  gboolean ret;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media), FALSE);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  ret = gst_rtsp_media_set_state (priv->media, state, priv->transports);
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_session_media_set_rtsp_state:
 * @media: a #GstRTSPSessionMedia
 * @state: a #GstRTSPState
 *
 * Set the RTSP state of @media to @state.
 */
void
gst_rtsp_session_media_set_rtsp_state (GstRTSPSessionMedia * media,
    GstRTSPState state)
{
  GstRTSPSessionMediaPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SESSION_MEDIA (media));

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  priv->state = state;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_session_media_get_rtsp_state:
 * @media: a #GstRTSPSessionMedia
 *
 * Get the current RTSP state of @media.
 *
 * Returns: the current RTSP state of @media.
 */
GstRTSPState
gst_rtsp_session_media_get_rtsp_state (GstRTSPSessionMedia * media)
{
  GstRTSPSessionMediaPrivate *priv;
  GstRTSPState ret;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_MEDIA (media),
      GST_RTSP_STATE_INVALID);

  priv = media->priv;

  g_mutex_lock (&priv->lock);
  ret = priv->state;
  g_mutex_unlock (&priv->lock);

  return ret;
}
 0707010000005A000081A400000000000000000000000169D554BF0000137A000000000000000000000000000000000000003C00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session-media.h   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp/gstrtsptransport.h>

#ifndef __GST_RTSP_SESSION_MEDIA_H__
#define __GST_RTSP_SESSION_MEDIA_H__

#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

#define GST_TYPE_RTSP_SESSION_MEDIA              (gst_rtsp_session_media_get_type ())
#define GST_IS_RTSP_SESSION_MEDIA(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SESSION_MEDIA))
#define GST_IS_RTSP_SESSION_MEDIA_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SESSION_MEDIA))
#define GST_RTSP_SESSION_MEDIA_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SESSION_MEDIA, GstRTSPSessionMediaClass))
#define GST_RTSP_SESSION_MEDIA(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SESSION_MEDIA, GstRTSPSessionMedia))
#define GST_RTSP_SESSION_MEDIA_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SESSION_MEDIA, GstRTSPSessionMediaClass))
#define GST_RTSP_SESSION_MEDIA_CAST(obj)         ((GstRTSPSessionMedia*)(obj))
#define GST_RTSP_SESSION_MEDIA_CLASS_CAST(klass) ((GstRTSPSessionMediaClass*)(klass))

typedef struct _GstRTSPSessionMedia GstRTSPSessionMedia;
typedef struct _GstRTSPSessionMediaClass GstRTSPSessionMediaClass;
typedef struct _GstRTSPSessionMediaPrivate GstRTSPSessionMediaPrivate;

/**
 * GstRTSPSessionMedia:
 *
 * State of a client session regarding a specific media identified by path.
 */
struct _GstRTSPSessionMedia
{
  GObject  parent;

  /*< private >*/
  GstRTSPSessionMediaPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

struct _GstRTSPSessionMediaClass
{
  GObjectClass  parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                    gst_rtsp_session_media_get_type       (void);

GST_RTSP_SERVER_API
GstRTSPSessionMedia *    gst_rtsp_session_media_new            (const gchar *path,
                                                                GstRTSPMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_session_media_matches        (GstRTSPSessionMedia *media,
                                                                const gchar *path,
                                                                gint * matched);

GST_RTSP_SERVER_API
GstRTSPMedia *           gst_rtsp_session_media_get_media      (GstRTSPSessionMedia *media);

GST_RTSP_SERVER_API
GstClockTime             gst_rtsp_session_media_get_base_time  (GstRTSPSessionMedia *media);
/* control media */

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_session_media_set_state      (GstRTSPSessionMedia *media,
                                                                GstState state);

GST_RTSP_SERVER_API
void                     gst_rtsp_session_media_set_rtsp_state (GstRTSPSessionMedia *media,
                                                                GstRTSPState state);

GST_RTSP_SERVER_API
GstRTSPState             gst_rtsp_session_media_get_rtsp_state (GstRTSPSessionMedia *media);

/* get stream transport config */

GST_RTSP_SERVER_API
GstRTSPStreamTransport * gst_rtsp_session_media_set_transport  (GstRTSPSessionMedia *media,
                                                                GstRTSPStream *stream,
                                                                GstRTSPTransport *tr);

GST_RTSP_SERVER_API
GstRTSPStreamTransport * gst_rtsp_session_media_get_transport  (GstRTSPSessionMedia *media,
                                                                guint idx);

GST_RTSP_SERVER_API
GPtrArray *              gst_rtsp_session_media_get_transports (GstRTSPSessionMedia *media) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_session_media_alloc_channels (GstRTSPSessionMedia *media,
                                                                GstRTSPRange *range);

GST_RTSP_SERVER_API
gchar *                  gst_rtsp_session_media_get_rtpinfo    (GstRTSPSessionMedia * media) G_GNUC_WARN_UNUSED_RESULT;

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPSessionMedia, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_SESSION_MEDIA_H__ */
  0707010000005B000081A400000000000000000000000169D554BF00004EE9000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session-pool.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-session-pool
 * @short_description: An object for managing sessions
 * @see_also: #GstRTSPSession
 *
 * The #GstRTSPSessionPool object manages a list of #GstRTSPSession objects.
 *
 * The maximum number of sessions can be configured with
 * gst_rtsp_session_pool_set_max_sessions(). The current number of sessions can
 * be retrieved with gst_rtsp_session_pool_get_n_sessions().
 *
 * Use gst_rtsp_session_pool_create() to create a new #GstRTSPSession object.
 * The session object can be found again with its id and
 * gst_rtsp_session_pool_find().
 *
 * All sessions can be iterated with gst_rtsp_session_pool_filter().
 *
 * Run gst_rtsp_session_pool_cleanup() periodically to remove timed out sessions
 * or use gst_rtsp_session_pool_create_watch() to be notified when session
 * cleanup should be performed.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "rtsp-session-pool.h"

struct _GstRTSPSessionPoolPrivate
{
  GMutex lock;                  /* protects everything in this struct */
  guint max_sessions;
  GHashTable *sessions;
  guint sessions_cookie;
};

#define DEFAULT_MAX_SESSIONS 0

enum
{
  PROP_0,
  PROP_MAX_SESSIONS,
  PROP_LAST
};

static const gchar session_id_charset[] =
    { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
  'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
  'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
  'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', '-', '_', '.', '+'  /* '$' Live555 in VLC strips off $ chars */
};

enum
{
  SIGNAL_SESSION_REMOVED,
  SIGNAL_LAST
};

static guint gst_rtsp_session_pool_signals[SIGNAL_LAST] = { 0 };

GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
#define GST_CAT_DEFAULT rtsp_session_debug

static void gst_rtsp_session_pool_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_session_pool_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_session_pool_finalize (GObject * object);

static gchar *create_session_id (GstRTSPSessionPool * pool);
static GstRTSPSession *create_session (GstRTSPSessionPool * pool,
    const gchar * id);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPSessionPool, gst_rtsp_session_pool,
    G_TYPE_OBJECT);

static void
gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_session_pool_get_property;
  gobject_class->set_property = gst_rtsp_session_pool_set_property;
  gobject_class->finalize = gst_rtsp_session_pool_finalize;

  g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
      g_param_spec_uint ("max-sessions", "Max Sessions",
          "the maximum amount of sessions (0 = unlimited)",
          0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED] =
      g_signal_new ("session-removed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPSessionPoolClass,
          session_removed), NULL, NULL, NULL, G_TYPE_NONE, 1,
      GST_TYPE_RTSP_SESSION);

  klass->create_session_id = create_session_id;
  klass->create_session = create_session;

  GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
      "GstRTSPSessionPool");
}

static void
gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
{
  GstRTSPSessionPoolPrivate *priv;

  pool->priv = priv = gst_rtsp_session_pool_get_instance_private (pool);

  g_mutex_init (&priv->lock);
  priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
      NULL, g_object_unref);
  priv->max_sessions = DEFAULT_MAX_SESSIONS;
}

static GstRTSPFilterResult
remove_sessions_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
    gpointer user_data)
{
  return GST_RTSP_FILTER_REMOVE;
}

static void
gst_rtsp_session_pool_finalize (GObject * object)
{
  GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
  GstRTSPSessionPoolPrivate *priv = pool->priv;
  GList *sessions G_GNUC_UNUSED;

  sessions = gst_rtsp_session_pool_filter (pool, remove_sessions_func, NULL);
  g_assert (sessions == NULL);
  g_hash_table_unref (priv->sessions);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
}

static void
gst_rtsp_session_pool_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);

  switch (propid) {
    case PROP_MAX_SESSIONS:
      g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
      break;
  }
}

static void
gst_rtsp_session_pool_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);

  switch (propid) {
    case PROP_MAX_SESSIONS:
      gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
      break;
  }
}

/**
 * gst_rtsp_session_pool_new:
 *
 * Create a new #GstRTSPSessionPool instance.
 *
 * Returns: (transfer full): A new #GstRTSPSessionPool. g_object_unref() after
 * usage.
 */
GstRTSPSessionPool *
gst_rtsp_session_pool_new (void)
{
  GstRTSPSessionPool *result;

  result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);

  return result;
}

/**
 * gst_rtsp_session_pool_set_max_sessions:
 * @pool: a #GstRTSPSessionPool
 * @max: the maximum number of sessions
 *
 * Configure the maximum allowed number of sessions in @pool to @max.
 * A value of 0 means an unlimited amount of sessions.
 */
void
gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
{
  GstRTSPSessionPoolPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  priv->max_sessions = max;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_session_pool_get_max_sessions:
 * @pool: a #GstRTSPSessionPool
 *
 * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
 * amount of sessions.
 *
 * Returns: the maximum allowed number of sessions.
 */
guint
gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
{
  GstRTSPSessionPoolPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  result = priv->max_sessions;
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_session_pool_get_n_sessions:
 * @pool: a #GstRTSPSessionPool
 *
 * Get the amount of active sessions in @pool.
 *
 * Returns: the amount of active sessions in @pool.
 */
guint
gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
{
  GstRTSPSessionPoolPrivate *priv;
  guint result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  result = g_hash_table_size (priv->sessions);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_session_pool_find:
 * @pool: the pool to search
 * @sessionid: the session id
 *
 * Find the session with @sessionid in @pool. The access time of the session
 * will be updated with gst_rtsp_session_touch().
 *
 * Returns: (transfer full) (nullable): the #GstRTSPSession with @sessionid
 * or %NULL when the session did not exist. g_object_unref() after usage.
 */
GstRTSPSession *
gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
{
  GstRTSPSessionPoolPrivate *priv;
  GstRTSPSession *result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
  g_return_val_if_fail (sessionid != NULL, NULL);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  result = g_hash_table_lookup (priv->sessions, sessionid);
  if (result) {
    g_object_ref (result);
    gst_rtsp_session_touch (result);
  }
  g_mutex_unlock (&priv->lock);

  return result;
}

static gchar *
create_session_id (GstRTSPSessionPool * pool)
{
  gchar id[16];
  gint i;

  for (i = 0; i < 16; i++) {
    id[i] =
        session_id_charset[g_random_int_range (0,
            G_N_ELEMENTS (session_id_charset))];
  }

  return g_strndup (id, 16);
}

static GstRTSPSession *
create_session (GstRTSPSessionPool * pool, const gchar * id)
{
  return gst_rtsp_session_new (id);
}

/**
 * gst_rtsp_session_pool_create:
 * @pool: a #GstRTSPSessionPool
 *
 * Create a new #GstRTSPSession object in @pool.
 *
 * Returns: (transfer full) (nullable): a new #GstRTSPSession.
 */
GstRTSPSession *
gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
{
  GstRTSPSessionPoolPrivate *priv;
  GstRTSPSession *result = NULL;
  GstRTSPSessionPoolClass *klass;
  gchar *id = NULL;
  guint retry;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);

  priv = pool->priv;

  klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);

  retry = 0;
  do {
    /* start by creating a new random session id, we assume that this is random
     * enough to not cause a collision, which we will check later  */
    if (klass->create_session_id)
      id = klass->create_session_id (pool);
    else
      goto no_function;

    if (id == NULL)
      goto no_session;

    g_mutex_lock (&priv->lock);
    /* check session limit */
    if (priv->max_sessions > 0) {
      if (g_hash_table_size (priv->sessions) >= priv->max_sessions)
        goto too_many_sessions;
    }
    /* check if the sessionid existed */
    result = g_hash_table_lookup (priv->sessions, id);
    if (result) {
      /* found, retry with a different session id */
      result = NULL;
      retry++;
      if (retry > 100)
        goto collision;
    } else {
      /* not found, create session and insert it in the pool */
      if (klass->create_session)
        result = klass->create_session (pool, id);
      if (result == NULL)
        goto too_many_sessions;
      /* take additional ref for the pool */
      g_object_ref (result);
      g_hash_table_insert (priv->sessions,
          (gchar *) gst_rtsp_session_get_sessionid (result), result);
      priv->sessions_cookie++;
    }
    g_mutex_unlock (&priv->lock);

    g_free (id);
  } while (result == NULL);

  return result;

  /* ERRORS */
no_function:
  {
    GST_WARNING ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
    return NULL;
  }
no_session:
  {
    GST_WARNING ("can't create session id with GstRTSPSessionPool %p", pool);
    return NULL;
  }
collision:
  {
    GST_WARNING ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
    g_mutex_unlock (&priv->lock);
    g_free (id);
    return NULL;
  }
too_many_sessions:
  {
    GST_WARNING ("session pool reached max sessions of %d", priv->max_sessions);
    g_mutex_unlock (&priv->lock);
    g_free (id);
    return NULL;
  }
}

/**
 * gst_rtsp_session_pool_remove:
 * @pool: a #GstRTSPSessionPool
 * @sess: (transfer none): a #GstRTSPSession
 *
 * Remove @sess from @pool, releasing the ref that the pool has on @sess.
 *
 * Returns: %TRUE if the session was found and removed.
 */
gboolean
gst_rtsp_session_pool_remove (GstRTSPSessionPool * pool, GstRTSPSession * sess)
{
  GstRTSPSessionPoolPrivate *priv;
  gboolean found;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  g_object_ref (sess);
  found =
      g_hash_table_remove (priv->sessions,
      gst_rtsp_session_get_sessionid (sess));
  if (found)
    priv->sessions_cookie++;
  g_mutex_unlock (&priv->lock);

  if (found)
    g_signal_emit (pool, gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED],
        0, sess);

  g_object_unref (sess);

  return found;
}

typedef struct
{
  gint64 now_monotonic_time;
  GstRTSPSessionPool *pool;
  GList *removed;
} CleanupData;

static gboolean
cleanup_func (gchar * sessionid, GstRTSPSession * sess, CleanupData * data)
{
  gboolean expired;

  expired = gst_rtsp_session_is_expired_usec (sess, data->now_monotonic_time);

  if (expired) {
    GST_DEBUG ("session expired");
    data->removed = g_list_prepend (data->removed, g_object_ref (sess));
  }

  return expired;
}

/**
 * gst_rtsp_session_pool_cleanup:
 * @pool: a #GstRTSPSessionPool
 *
 * Inspect all the sessions in @pool and remove the sessions that are inactive
 * for more than their timeout.
 *
 * Returns: the amount of sessions that got removed.
 */
guint
gst_rtsp_session_pool_cleanup (GstRTSPSessionPool * pool)
{
  GstRTSPSessionPoolPrivate *priv;
  guint result;
  CleanupData data;
  GList *walk;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);

  priv = pool->priv;

  data.now_monotonic_time = g_get_monotonic_time ();

  data.pool = pool;
  data.removed = NULL;

  g_mutex_lock (&priv->lock);
  result =
      g_hash_table_foreach_remove (priv->sessions, (GHRFunc) cleanup_func,
      &data);
  if (result > 0)
    priv->sessions_cookie++;
  g_mutex_unlock (&priv->lock);

  for (walk = data.removed; walk; walk = walk->next) {
    GstRTSPSession *sess = walk->data;

    g_signal_emit (pool,
        gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0, sess);

    g_object_unref (sess);
  }
  g_list_free (data.removed);

  return result;
}

/**
 * gst_rtsp_session_pool_filter:
 * @pool: a #GstRTSPSessionPool
 * @func: (scope call) (allow-none) (closure user_data): a callback
 * @user_data: user data passed to @func
 *
 * Call @func for each session in @pool. The result value of @func determines
 * what happens to the session. @func will be called with the session pool
 * locked so no further actions on @pool can be performed from @func.
 *
 * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be set to the
 * expired state and removed from @pool.
 *
 * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
 *
 * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @pool but
 * will also be added with an additional ref to the result GList of this
 * function..
 *
 * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all sessions.
 *
 * Returns: (element-type GstRTSPSession) (transfer full): a GList with all
 * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
 * element in the GList should be unreffed before the list is freed.
 */
GList *
gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
    GstRTSPSessionPoolFilterFunc func, gpointer user_data)
{
  GstRTSPSessionPoolPrivate *priv;
  GHashTableIter iter;
  gpointer key, value;
  GList *result;
  GHashTable *visited;
  guint cookie;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);

  priv = pool->priv;

  result = NULL;
  if (func)
    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  g_mutex_lock (&priv->lock);
restart:
  g_hash_table_iter_init (&iter, priv->sessions);
  cookie = priv->sessions_cookie;
  while (g_hash_table_iter_next (&iter, &key, &value)) {
    GstRTSPSession *session = value;
    GstRTSPFilterResult res;
    gboolean changed;

    if (func) {
      /* only visit each session once */
      if (g_hash_table_contains (visited, session))
        continue;

      g_hash_table_add (visited, g_object_ref (session));
      g_mutex_unlock (&priv->lock);

      res = func (pool, session, user_data);

      g_mutex_lock (&priv->lock);
    } else
      res = GST_RTSP_FILTER_REF;

    changed = (cookie != priv->sessions_cookie);

    switch (res) {
      case GST_RTSP_FILTER_REMOVE:
      {
        gboolean removed = TRUE;

        if (changed)
          /* something changed, check if we still have the session */
          removed = g_hash_table_remove (priv->sessions, key);
        else
          g_hash_table_iter_remove (&iter);

        if (removed) {
          /* if we managed to remove the session, update the cookie and
           * signal */
          cookie = ++priv->sessions_cookie;
          g_mutex_unlock (&priv->lock);

          g_signal_emit (pool,
              gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0,
              session);

          g_mutex_lock (&priv->lock);
          /* cookie could have changed again, make sure we restart */
          changed |= (cookie != priv->sessions_cookie);
        }
        break;
      }
      case GST_RTSP_FILTER_REF:
        /* keep ref */
        result = g_list_prepend (result, g_object_ref (session));
        break;
      case GST_RTSP_FILTER_KEEP:
      default:
        break;
    }
    if (changed)
      goto restart;
  }
  g_mutex_unlock (&priv->lock);

  if (func)
    g_hash_table_unref (visited);

  return result;
}

typedef struct
{
  GSource source;
  GstRTSPSessionPool *pool;
  gint timeout;
} GstPoolSource;

static void
collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
{
  gint timeout;
  gint64 now_monotonic_time;

  now_monotonic_time = g_get_monotonic_time ();

  timeout = gst_rtsp_session_next_timeout_usec (sess, now_monotonic_time);

  GST_INFO ("%p: next timeout: %d", sess, timeout);
  if (psrc->timeout == -1 || timeout < psrc->timeout)
    psrc->timeout = timeout;
}

static gboolean
gst_pool_source_prepare (GSource * source, gint * timeout)
{
  GstRTSPSessionPoolPrivate *priv;
  GstPoolSource *psrc;
  gboolean result;

  psrc = (GstPoolSource *) source;
  psrc->timeout = -1;
  priv = psrc->pool->priv;

  g_mutex_lock (&priv->lock);
  g_hash_table_foreach (priv->sessions, (GHFunc) collect_timeout, psrc);
  g_mutex_unlock (&priv->lock);

  if (timeout)
    *timeout = psrc->timeout;

  result = psrc->timeout == 0;

  GST_INFO ("prepare %d, %d", psrc->timeout, result);

  return result;
}

static gboolean
gst_pool_source_check (GSource * source)
{
  GST_INFO ("check");

  return gst_pool_source_prepare (source, NULL);
}

static gboolean
gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
    gpointer user_data)
{
  gboolean res;
  GstPoolSource *psrc = (GstPoolSource *) source;
  GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;

  GST_INFO ("dispatch");

  if (func)
    res = func (psrc->pool, user_data);
  else
    res = FALSE;

  return res;
}

static void
gst_pool_source_finalize (GSource * source)
{
  GstPoolSource *psrc = (GstPoolSource *) source;

  GST_INFO ("finalize %p", psrc);

  g_object_unref (psrc->pool);
  psrc->pool = NULL;
}

static GSourceFuncs gst_pool_source_funcs = {
  gst_pool_source_prepare,
  gst_pool_source_check,
  gst_pool_source_dispatch,
  gst_pool_source_finalize
};

/**
 * gst_rtsp_session_pool_create_watch:
 * @pool: a #GstRTSPSessionPool
 *
 * Create a #GSource that will be dispatched when the session should be cleaned
 * up.
 *
 * Returns: (transfer full): a #GSource
 */
GSource *
gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)
{
  GstPoolSource *source;

  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);

  source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
      sizeof (GstPoolSource));
  source->pool = g_object_ref (pool);

  return (GSource *) source;
}
   0707010000005C000081A400000000000000000000000169D554BF0000197C000000000000000000000000000000000000003B00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session-pool.h    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#ifndef __GST_RTSP_SESSION_POOL_H__
#define __GST_RTSP_SESSION_POOL_H__

#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

typedef struct _GstRTSPSessionPool GstRTSPSessionPool;
typedef struct _GstRTSPSessionPoolClass GstRTSPSessionPoolClass;
typedef struct _GstRTSPSessionPoolPrivate GstRTSPSessionPoolPrivate;

#include "rtsp-session.h"

#define GST_TYPE_RTSP_SESSION_POOL              (gst_rtsp_session_pool_get_type ())
#define GST_IS_RTSP_SESSION_POOL(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SESSION_POOL))
#define GST_IS_RTSP_SESSION_POOL_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SESSION_POOL))
#define GST_RTSP_SESSION_POOL_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolClass))
#define GST_RTSP_SESSION_POOL(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPool))
#define GST_RTSP_SESSION_POOL_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolClass))
#define GST_RTSP_SESSION_POOL_CAST(obj)         ((GstRTSPSessionPool*)(obj))
#define GST_RTSP_SESSION_POOL_CLASS_CAST(klass) ((GstRTSPSessionPoolClass*)(klass))

/**
 * GstRTSPSessionPool:
 *
 * An object that keeps track of the active sessions. This object is usually
 * attached to a #GstRTSPServer object to manage the sessions in that server.
 */
struct _GstRTSPSessionPool {
  GObject       parent;

  /*< private >*/
  GstRTSPSessionPoolPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPSessionPoolClass:
 * @create_session_id: create a new random session id. Subclasses can create
 *    custom session ids and should not check if the session exists.
 * @create_session: make a new session object.
 * @session_removed: a session was removed from the pool
 */
struct _GstRTSPSessionPoolClass {
  GObjectClass  parent_class;

  gchar *          (*create_session_id)   (GstRTSPSessionPool *pool);
  GstRTSPSession * (*create_session)      (GstRTSPSessionPool *pool, const gchar *id);

  /* signals */
  void             (*session_removed)     (GstRTSPSessionPool *pool,
                                           GstRTSPSession *session);

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING_LARGE - 1];
};

/**
 * GstRTSPSessionPoolFunc:
 * @pool: a #GstRTSPSessionPool object
 * @user_data: user data that has been given when registering the handler
 *
 * The function that will be called from the GSource watch on the session pool.
 *
 * The function will be called when the pool must be cleaned up because one or
 * more sessions timed out.
 *
 * Returns: %FALSE if the source should be removed.
 */
typedef gboolean (*GstRTSPSessionPoolFunc)  (GstRTSPSessionPool *pool, gpointer user_data);

/**
 * GstRTSPSessionPoolFilterFunc:
 * @pool: a #GstRTSPSessionPool object
 * @session: a #GstRTSPSession in @pool
 * @user_data: user data that has been given to gst_rtsp_session_pool_filter()
 *
 * This function will be called by the gst_rtsp_session_pool_filter(). An
 * implementation should return a value of #GstRTSPFilterResult.
 *
 * When this function returns #GST_RTSP_FILTER_REMOVE, @session will be removed
 * from @pool.
 *
 * A return value of #GST_RTSP_FILTER_KEEP will leave @session untouched in
 * @pool.
 *
 * A value of GST_RTSP_FILTER_REF will add @session to the result #GList of
 * gst_rtsp_session_pool_filter().
 *
 * Returns: a #GstRTSPFilterResult.
 */
typedef GstRTSPFilterResult (*GstRTSPSessionPoolFilterFunc)  (GstRTSPSessionPool *pool,
                                                              GstRTSPSession *session,
                                                              gpointer user_data);


GST_RTSP_SERVER_API
GType                 gst_rtsp_session_pool_get_type          (void);

/* creating a session pool */

GST_RTSP_SERVER_API
GstRTSPSessionPool *  gst_rtsp_session_pool_new               (void) G_GNUC_WARN_UNUSED_RESULT;

/* counting sessions */

GST_RTSP_SERVER_API
void                  gst_rtsp_session_pool_set_max_sessions  (GstRTSPSessionPool *pool, guint max);

GST_RTSP_SERVER_API
guint                 gst_rtsp_session_pool_get_max_sessions  (GstRTSPSessionPool *pool);

GST_RTSP_SERVER_API
guint                 gst_rtsp_session_pool_get_n_sessions    (GstRTSPSessionPool *pool);

/* managing sessions */

GST_RTSP_SERVER_API
GstRTSPSession *      gst_rtsp_session_pool_create            (GstRTSPSessionPool *pool) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPSession *      gst_rtsp_session_pool_find              (GstRTSPSessionPool *pool,
                                                               const gchar *sessionid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean              gst_rtsp_session_pool_remove            (GstRTSPSessionPool *pool,
                                                               GstRTSPSession *sess);

/* perform session maintenance */

GST_RTSP_SERVER_API
GList *               gst_rtsp_session_pool_filter            (GstRTSPSessionPool *pool,
                                                               GstRTSPSessionPoolFilterFunc func,
                                                               gpointer user_data) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
guint                 gst_rtsp_session_pool_cleanup           (GstRTSPSessionPool *pool);

GST_RTSP_SERVER_API
GSource *             gst_rtsp_session_pool_create_watch      (GstRTSPSessionPool *pool) G_GNUC_WARN_UNUSED_RESULT;

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPSessionPool, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_SESSION_POOL_H__ */
0707010000005D000081A400000000000000000000000169D554BF00005B0F000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-session
 * @short_description: An object to manage media
 * @see_also: #GstRTSPSessionPool, #GstRTSPSessionMedia, #GstRTSPMedia
 *
 * The #GstRTSPSession is identified by an id, unique in the
 * #GstRTSPSessionPool that created the session and manages media and its
 * configuration.
 *
 * A #GstRTSPSession has a timeout that can be retrieved with
 * gst_rtsp_session_get_timeout(). You can check if the sessions is expired with
 * gst_rtsp_session_is_expired(). gst_rtsp_session_touch() will reset the
 * expiration counter of the session.
 *
 * When a client configures a media with SETUP, a session will be created to
 * keep track of the configuration of that media. With
 * gst_rtsp_session_manage_media(), the media is added to the managed media
 * in the session. With gst_rtsp_session_release_media() the media can be
 * released again from the session. Managed media is identified in the sessions
 * with a url. Use gst_rtsp_session_get_media() to get the media that matches
 * (part of) the given url.
 *
 * The media in a session can be iterated with gst_rtsp_session_filter().
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-session.h"

struct _GstRTSPSessionPrivate
{
  GMutex lock;                  /* protects everything but sessionid and create_time */
  gchar *sessionid;

  guint timeout;
  gboolean timeout_always_visible;
  GMutex last_access_lock;
  gint64 last_access_monotonic_time;
  gint64 last_access_real_time;
  gint expire_count;

  GList *medias;
  guint medias_cookie;
  guint extra_time_timeout;
};

#undef DEBUG

#define DEFAULT_TIMEOUT	       60
#define NO_TIMEOUT              -1
#define DEFAULT_ALWAYS_VISIBLE  FALSE
#define DEFAULT_EXTRA_TIMEOUT 5

enum
{
  PROP_0,
  PROP_SESSIONID,
  PROP_TIMEOUT,
  PROP_TIMEOUT_ALWAYS_VISIBLE,
  PROP_EXTRA_TIME_TIMEOUT,
  PROP_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
#define GST_CAT_DEFAULT rtsp_session_debug

static void gst_rtsp_session_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_session_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_session_finalize (GObject * obj);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT);

static void
gst_rtsp_session_class_init (GstRTSPSessionClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_session_get_property;
  gobject_class->set_property = gst_rtsp_session_set_property;
  gobject_class->finalize = gst_rtsp_session_finalize;

  g_object_class_install_property (gobject_class, PROP_SESSIONID,
      g_param_spec_string ("sessionid", "Sessionid", "the session id",
          NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
          G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
      g_param_spec_uint ("timeout", "timeout",
          "the timeout of the session (0 = never)", 0, G_MAXUINT,
          DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TIMEOUT_ALWAYS_VISIBLE,
      g_param_spec_boolean ("timeout-always-visible", "Timeout Always Visible ",
          "timeout always visible in header",
          DEFAULT_ALWAYS_VISIBLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPSession::extra-timeout:
   *
   * Extra time to add to the timeout, in seconds. This only affects the
   * time until a session is considered timed out and is not signalled
   * in the RTSP request responses. Only the value of the timeout
   * property is signalled in the request responses.
   *
   * Default value is 5 seconds.
   * If the application is using a buffer that is configured to hold
   * amount of data equal to the sessiontimeout, extra-timeout can be
   * set to zero to prevent loss of data
   *
   * Since: 1.18
   */
  g_object_class_install_property (gobject_class, PROP_EXTRA_TIME_TIMEOUT,
      g_param_spec_uint ("extra-timeout",
          "Add extra time to timeout ", "Add extra time to timeout", 0,
          G_MAXUINT, DEFAULT_EXTRA_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));


  GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsession", 0,
      "GstRTSPSession");
}

static void
gst_rtsp_session_init (GstRTSPSession * session)
{
  GstRTSPSessionPrivate *priv;

  session->priv = priv = gst_rtsp_session_get_instance_private (session);

  GST_INFO ("init session %p", session);

  g_mutex_init (&priv->lock);
  g_mutex_init (&priv->last_access_lock);
  priv->timeout = DEFAULT_TIMEOUT;
  priv->extra_time_timeout = DEFAULT_EXTRA_TIMEOUT;

  gst_rtsp_session_touch (session);
}

static void
gst_rtsp_session_finalize (GObject * obj)
{
  GstRTSPSession *session;
  GstRTSPSessionPrivate *priv;

  session = GST_RTSP_SESSION (obj);
  priv = session->priv;

  GST_INFO ("finalize session %p", session);

  /* free all media */
  g_list_free_full (priv->medias, g_object_unref);

  /* free session id */
  g_free (priv->sessionid);
  g_mutex_clear (&priv->last_access_lock);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj);
}

static void
gst_rtsp_session_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPSession *session = GST_RTSP_SESSION (object);
  GstRTSPSessionPrivate *priv = session->priv;

  switch (propid) {
    case PROP_SESSIONID:
      g_value_set_string (value, priv->sessionid);
      break;
    case PROP_TIMEOUT:
      g_value_set_uint (value, gst_rtsp_session_get_timeout (session));
      break;
    case PROP_TIMEOUT_ALWAYS_VISIBLE:
      g_value_set_boolean (value, priv->timeout_always_visible);
      break;
    case PROP_EXTRA_TIME_TIMEOUT:
      g_value_set_uint (value, priv->extra_time_timeout);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_session_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPSession *session = GST_RTSP_SESSION (object);
  GstRTSPSessionPrivate *priv = session->priv;

  switch (propid) {
    case PROP_SESSIONID:
      g_free (priv->sessionid);
      priv->sessionid = g_value_dup_string (value);
      break;
    case PROP_TIMEOUT:
      gst_rtsp_session_set_timeout (session, g_value_get_uint (value));
      break;
    case PROP_TIMEOUT_ALWAYS_VISIBLE:
      g_mutex_lock (&priv->lock);
      priv->timeout_always_visible = g_value_get_boolean (value);
      g_mutex_unlock (&priv->lock);
      break;
    case PROP_EXTRA_TIME_TIMEOUT:
      g_mutex_lock (&priv->lock);
      priv->extra_time_timeout = g_value_get_uint (value);
      g_mutex_unlock (&priv->lock);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_session_manage_media:
 * @sess: a #GstRTSPSession
 * @path: the path for the media
 * @media: (transfer full): a #GstRTSPMedia
 *
 * Manage the media object @obj in @sess. @path will be used to retrieve this
 * media from the session with gst_rtsp_session_get_media().
 *
 * Ownership is taken from @media.
 *
 * Returns: (transfer none): a new @GstRTSPSessionMedia object.
 */
GstRTSPSessionMedia *
gst_rtsp_session_manage_media (GstRTSPSession * sess, const gchar * path,
    GstRTSPMedia * media)
{
  GstRTSPSessionPrivate *priv;
  GstRTSPSessionMedia *result;
  GstRTSPMediaStatus status GST_UNUSED_CHECKS;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
#ifndef G_DISABLE_CHECKS
  status = gst_rtsp_media_get_status (media);
  g_return_val_if_fail (status == GST_RTSP_MEDIA_STATUS_PREPARED || status ==
      GST_RTSP_MEDIA_STATUS_SUSPENDED, NULL);
#endif

  priv = sess->priv;

  result = gst_rtsp_session_media_new (path, media);

  g_mutex_lock (&priv->lock);
  priv->medias = g_list_prepend (priv->medias, result);
  priv->medias_cookie++;
  g_mutex_unlock (&priv->lock);

  GST_INFO ("manage new media %p in session %p", media, result);

  return result;
}

static void
gst_rtsp_session_unset_transport_keepalive (GstRTSPSessionMedia * sessmedia)
{
  GstRTSPMedia *media;
  guint i, n_streams;

  media = gst_rtsp_session_media_get_media (sessmedia);
  n_streams = gst_rtsp_media_n_streams (media);

  for (i = 0; i < n_streams; i++) {
    GstRTSPStreamTransport *transport =
        gst_rtsp_session_media_get_transport (sessmedia, i);

    if (!transport)
      continue;

    gst_rtsp_stream_transport_set_keepalive (transport, NULL, NULL, NULL);
  }
}

/**
 * gst_rtsp_session_release_media:
 * @sess: a #GstRTSPSession
 * @media: (transfer none): a #GstRTSPMedia
 *
 * Release the managed @media in @sess, freeing the memory allocated by it.
 *
 * Returns: %TRUE if there are more media session left in @sess.
 */
gboolean
gst_rtsp_session_release_media (GstRTSPSession * sess,
    GstRTSPSessionMedia * media)
{
  GstRTSPSessionPrivate *priv;
  GList *find;
  gboolean more;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
  g_return_val_if_fail (media != NULL, FALSE);

  priv = sess->priv;

  g_mutex_lock (&priv->lock);
  find = g_list_find (priv->medias, media);
  if (find) {
    priv->medias = g_list_delete_link (priv->medias, find);
    priv->medias_cookie++;
  }
  more = (priv->medias != NULL);
  g_mutex_unlock (&priv->lock);

  if (find && !more)
    gst_rtsp_session_unset_transport_keepalive (media);

  if (find)
    g_object_unref (media);

  return more;
}

static GstRTSPSessionMedia *
_gst_rtsp_session_get_media (GstRTSPSession * sess, const gchar * path,
    gint * matched, gboolean dup)
{
  GstRTSPSessionPrivate *priv;
  GstRTSPSessionMedia *result;
  GList *walk;
  gint best;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
  g_return_val_if_fail (path != NULL, NULL);

  priv = sess->priv;
  result = NULL;
  best = 0;

  g_mutex_lock (&priv->lock);
  for (walk = priv->medias; walk; walk = g_list_next (walk)) {
    GstRTSPSessionMedia *test;

    test = (GstRTSPSessionMedia *) walk->data;

    /* find largest match */
    if (gst_rtsp_session_media_matches (test, path, matched)) {
      if (best < *matched) {
        result = test;
        best = *matched;
      }
    }
  }

  if (result && dup)
    result = g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  *matched = best;

  return result;
}

/**
 * gst_rtsp_session_get_media:
 * @sess: a #GstRTSPSession
 * @path: the path for the media
 * @matched: (out): the amount of matched characters
 *
 * Gets the session media for @path. @matched will contain the number of matched
 * characters of @path.
 *
 * Returns: (transfer none) (nullable): the configuration for @path in @sess.
 */
GstRTSPSessionMedia *
gst_rtsp_session_get_media (GstRTSPSession * sess, const gchar * path,
    gint * matched)
{
  return _gst_rtsp_session_get_media (sess, path, matched, FALSE);
}

/**
 * gst_rtsp_session_dup_media:
 * @sess: a #GstRTSPSession
 * @path: the path for the media
 * @matched: (out): the amount of matched characters
 *
 * Gets the session media for @path, increasing its reference count. @matched
 * will contain the number of matched characters of @path.
 *
 * Returns: (transfer full) (nullable): the configuration for @path in @sess,
 * should be unreferenced when no longer needed.
 *
 * Since: 1.20
 */
GstRTSPSessionMedia *
gst_rtsp_session_dup_media (GstRTSPSession * sess, const gchar * path,
    gint * matched)
{
  return _gst_rtsp_session_get_media (sess, path, matched, TRUE);
}

/**
 * gst_rtsp_session_filter:
 * @sess: a #GstRTSPSession
 * @func: (scope call) (allow-none) (closure user_data): a callback
 * @user_data: user data passed to @func
 *
 * Call @func for each media in @sess. The result value of @func determines
 * what happens to the media. @func will be called with @sess
 * locked so no further actions on @sess can be performed from @func.
 *
 * If @func returns #GST_RTSP_FILTER_REMOVE, the media will be removed from
 * @sess.
 *
 * If @func returns #GST_RTSP_FILTER_KEEP, the media will remain in @sess.
 *
 * If @func returns #GST_RTSP_FILTER_REF, the media will remain in @sess but
 * will also be added with an additional ref to the result #GList of this
 * function..
 *
 * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all media.
 *
 * Returns: (element-type GstRTSPSessionMedia) (transfer full): a GList with all
 * media for which @func returned #GST_RTSP_FILTER_REF. After usage, each
 * element in the #GList should be unreffed before the list is freed.
 */
GList *
gst_rtsp_session_filter (GstRTSPSession * sess,
    GstRTSPSessionFilterFunc func, gpointer user_data)
{
  GstRTSPSessionPrivate *priv;
  GList *result, *walk, *next;
  GHashTable *visited;
  guint cookie;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);

  priv = sess->priv;

  result = NULL;
  if (func)
    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  g_mutex_lock (&priv->lock);
restart:
  cookie = priv->medias_cookie;
  for (walk = priv->medias; walk; walk = next) {
    GstRTSPSessionMedia *media = walk->data;
    GstRTSPFilterResult res;
    gboolean changed;

    next = g_list_next (walk);

    if (func) {
      /* only visit each media once */
      if (g_hash_table_contains (visited, media))
        continue;

      g_hash_table_add (visited, g_object_ref (media));
      g_mutex_unlock (&priv->lock);

      res = func (sess, media, user_data);

      g_mutex_lock (&priv->lock);
    } else {
      res = GST_RTSP_FILTER_REF;
    }

    changed = (cookie != priv->medias_cookie);

    switch (res) {
      case GST_RTSP_FILTER_REMOVE:
        if (changed) {
          GList *l;

          walk = NULL;

          for (l = priv->medias; l; l = l->next) {
            if (l->data == media) {
              walk = l;
              break;
            }
          }
        }

        /* The media might have been removed from the list while the mutex was
         * unlocked above. In that case there's nothing else to do here as the
         * only reference to the media owned by this function is in the
         * visited hash table and that is released in the end
         */
        if (walk) {
          priv->medias = g_list_delete_link (priv->medias, walk);
          g_object_unref (media);
        }

        cookie = ++priv->medias_cookie;
        break;
      case GST_RTSP_FILTER_REF:
        result = g_list_prepend (result, g_object_ref (media));
        break;
      case GST_RTSP_FILTER_KEEP:
      default:
        break;
    }
    if (changed)
      goto restart;
  }
  g_mutex_unlock (&priv->lock);

  if (func)
    g_hash_table_unref (visited);

  return result;
}

/**
 * gst_rtsp_session_new:
 * @sessionid: a session id
 *
 * Create a new #GstRTSPSession instance with @sessionid.
 *
 * Returns: (transfer full): a new #GstRTSPSession
 */
GstRTSPSession *
gst_rtsp_session_new (const gchar * sessionid)
{
  GstRTSPSession *result;

  g_return_val_if_fail (sessionid != NULL, NULL);

  result = g_object_new (GST_TYPE_RTSP_SESSION, "sessionid", sessionid, NULL);

  return result;
}

/**
 * gst_rtsp_session_get_sessionid:
 * @session: a #GstRTSPSession
 *
 * Get the sessionid of @session.
 *
 * Returns: (transfer none) (nullable): the sessionid of @session.
 * The value remains valid as long as @session is alive.
 */
const gchar *
gst_rtsp_session_get_sessionid (GstRTSPSession * session)
{
  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);

  return session->priv->sessionid;
}

/**
 * gst_rtsp_session_get_header:
 * @session: a #GstRTSPSession
 *
 * Get the string that can be placed in the Session header field.
 *
 * Returns: (transfer full) (nullable): the Session header of @session.
 * g_free() after usage.
 */
gchar *
gst_rtsp_session_get_header (GstRTSPSession * session)
{
  GstRTSPSessionPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);

  priv = session->priv;


  g_mutex_lock (&priv->lock);
  if (priv->timeout_always_visible || priv->timeout != 60)
    result = g_strdup_printf ("%s;timeout=%d", priv->sessionid, priv->timeout);
  else
    result = g_strdup (priv->sessionid);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_session_set_timeout:
 * @session: a #GstRTSPSession
 * @timeout: the new timeout
 *
 * Configure @session for a timeout of @timeout seconds. The session will be
 * cleaned up when there is no activity for @timeout seconds.
 */
void
gst_rtsp_session_set_timeout (GstRTSPSession * session, guint timeout)
{
  GstRTSPSessionPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SESSION (session));

  priv = session->priv;

  g_mutex_lock (&priv->lock);
  priv->timeout = timeout;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_session_get_timeout:
 * @session: a #GstRTSPSession
 *
 * Get the timeout value of @session.
 *
 * Returns: the timeout of @session in seconds.
 */
guint
gst_rtsp_session_get_timeout (GstRTSPSession * session)
{
  GstRTSPSessionPrivate *priv;
  guint res;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), 0);

  priv = session->priv;

  g_mutex_lock (&priv->lock);
  res = priv->timeout;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_session_touch:
 * @session: a #GstRTSPSession
 *
 * Update the last_access time of the session to the current time.
 */
void
gst_rtsp_session_touch (GstRTSPSession * session)
{
  GstRTSPSessionPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_SESSION (session));

  priv = session->priv;

  g_mutex_lock (&priv->last_access_lock);
  priv->last_access_monotonic_time = g_get_monotonic_time ();
  priv->last_access_real_time = g_get_real_time ();
  g_mutex_unlock (&priv->last_access_lock);
}

/**
 * gst_rtsp_session_prevent_expire:
 * @session: a #GstRTSPSession
 *
 * Prevent @session from expiring.
 */
void
gst_rtsp_session_prevent_expire (GstRTSPSession * session)
{
  g_return_if_fail (GST_IS_RTSP_SESSION (session));

  g_atomic_int_add (&session->priv->expire_count, 1);
}

/**
 * gst_rtsp_session_allow_expire:
 * @session: a #GstRTSPSession
 *
 * Allow @session to expire. This method must be called an equal
 * amount of time as gst_rtsp_session_prevent_expire().
 */
void
gst_rtsp_session_allow_expire (GstRTSPSession * session)
{
  g_atomic_int_add (&session->priv->expire_count, -1);
}

/**
 * gst_rtsp_session_next_timeout_usec:
 * @session: a #GstRTSPSession
 * @now: the current monotonic time
 *
 * Get the amount of milliseconds till the session will expire.
 *
 * Returns: the amount of milliseconds since the session will time out.
 */
gint
gst_rtsp_session_next_timeout_usec (GstRTSPSession * session, gint64 now)
{
  GstRTSPSessionPrivate *priv;
  gint res;
  GstClockTime last_access, now_ns;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), -1);

  priv = session->priv;

  g_mutex_lock (&priv->lock);
  /* If timeout is set to 0, we never timeout */
  if (priv->timeout == 0) {
    g_mutex_unlock (&priv->lock);
    return NO_TIMEOUT;
  }
  g_mutex_unlock (&priv->lock);

  g_mutex_lock (&priv->last_access_lock);
  if (g_atomic_int_get (&priv->expire_count) != 0) {
    /* touch session when the expire count is not 0 */
    priv->last_access_monotonic_time = g_get_monotonic_time ();
    priv->last_access_real_time = g_get_real_time ();
  }

  last_access = GST_USECOND * (priv->last_access_monotonic_time);

  /* add timeout allow for priv->extra_time_timeout
   * seconds of extra time */
  last_access += priv->timeout * GST_SECOND +
      (priv->extra_time_timeout * GST_SECOND);

  g_mutex_unlock (&priv->last_access_lock);

  now_ns = GST_USECOND * now;

  if (last_access > now_ns) {
    res = GST_TIME_AS_MSECONDS (last_access - now_ns);
  } else {
    res = 0;
  }

  return res;
}

/****** Deprecated API *******/

/**
 * gst_rtsp_session_next_timeout:
 * @session: a #GstRTSPSession
 * @now: (transfer none): the current system time
 *
 * Get the amount of milliseconds till the session will expire.
 *
 * Returns: the amount of milliseconds since the session will time out.
 *
 * Deprecated: Use gst_rtsp_session_next_timeout_usec() instead.
 */
#ifndef GST_REMOVE_DEPRECATED
G_GNUC_BEGIN_IGNORE_DEPRECATIONS gint
gst_rtsp_session_next_timeout (GstRTSPSession * session, GTimeVal * now)
{
  GstRTSPSessionPrivate *priv;
  gint res;
  GstClockTime last_access, now_ns;

  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), -1);
  g_return_val_if_fail (now != NULL, -1);

  priv = session->priv;

  g_mutex_lock (&priv->last_access_lock);
  if (g_atomic_int_get (&priv->expire_count) != 0) {
    /* touch session when the expire count is not 0 */
    priv->last_access_monotonic_time = g_get_monotonic_time ();
    priv->last_access_real_time = g_get_real_time ();
  }

  last_access = GST_USECOND * (priv->last_access_real_time);

  /* add timeout allow for priv->extra_time_timeout
   * seconds of extra time */
  last_access += priv->timeout * GST_SECOND +
      (priv->extra_time_timeout * GST_SECOND);

  g_mutex_unlock (&priv->last_access_lock);

  now_ns = GST_TIMEVAL_TO_TIME (*now);

  if (last_access > now_ns) {
    res = GST_TIME_AS_MSECONDS (last_access - now_ns);
  } else {
    res = 0;
  }

  return res;
}

G_GNUC_END_IGNORE_DEPRECATIONS
#endif
/**
 * gst_rtsp_session_is_expired_usec:
 * @session: a #GstRTSPSession
 * @now: the current monotonic time
 *
 * Check if @session timeout out.
 *
 * Returns: %TRUE if @session timed out
 */
    gboolean
gst_rtsp_session_is_expired_usec (GstRTSPSession * session, gint64 now)
{
  gboolean res;

  res = (gst_rtsp_session_next_timeout_usec (session, now) == 0);

  return res;
}


/****** Deprecated API *******/

/**
 * gst_rtsp_session_is_expired:
 * @session: a #GstRTSPSession
 * @now: (transfer none): the current system time
 *
 * Check if @session timeout out.
 *
 * Returns: %TRUE if @session timed out
 *
 * Deprecated: Use gst_rtsp_session_is_expired_usec() instead.
 */
#ifndef GST_REMOVE_DEPRECATED
G_GNUC_BEGIN_IGNORE_DEPRECATIONS gboolean
gst_rtsp_session_is_expired (GstRTSPSession * session, GTimeVal * now)
{
  gboolean res;

  res = gst_rtsp_session_next_timeout_usec (session,
      (now->tv_sec * G_USEC_PER_SEC) + (now->tv_usec));

  return res;
}

G_GNUC_END_IGNORE_DEPRECATIONS
#endif
 0707010000005E000081A400000000000000000000000169D554BF00001BCB000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-session.h /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp/gstrtsptransport.h>
#include "rtsp-server-prelude.h" /* for GST_RTSP_SERVER_DEPRECATED_FOR */

#ifndef __GST_RTSP_SESSION_H__
#define __GST_RTSP_SESSION_H__

G_BEGIN_DECLS

#define GST_TYPE_RTSP_SESSION              (gst_rtsp_session_get_type ())
#define GST_IS_RTSP_SESSION(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SESSION))
#define GST_IS_RTSP_SESSION_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SESSION))
#define GST_RTSP_SESSION_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SESSION, GstRTSPSessionClass))
#define GST_RTSP_SESSION(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SESSION, GstRTSPSession))
#define GST_RTSP_SESSION_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SESSION, GstRTSPSessionClass))
#define GST_RTSP_SESSION_CAST(obj)         ((GstRTSPSession*)(obj))
#define GST_RTSP_SESSION_CLASS_CAST(klass) ((GstRTSPSessionClass*)(klass))

typedef struct _GstRTSPSession GstRTSPSession;
typedef struct _GstRTSPSessionClass GstRTSPSessionClass;
typedef struct _GstRTSPSessionPrivate GstRTSPSessionPrivate;

/**
 * GstRTSPFilterResult:
 * @GST_RTSP_FILTER_REMOVE: Remove session
 * @GST_RTSP_FILTER_KEEP: Keep session in the pool
 * @GST_RTSP_FILTER_REF: Ref session in the result list
 *
 * Possible return values for gst_rtsp_session_pool_filter().
 */
typedef enum
{
  GST_RTSP_FILTER_REMOVE,
  GST_RTSP_FILTER_KEEP,
  GST_RTSP_FILTER_REF,
} GstRTSPFilterResult;

#include "rtsp-media.h"
#include "rtsp-session-media.h"

/**
 * GstRTSPSession:
 *
 * Session information kept by the server for a specific client.
 * One client session, identified with a session id, can handle multiple medias
 * identified with the url of a media.
 */
struct _GstRTSPSession {
  GObject       parent;

  /*< private >*/
  GstRTSPSessionPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

struct _GstRTSPSessionClass {
  GObjectClass  parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                  gst_rtsp_session_get_type             (void);

/* create a new session */

GST_RTSP_SERVER_API
GstRTSPSession *       gst_rtsp_session_new                  (const gchar *sessionid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
const gchar *          gst_rtsp_session_get_sessionid        (GstRTSPSession *session);

GST_RTSP_SERVER_API
gchar *                gst_rtsp_session_get_header           (GstRTSPSession *session) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                   gst_rtsp_session_set_timeout          (GstRTSPSession *session, guint timeout);

GST_RTSP_SERVER_API
guint                  gst_rtsp_session_get_timeout          (GstRTSPSession *session);

/* session timeout stuff */

GST_RTSP_SERVER_API
void                   gst_rtsp_session_touch                (GstRTSPSession *session);

GST_RTSP_SERVER_API
void                   gst_rtsp_session_prevent_expire       (GstRTSPSession *session);

GST_RTSP_SERVER_API
void                   gst_rtsp_session_allow_expire         (GstRTSPSession *session);

GST_RTSP_SERVER_API
gint                   gst_rtsp_session_next_timeout_usec    (GstRTSPSession *session, gint64 now);

GST_RTSP_SERVER_API
gboolean               gst_rtsp_session_is_expired_usec      (GstRTSPSession *session, gint64 now);

G_GNUC_BEGIN_IGNORE_DEPRECATIONS
GST_RTSP_SERVER_DEPRECATED_FOR(gst_rtsp_session_next_timeout_usec)
gint                   gst_rtsp_session_next_timeout         (GstRTSPSession *session, GTimeVal *now);

GST_RTSP_SERVER_DEPRECATED_FOR(gst_rtsp_session_is_expired_usec)
gboolean               gst_rtsp_session_is_expired           (GstRTSPSession *session, GTimeVal *now);
G_GNUC_END_IGNORE_DEPRECATIONS

/* handle media in a session */

GST_RTSP_SERVER_API
GstRTSPSessionMedia *  gst_rtsp_session_manage_media         (GstRTSPSession *sess,
                                                              const gchar *path,
                                                              GstRTSPMedia *media);

GST_RTSP_SERVER_API
gboolean               gst_rtsp_session_release_media        (GstRTSPSession *sess,
                                                              GstRTSPSessionMedia *media);
/* get media in a session */

GST_RTSP_SERVER_API
GstRTSPSessionMedia *  gst_rtsp_session_get_media            (GstRTSPSession *sess,
                                                              const gchar *path,
                                                              gint * matched);
/* get media in a session, increasing its reference count */

GST_RTSP_SERVER_API
GstRTSPSessionMedia *  gst_rtsp_session_dup_media            (GstRTSPSession *sess,
                                                              const gchar *path,
                                                              gint * matched) G_GNUC_WARN_UNUSED_RESULT;
/**
 * GstRTSPSessionFilterFunc:
 * @sess: a #GstRTSPSession object
 * @media: a #GstRTSPSessionMedia in @sess
 * @user_data: user data that has been given to gst_rtsp_session_filter()
 *
 * This function will be called by the gst_rtsp_session_filter(). An
 * implementation should return a value of #GstRTSPFilterResult.
 *
 * When this function returns #GST_RTSP_FILTER_REMOVE, @media will be removed
 * from @sess.
 *
 * A return value of #GST_RTSP_FILTER_KEEP will leave @media untouched in
 * @sess.
 *
 * A value of GST_RTSP_FILTER_REF will add @media to the result #GList of
 * gst_rtsp_session_filter().
 *
 * Returns: a #GstRTSPFilterResult.
 */
typedef GstRTSPFilterResult (*GstRTSPSessionFilterFunc)  (GstRTSPSession *sess,
                                                          GstRTSPSessionMedia *media,
                                                          gpointer user_data);

GST_RTSP_SERVER_API
GList *                gst_rtsp_session_filter           (GstRTSPSession *sess,
                                                          GstRTSPSessionFilterFunc func,
                                                          gpointer user_data) G_GNUC_WARN_UNUSED_RESULT;


#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPSession, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_SESSION_H__ */
 0707010000005F000081A400000000000000000000000169D554BF00006DC5000000000000000000000000000000000000003F00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-stream-transport.c    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-stream-transport
 * @short_description: A media stream transport configuration
 * @see_also: #GstRTSPStream, #GstRTSPSessionMedia
 *
 * The #GstRTSPStreamTransport configures the transport used by a
 * #GstRTSPStream. It is usually manages by a #GstRTSPSessionMedia object.
 *
 * With gst_rtsp_stream_transport_set_callbacks(), callbacks can be configured
 * to handle the RTP and RTCP packets from the stream, for example when they
 * need to be sent over TCP.
 *
 * With gst_rtsp_stream_transport_set_active() the transports are added and
 * removed from the stream.
 *
 * A #GstRTSPStream will call gst_rtsp_stream_transport_keep_alive() when RTCP
 * is received from the client. It will also call
 * gst_rtsp_stream_transport_set_timed_out() when a receiver has timed out.
 *
 * A #GstRTSPClient will call gst_rtsp_stream_transport_message_sent() when it
 * has sent a data message for the transport.
 *
 * Last reviewed on 2013-07-16 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdlib.h>

#include "rtsp-stream-transport.h"
#include "rtsp-server-internal.h"

struct _GstRTSPStreamTransportPrivate
{
  GstRTSPStream *stream;

  GstRTSPSendFunc send_rtp;
  GstRTSPSendFunc send_rtcp;
  gpointer user_data;
  GDestroyNotify notify;

  GstRTSPSendListFunc send_rtp_list;
  GstRTSPSendListFunc send_rtcp_list;
  gpointer list_user_data;
  GDestroyNotify list_notify;

  GstRTSPBackPressureFunc back_pressure_func;
  gpointer back_pressure_func_data;
  GDestroyNotify back_pressure_func_notify;

  GstRTSPKeepAliveFunc keep_alive;
  gpointer ka_user_data;
  GDestroyNotify ka_notify;
  gboolean timed_out;

  GstRTSPMessageSentFunc message_sent;
  gpointer ms_user_data;
  GDestroyNotify ms_notify;

  GstRTSPMessageSentFuncFull message_sent_full;
  gpointer msf_user_data;
  GDestroyNotify msf_notify;

  GstRTSPTransport *transport;
  GstRTSPUrl *url;

  GObject *rtpsource;

  /* TCP backlog */
  GstClockTime first_rtp_timestamp;
  GstVecDeque *items;
  GRecMutex backlog_lock;
};

#define MAX_BACKLOG_DURATION (10 * GST_SECOND)
#define MAX_BACKLOG_SIZE 100

typedef struct
{
  GstBuffer *buffer;
  GstBufferList *buffer_list;
  gboolean is_rtp;
} BackLogItem;


enum
{
  PROP_0,
  PROP_TIMED_OUT,
  PROP_LAST
};

#define DEFAULT_TIMED_OUT FALSE

GST_DEBUG_CATEGORY_STATIC (rtsp_stream_transport_debug);
#define GST_CAT_DEFAULT rtsp_stream_transport_debug

static void gst_rtsp_stream_transport_finalize (GObject * obj);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPStreamTransport, gst_rtsp_stream_transport,
    G_TYPE_OBJECT);

static void
gst_rtsp_stream_transport_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPStreamTransport *transport = GST_RTSP_STREAM_TRANSPORT (object);

  switch (propid) {
    case PROP_TIMED_OUT:
      g_value_set_boolean (value, transport->priv->timed_out);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_stream_transport_class_init (GstRTSPStreamTransportClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gst_rtsp_stream_transport_finalize;
  gobject_class->get_property = gst_rtsp_stream_transport_get_property;

  /**
   * GstRTSPStreamTransport:timed-out:
   *
   * Whether this transport is timed out
   *
   * Since: 1.28
   */
  g_object_class_install_property (gobject_class, PROP_TIMED_OUT,
      g_param_spec_boolean ("timed-out", "Timed Out",
          "Whether this transport is timed out",
          DEFAULT_TIMED_OUT, G_PARAM_READABLE));

  GST_DEBUG_CATEGORY_INIT (rtsp_stream_transport_debug, "rtspmediatransport",
      0, "GstRTSPStreamTransport");
}

static void
clear_backlog_item (BackLogItem * item)
{
  gst_clear_buffer (&item->buffer);
  gst_clear_buffer_list (&item->buffer_list);
}

static void
gst_rtsp_stream_transport_init (GstRTSPStreamTransport * trans)
{
  trans->priv = gst_rtsp_stream_transport_get_instance_private (trans);
  trans->priv->items = gst_vec_deque_new_for_struct (sizeof (BackLogItem), 0);
  trans->priv->first_rtp_timestamp = GST_CLOCK_TIME_NONE;
  gst_vec_deque_set_clear_func (trans->priv->items,
      (GDestroyNotify) clear_backlog_item);
  g_rec_mutex_init (&trans->priv->backlog_lock);
}

static void
gst_rtsp_stream_transport_finalize (GObject * obj)
{
  GstRTSPStreamTransportPrivate *priv;
  GstRTSPStreamTransport *trans;

  trans = GST_RTSP_STREAM_TRANSPORT (obj);
  priv = trans->priv;

  /* remove callbacks now */
  gst_rtsp_stream_transport_set_callbacks (trans, NULL, NULL, NULL, NULL);
  gst_rtsp_stream_transport_set_keepalive (trans, NULL, NULL, NULL);
  gst_rtsp_stream_transport_set_message_sent (trans, NULL, NULL, NULL);

  if (priv->stream)
    g_object_unref (priv->stream);

  if (priv->transport)
    gst_rtsp_transport_free (priv->transport);

  if (priv->url)
    gst_rtsp_url_free (priv->url);

  gst_vec_deque_free (priv->items);

  g_rec_mutex_clear (&priv->backlog_lock);

  G_OBJECT_CLASS (gst_rtsp_stream_transport_parent_class)->finalize (obj);
}

/**
 * gst_rtsp_stream_transport_new:
 * @stream: a #GstRTSPStream
 * @tr: (transfer full): a GstRTSPTransport
 *
 * Create a new #GstRTSPStreamTransport that can be used to manage
 * @stream with transport @tr.
 *
 * Returns: (transfer full): a new #GstRTSPStreamTransport
 */
GstRTSPStreamTransport *
gst_rtsp_stream_transport_new (GstRTSPStream * stream, GstRTSPTransport * tr)
{
  GstRTSPStreamTransportPrivate *priv;
  GstRTSPStreamTransport *trans;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (tr != NULL, NULL);

  trans = g_object_new (GST_TYPE_RTSP_STREAM_TRANSPORT, NULL);
  priv = trans->priv;
  priv->stream = stream;
  priv->stream = g_object_ref (priv->stream);
  priv->transport = tr;

  return trans;
}

/**
 * gst_rtsp_stream_transport_get_stream:
 * @trans: a #GstRTSPStreamTransport
 *
 * Get the #GstRTSPStream used when constructing @trans.
 *
 * Returns: (transfer none) (nullable): the stream used when constructing @trans.
 */
GstRTSPStream *
gst_rtsp_stream_transport_get_stream (GstRTSPStreamTransport * trans)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), NULL);

  return trans->priv->stream;
}

/**
 * gst_rtsp_stream_transport_set_callbacks:
 * @trans: a #GstRTSPStreamTransport
 * @send_rtp: (scope notified) (closure user_data): a callback called when RTP should be sent
 * @send_rtcp: (scope notified) (closure user_data): a callback called when RTCP should be sent
 * @user_data: user data passed to callbacks
 * @notify: (allow-none): called with the user_data when no longer needed.
 *
 * Install callbacks that will be called when data for a stream should be sent
 * to a client. This is usually used when sending RTP/RTCP over TCP.
 */
void
gst_rtsp_stream_transport_set_callbacks (GstRTSPStreamTransport * trans,
    GstRTSPSendFunc send_rtp, GstRTSPSendFunc send_rtcp,
    gpointer user_data, GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->send_rtp = send_rtp;
  priv->send_rtcp = send_rtcp;
  if (priv->notify)
    priv->notify (priv->user_data);
  priv->user_data = user_data;
  priv->notify = notify;
}

/**
 * gst_rtsp_stream_transport_set_list_callbacks:
 * @trans: a #GstRTSPStreamTransport
 * @send_rtp_list: (scope notified) (closure user_data): a callback called when RTP should be sent
 * @send_rtcp_list: (scope notified) (closure user_data): a callback called when RTCP should be sent
 * @user_data: user data passed to callbacks
 * @notify: (allow-none): called with the user_data when no longer needed.
 *
 * Install callbacks that will be called when data for a stream should be sent
 * to a client. This is usually used when sending RTP/RTCP over TCP.
 *
 * Since: 1.16
 */
void
gst_rtsp_stream_transport_set_list_callbacks (GstRTSPStreamTransport * trans,
    GstRTSPSendListFunc send_rtp_list, GstRTSPSendListFunc send_rtcp_list,
    gpointer user_data, GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->send_rtp_list = send_rtp_list;
  priv->send_rtcp_list = send_rtcp_list;
  if (priv->list_notify)
    priv->list_notify (priv->list_user_data);
  priv->list_user_data = user_data;
  priv->list_notify = notify;
}

void
gst_rtsp_stream_transport_set_back_pressure_callback (GstRTSPStreamTransport *
    trans, GstRTSPBackPressureFunc back_pressure_func, gpointer user_data,
    GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->back_pressure_func = back_pressure_func;
  if (priv->back_pressure_func_notify)
    priv->back_pressure_func_notify (priv->back_pressure_func_data);
  priv->back_pressure_func_data = user_data;
  priv->back_pressure_func_notify = notify;
}

gboolean
gst_rtsp_stream_transport_check_back_pressure (GstRTSPStreamTransport * trans,
    gboolean is_rtp)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean ret = FALSE;
  guint8 channel;

  priv = trans->priv;

  if (is_rtp)
    channel = priv->transport->interleaved.min;
  else
    channel = priv->transport->interleaved.max;

  if (priv->back_pressure_func)
    ret = priv->back_pressure_func (channel, priv->back_pressure_func_data);

  return ret;
}

/**
 * gst_rtsp_stream_transport_set_keepalive:
 * @trans: a #GstRTSPStreamTransport
 * @keep_alive: (scope notified) (closure user_data): a callback called when the receiver is active
 * @user_data: user data passed to callback
 * @notify: (allow-none): called with the user_data when no longer needed.
 *
 * Install callbacks that will be called when RTCP packets are received from the
 * receiver of @trans.
 */
void
gst_rtsp_stream_transport_set_keepalive (GstRTSPStreamTransport * trans,
    GstRTSPKeepAliveFunc keep_alive, gpointer user_data, GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->keep_alive = keep_alive;
  if (priv->ka_notify)
    priv->ka_notify (priv->ka_user_data);
  priv->ka_user_data = user_data;
  priv->ka_notify = notify;
}

/**
 * gst_rtsp_stream_transport_set_message_sent:
 * @trans: a #GstRTSPStreamTransport
 * @message_sent: (scope notified) (closure user_data): a callback called when a message has been sent
 * @user_data: user data passed to callback
 * @notify: (allow-none): called with the user_data when no longer needed
 *
 * Install a callback that will be called when a message has been sent on @trans.
 */
void
gst_rtsp_stream_transport_set_message_sent (GstRTSPStreamTransport * trans,
    GstRTSPMessageSentFunc message_sent, gpointer user_data,
    GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->message_sent = message_sent;
  if (priv->ms_notify)
    priv->ms_notify (priv->ms_user_data);
  priv->ms_user_data = user_data;
  priv->ms_notify = notify;
}

/**
 * gst_rtsp_stream_transport_set_message_sent_full:
 * @trans: a #GstRTSPStreamTransport
 * @message_sent: (scope notified) (closure user_data): a callback called when a message has been sent
 * @user_data: user data passed to callback
 * @notify: (allow-none): called with the user_data when no longer needed
 *
 * Install a callback that will be called when a message has been sent on @trans.
 *
 * Since: 1.18
 */
void
gst_rtsp_stream_transport_set_message_sent_full (GstRTSPStreamTransport * trans,
    GstRTSPMessageSentFuncFull message_sent, gpointer user_data,
    GDestroyNotify notify)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  priv->message_sent_full = message_sent;
  if (priv->msf_notify)
    priv->msf_notify (priv->msf_user_data);
  priv->msf_user_data = user_data;
  priv->msf_notify = notify;
}

/**
 * gst_rtsp_stream_transport_set_transport:
 * @trans: a #GstRTSPStreamTransport
 * @tr: (transfer full): a client #GstRTSPTransport
 *
 * Set @tr as the client transport. This function takes ownership of the
 * passed @tr.
 */
void
gst_rtsp_stream_transport_set_transport (GstRTSPStreamTransport * trans,
    GstRTSPTransport * tr)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));
  g_return_if_fail (tr != NULL);

  priv = trans->priv;

  /* keep track of the transports in the stream. */
  if (priv->transport)
    gst_rtsp_transport_free (priv->transport);
  priv->transport = tr;
}

/**
 * gst_rtsp_stream_transport_get_transport:
 * @trans: a #GstRTSPStreamTransport
 *
 * Get the transport configured in @trans.
 *
 * Returns: (transfer none) (nullable): the transport configured in @trans. It remains
 * valid for as long as @trans is valid.
 */
const GstRTSPTransport *
gst_rtsp_stream_transport_get_transport (GstRTSPStreamTransport * trans)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), NULL);

  return trans->priv->transport;
}

/**
 * gst_rtsp_stream_transport_set_url:
 * @trans: a #GstRTSPStreamTransport
 * @url: (transfer none) (nullable): a client #GstRTSPUrl
 *
 * Set @url as the client url.
 */
void
gst_rtsp_stream_transport_set_url (GstRTSPStreamTransport * trans,
    const GstRTSPUrl * url)
{
  GstRTSPStreamTransportPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  priv = trans->priv;

  /* keep track of the transports in the stream. */
  if (priv->url)
    gst_rtsp_url_free (priv->url);
  priv->url = (url ? gst_rtsp_url_copy (url) : NULL);
}

/**
 * gst_rtsp_stream_transport_get_url:
 * @trans: a #GstRTSPStreamTransport
 *
 * Get the url configured in @trans.
 *
 * Returns: (transfer none) (nullable): the url configured in @trans.
 * It remains valid for as long as @trans is valid.
 */
const GstRTSPUrl *
gst_rtsp_stream_transport_get_url (GstRTSPStreamTransport * trans)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), NULL);

  return trans->priv->url;
}

 /**
 * gst_rtsp_stream_transport_get_rtpinfo:
 * @trans: a #GstRTSPStreamTransport
 * @start_time: a star time
 *
 * Get the RTP-Info string for @trans and @start_time.
 *
 * Returns: (transfer full) (nullable): the RTPInfo string for @trans
 * and @start_time or %NULL when the RTP-Info could not be
 * determined. g_free() after usage.
 */
gchar *
gst_rtsp_stream_transport_get_rtpinfo (GstRTSPStreamTransport * trans,
    GstClockTime start_time)
{
  GstRTSPStreamTransportPrivate *priv;
  gchar *url_str;
  GString *rtpinfo;
  guint rtptime, seq, clock_rate;
  GstClockTime running_time = GST_CLOCK_TIME_NONE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), NULL);

  priv = trans->priv;

  if (!gst_rtsp_stream_is_sender (priv->stream))
    return NULL;
  if (!gst_rtsp_stream_get_rtpinfo (priv->stream, &rtptime, &seq, &clock_rate,
          &running_time))
    return NULL;

  GST_DEBUG ("RTP time %u, seq %u, rate %u, running-time %" GST_TIME_FORMAT,
      rtptime, seq, clock_rate, GST_TIME_ARGS (running_time));

  if (GST_CLOCK_TIME_IS_VALID (running_time)
      && GST_CLOCK_TIME_IS_VALID (start_time)) {
    if (running_time > start_time) {
      rtptime -=
          gst_util_uint64_scale_int (running_time - start_time, clock_rate,
          GST_SECOND);
    } else {
      rtptime +=
          gst_util_uint64_scale_int (start_time - running_time, clock_rate,
          GST_SECOND);
    }
  }
  GST_DEBUG ("RTP time %u, for start-time %" GST_TIME_FORMAT,
      rtptime, GST_TIME_ARGS (start_time));

  rtpinfo = g_string_new ("");

  url_str = gst_rtsp_url_get_request_uri (trans->priv->url);
  g_string_append_printf (rtpinfo, "url=%s;seq=%u;rtptime=%u",
      url_str, seq, rtptime);
  g_free (url_str);

  return g_string_free (rtpinfo, FALSE);
}

/**
 * gst_rtsp_stream_transport_set_active:
 * @trans: a #GstRTSPStreamTransport
 * @active: new state of @trans
 *
 * Activate or deactivate datatransfer configured in @trans.
 *
 * Returns: %TRUE when the state was changed.
 */
gboolean
gst_rtsp_stream_transport_set_active (GstRTSPStreamTransport * trans,
    gboolean active)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);

  priv = trans->priv;

  if (active)
    res = gst_rtsp_stream_add_transport (priv->stream, trans);
  else
    res = gst_rtsp_stream_remove_transport (priv->stream, trans);

  return res;
}

/**
 * gst_rtsp_stream_transport_set_timed_out:
 * @trans: a #GstRTSPStreamTransport
 * @timedout: timed out value
 *
 * Set the timed out state of @trans to @timedout
 */
void
gst_rtsp_stream_transport_set_timed_out (GstRTSPStreamTransport * trans,
    gboolean timedout)
{
  g_return_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans));

  trans->priv->timed_out = timedout;

  g_object_notify (G_OBJECT (trans), "timed-out");
}

/**
 * gst_rtsp_stream_transport_is_timed_out:
 * @trans: a #GstRTSPStreamTransport
 *
 * Check if @trans is timed out.
 *
 * Returns: %TRUE if @trans timed out.
 */
gboolean
gst_rtsp_stream_transport_is_timed_out (GstRTSPStreamTransport * trans)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);

  return trans->priv->timed_out;
}

/**
 * gst_rtsp_stream_transport_send_rtp:
 * @trans: a #GstRTSPStreamTransport
 * @buffer: (transfer none): a #GstBuffer
 *
 * Send @buffer to the installed RTP callback for @trans.
 *
 * Returns: %TRUE on success
 */
gboolean
gst_rtsp_stream_transport_send_rtp (GstRTSPStreamTransport * trans,
    GstBuffer * buffer)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean res = FALSE;

  g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);

  priv = trans->priv;

  if (priv->send_rtp)
    res =
        priv->send_rtp (buffer, priv->transport->interleaved.min,
        priv->user_data);

  if (res)
    gst_rtsp_stream_transport_keep_alive (trans);

  return res;
}

/**
 * gst_rtsp_stream_transport_send_rtcp:
 * @trans: a #GstRTSPStreamTransport
 * @buffer: (transfer none): a #GstBuffer
 *
 * Send @buffer to the installed RTCP callback for @trans.
 *
 * Returns: %TRUE on success
 */
gboolean
gst_rtsp_stream_transport_send_rtcp (GstRTSPStreamTransport * trans,
    GstBuffer * buffer)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean res = FALSE;

  g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);

  priv = trans->priv;

  if (priv->send_rtcp)
    res =
        priv->send_rtcp (buffer, priv->transport->interleaved.max,
        priv->user_data);

  if (res)
    gst_rtsp_stream_transport_keep_alive (trans);

  return res;
}

/**
 * gst_rtsp_stream_transport_send_rtp_list:
 * @trans: a #GstRTSPStreamTransport
 * @buffer_list: (transfer none): a #GstBufferList
 *
 * Send @buffer_list to the installed RTP callback for @trans.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_transport_send_rtp_list (GstRTSPStreamTransport * trans,
    GstBufferList * buffer_list)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean res = FALSE;

  g_return_val_if_fail (GST_IS_BUFFER_LIST (buffer_list), FALSE);

  priv = trans->priv;

  if (priv->send_rtp_list) {
    res =
        priv->send_rtp_list (buffer_list, priv->transport->interleaved.min,
        priv->list_user_data);
  } else if (priv->send_rtp) {
    guint n = gst_buffer_list_length (buffer_list), i;

    for (i = 0; i < n; i++) {
      GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);

      res =
          priv->send_rtp (buffer, priv->transport->interleaved.min,
          priv->user_data);
      if (!res)
        break;
    }
  }

  if (res)
    gst_rtsp_stream_transport_keep_alive (trans);

  return res;
}

/**
 * gst_rtsp_stream_transport_send_rtcp_list:
 * @trans: a #GstRTSPStreamTransport
 * @buffer_list: (transfer none): a #GstBuffer
 *
 * Send @buffer_list to the installed RTCP callback for @trans.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_transport_send_rtcp_list (GstRTSPStreamTransport * trans,
    GstBufferList * buffer_list)
{
  GstRTSPStreamTransportPrivate *priv;
  gboolean res = FALSE;

  g_return_val_if_fail (GST_IS_BUFFER_LIST (buffer_list), FALSE);

  priv = trans->priv;

  if (priv->send_rtcp_list) {
    res =
        priv->send_rtcp_list (buffer_list, priv->transport->interleaved.max,
        priv->list_user_data);
  } else if (priv->send_rtcp) {
    guint n = gst_buffer_list_length (buffer_list), i;

    for (i = 0; i < n; i++) {
      GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);

      res =
          priv->send_rtcp (buffer, priv->transport->interleaved.max,
          priv->user_data);
      if (!res)
        break;
    }
  }

  if (res)
    gst_rtsp_stream_transport_keep_alive (trans);

  return res;
}

/**
 * gst_rtsp_stream_transport_keep_alive:
 * @trans: a #GstRTSPStreamTransport
 *
 * Signal the installed keep_alive callback for @trans.
 */
void
gst_rtsp_stream_transport_keep_alive (GstRTSPStreamTransport * trans)
{
  GstRTSPStreamTransportPrivate *priv;

  priv = trans->priv;

  if (priv->keep_alive)
    priv->keep_alive (priv->ka_user_data);
}

/**
 * gst_rtsp_stream_transport_message_sent:
 * @trans: a #GstRTSPStreamTransport
 *
 * Signal the installed message_sent / message_sent_full callback for @trans.
 *
 * Since: 1.16
 */
void
gst_rtsp_stream_transport_message_sent (GstRTSPStreamTransport * trans)
{
  GstRTSPStreamTransportPrivate *priv;

  priv = trans->priv;

  if (priv->message_sent_full)
    priv->message_sent_full (trans, priv->msf_user_data);
  if (priv->message_sent)
    priv->message_sent (priv->ms_user_data);
}

/**
 * gst_rtsp_stream_transport_recv_data:
 * @trans: a #GstRTSPStreamTransport
 * @channel: a channel
 * @buffer: (transfer full): a #GstBuffer
 *
 * Receive @buffer on @channel @trans.
 *
 * Returns: a #GstFlowReturn. Returns GST_FLOW_NOT_LINKED when @channel is not
 *    configured in the transport of @trans.
 */
GstFlowReturn
gst_rtsp_stream_transport_recv_data (GstRTSPStreamTransport * trans,
    guint channel, GstBuffer * buffer)
{
  GstRTSPStreamTransportPrivate *priv;
  const GstRTSPTransport *tr;
  GstFlowReturn res;

  g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);

  priv = trans->priv;
  tr = priv->transport;

  if (tr->interleaved.min == channel) {
    res = gst_rtsp_stream_recv_rtp (priv->stream, buffer);
  } else if (tr->interleaved.max == channel) {
    res = gst_rtsp_stream_recv_rtcp (priv->stream, buffer);
  } else {
    res = GST_FLOW_NOT_LINKED;
  }
  return res;
}

static GstClockTime
get_backlog_item_timestamp (BackLogItem * item)
{
  GstClockTime ret = GST_CLOCK_TIME_NONE;

  if (item->buffer) {
    ret = GST_BUFFER_DTS_OR_PTS (item->buffer);
  } else if (item->buffer_list) {
    g_assert (gst_buffer_list_length (item->buffer_list) > 0);
    ret = GST_BUFFER_DTS_OR_PTS (gst_buffer_list_get (item->buffer_list, 0));
  }

  return ret;
}

static GstClockTime
get_first_backlog_timestamp (GstRTSPStreamTransport * trans)
{
  GstRTSPStreamTransportPrivate *priv = trans->priv;
  GstClockTime ret = GST_CLOCK_TIME_NONE;
  guint i, l;

  l = gst_vec_deque_get_length (priv->items);

  for (i = 0; i < l; i++) {
    BackLogItem *item = (BackLogItem *)
        gst_vec_deque_peek_nth_struct (priv->items, i);

    if (item->is_rtp) {
      ret = get_backlog_item_timestamp (item);
      break;
    }
  }

  return ret;
}

/* Not MT-safe, caller should ensure consistent locking (see
 * gst_rtsp_stream_transport_lock_backlog()). Ownership
 * of @buffer and @buffer_list is transfered to the transport */
gboolean
gst_rtsp_stream_transport_backlog_push (GstRTSPStreamTransport * trans,
    GstBuffer * buffer, GstBufferList * buffer_list, gboolean is_rtp)
{
  gboolean ret = TRUE;
  BackLogItem item = { 0, };
  GstClockTime item_timestamp;
  GstRTSPStreamTransportPrivate *priv;

  priv = trans->priv;

  if (buffer)
    item.buffer = buffer;
  if (buffer_list)
    item.buffer_list = buffer_list;
  item.is_rtp = is_rtp;

  gst_vec_deque_push_tail_struct (priv->items, &item);

  item_timestamp = get_backlog_item_timestamp (&item);

  if (is_rtp && priv->first_rtp_timestamp != GST_CLOCK_TIME_NONE) {
    GstClockTimeDiff queue_duration;

    g_assert (GST_CLOCK_TIME_IS_VALID (item_timestamp));

    queue_duration = GST_CLOCK_DIFF (priv->first_rtp_timestamp, item_timestamp);

    g_assert (queue_duration >= 0);

    if (queue_duration > MAX_BACKLOG_DURATION &&
        gst_vec_deque_get_length (priv->items) > MAX_BACKLOG_SIZE) {
      ret = FALSE;
    }
  } else if (is_rtp) {
    priv->first_rtp_timestamp = item_timestamp;
  }

  return ret;
}

/* Not MT-safe, caller should ensure consistent locking (see
 * gst_rtsp_stream_transport_lock_backlog()). Ownership
 * of @buffer and @buffer_list is transfered back to the caller,
 * if either of those is NULL the underlying object is unreffed */
gboolean
gst_rtsp_stream_transport_backlog_pop (GstRTSPStreamTransport * trans,
    GstBuffer ** buffer, GstBufferList ** buffer_list, gboolean * is_rtp)
{
  BackLogItem *item;
  GstRTSPStreamTransportPrivate *priv;

  g_return_val_if_fail (!gst_rtsp_stream_transport_backlog_is_empty (trans),
      FALSE);

  priv = trans->priv;

  item = (BackLogItem *) gst_vec_deque_pop_head_struct (priv->items);

  priv->first_rtp_timestamp = get_first_backlog_timestamp (trans);

  if (buffer)
    *buffer = item->buffer;
  else if (item->buffer)
    gst_buffer_unref (item->buffer);

  if (buffer_list)
    *buffer_list = item->buffer_list;
  else if (item->buffer_list)
    gst_buffer_list_unref (item->buffer_list);

  if (is_rtp)
    *is_rtp = item->is_rtp;

  return TRUE;
}

/* Not MT-safe, caller should ensure consistent locking.
 * See gst_rtsp_stream_transport_lock_backlog() */
gboolean
gst_rtsp_stream_transport_backlog_peek_is_rtp (GstRTSPStreamTransport * trans)
{
  BackLogItem *item;
  GstRTSPStreamTransportPrivate *priv;

  g_return_val_if_fail (!gst_rtsp_stream_transport_backlog_is_empty (trans),
      FALSE);

  priv = trans->priv;

  item = (BackLogItem *) gst_vec_deque_peek_head_struct (priv->items);

  return item->is_rtp;
}


/* Not MT-safe, caller should ensure consistent locking.
 * See gst_rtsp_stream_transport_lock_backlog() */
gboolean
gst_rtsp_stream_transport_backlog_is_empty (GstRTSPStreamTransport * trans)
{
  return gst_vec_deque_is_empty (trans->priv->items);
}

/* Not MT-safe, caller should ensure consistent locking.
 * See gst_rtsp_stream_transport_lock_backlog() */
void
gst_rtsp_stream_transport_clear_backlog (GstRTSPStreamTransport * trans)
{
  while (!gst_rtsp_stream_transport_backlog_is_empty (trans)) {
    gst_rtsp_stream_transport_backlog_pop (trans, NULL, NULL, NULL);
  }
}

/* Internal API, protects access to the TCP backlog. Safe to
 * call recursively */
void
gst_rtsp_stream_transport_lock_backlog (GstRTSPStreamTransport * trans)
{
  g_rec_mutex_lock (&trans->priv->backlog_lock);
}

/* See gst_rtsp_stream_transport_lock_backlog() */
void
gst_rtsp_stream_transport_unlock_backlog (GstRTSPStreamTransport * trans)
{
  g_rec_mutex_unlock (&trans->priv->backlog_lock);
}
   07070100000060000081A400000000000000000000000169D554BF0000266B000000000000000000000000000000000000003F00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-stream-transport.h    /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/base/base.h>
#include <gst/rtsp/gstrtsprange.h>
#include <gst/rtsp/gstrtspurl.h>

#ifndef __GST_RTSP_STREAM_TRANSPORT_H__
#define __GST_RTSP_STREAM_TRANSPORT_H__

#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

/* types for the media */
#define GST_TYPE_RTSP_STREAM_TRANSPORT              (gst_rtsp_stream_transport_get_type ())
#define GST_IS_RTSP_STREAM_TRANSPORT(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_STREAM_TRANSPORT))
#define GST_IS_RTSP_STREAM_TRANSPORT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_STREAM_TRANSPORT))
#define GST_RTSP_STREAM_TRANSPORT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_STREAM_TRANSPORT, GstRTSPStreamTransportClass))
#define GST_RTSP_STREAM_TRANSPORT(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_STREAM_TRANSPORT, GstRTSPStreamTransport))
#define GST_RTSP_STREAM_TRANSPORT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_STREAM_TRANSPORT, GstRTSPStreamTransportClass))
#define GST_RTSP_STREAM_TRANSPORT_CAST(obj)         ((GstRTSPStreamTransport*)(obj))
#define GST_RTSP_STREAM_TRANSPORT_CLASS_CAST(klass) ((GstRTSPStreamTransportClass*)(klass))

typedef struct _GstRTSPStreamTransport GstRTSPStreamTransport;
typedef struct _GstRTSPStreamTransportClass GstRTSPStreamTransportClass;
typedef struct _GstRTSPStreamTransportPrivate GstRTSPStreamTransportPrivate;

#include "rtsp-stream.h"

/**
 * GstRTSPSendFunc:
 * @buffer: a #GstBuffer
 * @channel: a channel
 * @user_data: user data
 *
 * Function registered with gst_rtsp_stream_transport_set_callbacks() and
 * called when @buffer must be sent on @channel.
 *
 * Returns: %TRUE on success
 */
typedef gboolean (*GstRTSPSendFunc)      (GstBuffer *buffer, guint8 channel, gpointer user_data);

/**
 * GstRTSPSendListFunc:
 * @buffer_list: a #GstBufferList
 * @channel: a channel
 * @user_data: user data
 *
 * Function registered with gst_rtsp_stream_transport_set_callbacks() and
 * called when @buffer_list must be sent on @channel.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.16
 */
typedef gboolean (*GstRTSPSendListFunc)      (GstBufferList *buffer_list, guint8 channel, gpointer user_data);

/**
 * GstRTSPKeepAliveFunc:
 * @user_data: user data
 *
 * Function registered with gst_rtsp_stream_transport_set_keepalive() and called
 * when the stream is active.
 */
typedef void     (*GstRTSPKeepAliveFunc) (gpointer user_data);

/**
 * GstRTSPMessageSentFunc:
 * @user_data: user data
 *
 * Function registered with gst_rtsp_stream_transport_set_message_sent()
 * and called when a message has been sent on the transport.
 */
typedef void     (*GstRTSPMessageSentFunc) (gpointer user_data);

/**
 * GstRTSPMessageSentFuncFull:
 * @user_data: user data
 *
 * Function registered with gst_rtsp_stream_transport_set_message_sent_full()
 * and called when a message has been sent on the transport.
 *
 * Since: 1.18
 */
typedef void     (*GstRTSPMessageSentFuncFull) (GstRTSPStreamTransport *trans, gpointer user_data);

/**
 * GstRTSPStreamTransport:
 * @parent: parent instance
 *
 * A Transport description for a stream
 */
struct _GstRTSPStreamTransport {
  GObject              parent;

  /*< private >*/
  GstRTSPStreamTransportPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

struct _GstRTSPStreamTransportClass {
  GObjectClass parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType                    gst_rtsp_stream_transport_get_type (void);

GST_RTSP_SERVER_API
GstRTSPStreamTransport * gst_rtsp_stream_transport_new           (GstRTSPStream *stream,
                                                                  GstRTSPTransport *tr) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPStream *          gst_rtsp_stream_transport_get_stream    (GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_transport (GstRTSPStreamTransport *trans,
                                                                  GstRTSPTransport * tr);

GST_RTSP_SERVER_API
const GstRTSPTransport * gst_rtsp_stream_transport_get_transport (GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_url       (GstRTSPStreamTransport *trans,
                                                                  const GstRTSPUrl * url);

GST_RTSP_SERVER_API
const GstRTSPUrl *       gst_rtsp_stream_transport_get_url       (GstRTSPStreamTransport *trans);


GST_RTSP_SERVER_API
gchar *                  gst_rtsp_stream_transport_get_rtpinfo   (GstRTSPStreamTransport *trans,
                                                                  GstClockTime start_time) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_callbacks (GstRTSPStreamTransport *trans,
                                                                  GstRTSPSendFunc send_rtp,
                                                                  GstRTSPSendFunc send_rtcp,
                                                                  gpointer user_data,
                                                                  GDestroyNotify  notify);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_list_callbacks (GstRTSPStreamTransport *trans,
                                                                       GstRTSPSendListFunc send_rtp_list,
                                                                       GstRTSPSendListFunc send_rtcp_list,
                                                                       gpointer user_data,
                                                                       GDestroyNotify  notify);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_keepalive (GstRTSPStreamTransport *trans,
                                                                  GstRTSPKeepAliveFunc keep_alive,
                                                                  gpointer user_data,
                                                                  GDestroyNotify  notify);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_message_sent (GstRTSPStreamTransport *trans,
                                                                     GstRTSPMessageSentFunc message_sent,
                                                                     gpointer user_data,
                                                                     GDestroyNotify  notify);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_message_sent_full (GstRTSPStreamTransport *trans,
                                                                          GstRTSPMessageSentFuncFull message_sent,
                                                                          gpointer user_data,
                                                                          GDestroyNotify  notify);
GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_keep_alive    (GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_message_sent  (GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_set_active    (GstRTSPStreamTransport *trans,
                                                                  gboolean active);

GST_RTSP_SERVER_API
void                     gst_rtsp_stream_transport_set_timed_out (GstRTSPStreamTransport *trans,
                                                                  gboolean timedout);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_is_timed_out  (GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_send_rtp      (GstRTSPStreamTransport *trans,
                                                                  GstBuffer *buffer);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_send_rtcp     (GstRTSPStreamTransport *trans,
                                                                  GstBuffer *buffer);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_send_rtp_list (GstRTSPStreamTransport *trans,
                                                                  GstBufferList *buffer_list);

GST_RTSP_SERVER_API
gboolean                 gst_rtsp_stream_transport_send_rtcp_list(GstRTSPStreamTransport *trans,
                                                                  GstBufferList *buffer_list);

GST_RTSP_SERVER_API
GstFlowReturn            gst_rtsp_stream_transport_recv_data     (GstRTSPStreamTransport *trans,
                                                                  guint channel, GstBuffer *buffer);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPStreamTransport, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_STREAM_TRANSPORT_H__ */
 07070100000061000081A400000000000000000000000169D554BF0002B6B0000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-stream.c  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 * Copyright (C) 2015 Centricular Ltd
 *     Author: Sebastian Dröge <sebastian@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-stream
 * @short_description: A media stream
 * @see_also: #GstRTSPMedia
 *
 * The #GstRTSPStream object manages the data transport for one stream. It
 * is created from a payloader element and a source pad that produce the RTP
 * packets for the stream.
 *
 * With gst_rtsp_stream_join_bin() the streaming elements are added to the bin
 * and rtpbin. gst_rtsp_stream_leave_bin() removes the elements again.
 *
 * The #GstRTSPStream will use the configured addresspool, as set with
 * gst_rtsp_stream_set_address_pool(), to allocate multicast addresses for the
 * stream. With gst_rtsp_stream_get_multicast_address() you can get the
 * configured address.
 *
 * With gst_rtsp_stream_get_server_port () you can get the port that the server
 * will use to receive RTCP. This is the part that the clients will use to send
 * RTCP to.
 *
 * With gst_rtsp_stream_add_transport() destinations can be added where the
 * stream should be sent to. Use gst_rtsp_stream_remove_transport() to remove
 * the destination again.
 *
 * Each #GstRTSPStreamTransport spawns one queue that will serve as a backlog of a
 * controllable maximum size when the reflux from the TCP connection's backpressure
 * starts spilling all over.
 *
 * Unlike the backlog in rtspconnection, which we have decided should only contain
 * at most one RTP and one RTCP data message in order to allow control messages to
 * go through unobstructed, this backlog only consists of data messages, allowing
 * us to fill it up without concern.
 *
 * When multiple TCP transports exist, for example in the context of a shared media,
 * we only pop samples from our appsinks when at least one of the transports doesn't
 * experience back pressure: this allows us to pace our sample popping to the speed
 * of the fastest client.
 *
 * When a sample is popped, it is either sent directly on transports that don't
 * experience backpressure, or queued on the transport's backlog otherwise. Samples
 * are then popped from that backlog when the transport reports it has sent the message.
 *
 * Once the backlog reaches an overly large duration, the transport is dropped as
 * the client was deemed too slow.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <gio/gio.h>

#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>

#include <gst/rtp/gstrtpbuffer.h>

#include "rtsp-stream.h"
#include "rtsp-server-internal.h"

struct _GstRTSPStreamPrivate
{
  GMutex lock;
  guint idx;
  /* Only one pad is ever set */
  GstPad *srcpad, *sinkpad;
  GstElement *payloader;
  guint buffer_size;
  GstBin *joined_bin;

  /* TRUE if this stream is running on
   * the client side of an RTSP link (for RECORD) */
  gboolean client_side;
  gchar *control;

  /* TRUE if stream is complete. This means that the receiver and the sender
   * parts are present in the stream. */
  gboolean is_complete;
  GstRTSPProfile profiles;
  GstRTSPLowerTrans allowed_protocols;
  GstRTSPLowerTrans configured_protocols;

  /* pads on the rtpbin */
  GstPad *send_rtp_sink;
  GstPad *recv_rtp_src;
  GstPad *recv_sink[2];
  GstPad *send_src[2];

  /* the RTPSession object */
  GObject *session;

  /* SRTP encoder/decoder */
  GstElement *srtpenc;
  GstElement *srtpdec;
  GHashTable *keys;

  /* for UDP unicast */
  GstElement *udpsrc_v4[2];
  GstElement *udpsrc_v6[2];
  GstElement *udpqueue[2];
  GstElement *udpsink[2];
  GSocket *socket_v4[2];
  GSocket *socket_v6[2];

  /* for UDP multicast */
  GstElement *mcast_udpsrc_v4[2];
  GstElement *mcast_udpsrc_v6[2];
  GstElement *mcast_udpqueue[2];
  GstElement *mcast_udpsink[2];
  GSocket *mcast_socket_v4[2];
  GSocket *mcast_socket_v6[2];
  GList *mcast_clients;

  /* for TCP transport */
  GstElement *appsrc[2];
  GstClockTime appsrc_base_time[2];
  GstElement *appqueue[2];
  GstElement *appsink[2];

  GstElement *tee[2];
  GstElement *funnel[2];

  /* retransmission */
  GstElement *rtxsend;
  GstElement *rtxreceive;
  guint rtx_pt;
  GstClockTime rtx_time;

  /* rate control */
  gboolean do_rate_control;

  /* Forward Error Correction with RFC 5109 */
  GstElement *ulpfec_decoder;
  GstElement *ulpfec_encoder;
  guint ulpfec_pt;
  gboolean ulpfec_enabled;
  guint ulpfec_percentage;

  /* pool used to manage unicast and multicast addresses */
  GstRTSPAddressPool *pool;

  /* unicast server addr/port */
  GstRTSPAddress *server_addr_v4;
  GstRTSPAddress *server_addr_v6;

  /* multicast addresses */
  GstRTSPAddress *mcast_addr_v4;
  GstRTSPAddress *mcast_addr_v6;

  gchar *multicast_iface;
  guint max_mcast_ttl;
  gboolean bind_mcast_address;

  /* the caps of the stream */
  gulong caps_sig;
  GstCaps *caps;

  /* transports we stream to */
  guint n_active;
  GList *transports;
  guint transports_cookie;
  GPtrArray *tr_cache;
  guint tr_cache_cookie;
  guint n_tcp_transports;
  gboolean have_buffer[2];

  gint dscp_qos;

  /* Sending logic for TCP */
  GThread *send_thread;
  GCond send_cond;
  GMutex send_lock;
  /* @send_lock is released when pushing data out, we use
   * a cookie to decide whether we should wait on @send_cond
   * before checking the transports' backlogs again
   */
  guint send_cookie;
  /* Used to control shutdown of @send_thread */
  gboolean continue_sending;

  /* stream blocking */
  gulong blocked_id[2];
  gboolean blocking;

  /* current stream postion */
  GstClockTime position;

  /* pt->caps map for RECORD streams */
  GHashTable *ptmap;

  GstRTSPPublishClockMode publish_clock_mode;
  GThreadPool *send_pool;

  /* Used to provide accurate rtpinfo when the stream is blocking */
  gboolean blocked_buffer;
  guint32 blocked_seqnum;
  guint32 blocked_rtptime;
  GstClockTime blocked_running_time;
  gint blocked_clock_rate;

  /* Whether we should send and receive RTCP */
  gboolean enable_rtcp;

  /* blocking early rtcp packets */
  GstPad *block_early_rtcp_pad;
  gulong block_early_rtcp_probe;
  GstPad *block_early_rtcp_pad_ipv6;
  gulong block_early_rtcp_probe_ipv6;

  /* set to drop delta units in blocking pad */
  gboolean drop_delta_units;

  /* used to indicate that the drop probe has dropped a buffer and should be
   * removed */
  gboolean remove_drop_probe;

};

#define DEFAULT_CONTROL         NULL
#define DEFAULT_PROFILES        GST_RTSP_PROFILE_AVP
#define DEFAULT_PROTOCOLS       GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
                                        GST_RTSP_LOWER_TRANS_TCP
#define DEFAULT_MAX_MCAST_TTL   255
#define DEFAULT_BIND_MCAST_ADDRESS FALSE
#define DEFAULT_DO_RATE_CONTROL TRUE
#define DEFAULT_ENABLE_RTCP TRUE

enum
{
  PROP_0,
  PROP_CONTROL,
  PROP_PROFILES,
  PROP_PROTOCOLS,
  PROP_LAST
};

enum
{
  SIGNAL_NEW_RTP_ENCODER,
  SIGNAL_NEW_RTCP_ENCODER,
  SIGNAL_NEW_RTP_RTCP_DECODER,
  SIGNAL_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_stream_debug);
#define GST_CAT_DEFAULT rtsp_stream_debug

static GQuark ssrc_stream_map_key;

static void gst_rtsp_stream_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_stream_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);

static void gst_rtsp_stream_finalize (GObject * obj);

static gboolean
update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
    gboolean add);

static guint gst_rtsp_stream_signals[SIGNAL_LAST] = { 0 };

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPStream, gst_rtsp_stream, G_TYPE_OBJECT);

static void
gst_rtsp_stream_class_init (GstRTSPStreamClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_stream_get_property;
  gobject_class->set_property = gst_rtsp_stream_set_property;
  gobject_class->finalize = gst_rtsp_stream_finalize;

  g_object_class_install_property (gobject_class, PROP_CONTROL,
      g_param_spec_string ("control", "Control",
          "The control string for this stream", DEFAULT_CONTROL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROFILES,
      g_param_spec_flags ("profiles", "Profiles",
          "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
          DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
      g_param_spec_flags ("protocols", "Protocols",
          "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
          DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_rtsp_stream_signals[SIGNAL_NEW_RTP_ENCODER] =
      g_signal_new ("new-rtp-encoder", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);

  gst_rtsp_stream_signals[SIGNAL_NEW_RTCP_ENCODER] =
      g_signal_new ("new-rtcp-encoder", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);

  gst_rtsp_stream_signals[SIGNAL_NEW_RTP_RTCP_DECODER] =
      g_signal_new ("new-rtp-rtcp-decoder", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);

  GST_DEBUG_CATEGORY_INIT (rtsp_stream_debug, "rtspstream", 0, "GstRTSPStream");

  ssrc_stream_map_key = g_quark_from_static_string ("GstRTSPServer.stream");
}

static void
gst_rtsp_stream_init (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = gst_rtsp_stream_get_instance_private (stream);

  GST_DEBUG ("new stream %p", stream);

  stream->priv = priv;

  priv->dscp_qos = -1;
  priv->control = g_strdup (DEFAULT_CONTROL);
  priv->profiles = DEFAULT_PROFILES;
  priv->allowed_protocols = DEFAULT_PROTOCOLS;
  priv->configured_protocols = 0;
  priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
  priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
  priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
  priv->do_rate_control = DEFAULT_DO_RATE_CONTROL;
  priv->enable_rtcp = DEFAULT_ENABLE_RTCP;

  g_mutex_init (&priv->lock);

  priv->continue_sending = TRUE;
  priv->send_cookie = 0;
  g_cond_init (&priv->send_cond);
  g_mutex_init (&priv->send_lock);

  priv->keys = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, (GDestroyNotify) gst_caps_unref);
  priv->ptmap = g_hash_table_new_full (NULL, NULL, NULL,
      (GDestroyNotify) gst_caps_unref);
  priv->send_pool = NULL;
  priv->block_early_rtcp_pad = NULL;
  priv->block_early_rtcp_probe = 0;
  priv->block_early_rtcp_pad_ipv6 = NULL;
  priv->block_early_rtcp_probe_ipv6 = 0;
  priv->drop_delta_units = FALSE;
  priv->remove_drop_probe = FALSE;
}

typedef struct _UdpClientAddrInfo UdpClientAddrInfo;

struct _UdpClientAddrInfo
{
  gchar *address;
  guint rtp_port;
  guint add_count;              /* how often this address has been added */
};

static void
free_mcast_client (gpointer data)
{
  UdpClientAddrInfo *client = data;

  g_free (client->address);
  g_free (client);
}

static void
gst_rtsp_stream_finalize (GObject * obj)
{
  GstRTSPStream *stream;
  GstRTSPStreamPrivate *priv;
  guint i;

  stream = GST_RTSP_STREAM (obj);
  priv = stream->priv;

  GST_DEBUG ("finalize stream %p", stream);

  /* we really need to be unjoined now */
  g_return_if_fail (priv->joined_bin == NULL);

  if (priv->send_pool)
    g_thread_pool_free (priv->send_pool, TRUE, TRUE);
  if (priv->mcast_addr_v4)
    gst_rtsp_address_free (priv->mcast_addr_v4);
  if (priv->mcast_addr_v6)
    gst_rtsp_address_free (priv->mcast_addr_v6);
  if (priv->server_addr_v4)
    gst_rtsp_address_free (priv->server_addr_v4);
  if (priv->server_addr_v6)
    gst_rtsp_address_free (priv->server_addr_v6);
  if (priv->pool)
    g_object_unref (priv->pool);
  if (priv->rtxsend)
    g_object_unref (priv->rtxsend);
  if (priv->rtxreceive)
    g_object_unref (priv->rtxreceive);
  if (priv->ulpfec_encoder)
    gst_object_unref (priv->ulpfec_encoder);
  if (priv->ulpfec_decoder)
    gst_object_unref (priv->ulpfec_decoder);

  for (i = 0; i < 2; i++) {
    if (priv->socket_v4[i])
      g_object_unref (priv->socket_v4[i]);
    if (priv->socket_v6[i])
      g_object_unref (priv->socket_v6[i]);
    if (priv->mcast_socket_v4[i])
      g_object_unref (priv->mcast_socket_v4[i]);
    if (priv->mcast_socket_v6[i])
      g_object_unref (priv->mcast_socket_v6[i]);
  }

  g_free (priv->multicast_iface);
  g_list_free_full (priv->mcast_clients, (GDestroyNotify) free_mcast_client);

  gst_object_unref (priv->payloader);
  if (priv->srcpad)
    gst_object_unref (priv->srcpad);
  if (priv->sinkpad)
    gst_object_unref (priv->sinkpad);
  g_free (priv->control);
  g_mutex_clear (&priv->lock);

  g_hash_table_unref (priv->keys);
  g_hash_table_destroy (priv->ptmap);

  g_mutex_clear (&priv->send_lock);
  g_cond_clear (&priv->send_cond);

  if (priv->block_early_rtcp_probe != 0) {
    gst_pad_remove_probe
        (priv->block_early_rtcp_pad, priv->block_early_rtcp_probe);
    gst_object_unref (priv->block_early_rtcp_pad);
  }

  if (priv->block_early_rtcp_probe_ipv6 != 0) {
    gst_pad_remove_probe
        (priv->block_early_rtcp_pad_ipv6, priv->block_early_rtcp_probe_ipv6);
    gst_object_unref (priv->block_early_rtcp_pad_ipv6);
  }

  G_OBJECT_CLASS (gst_rtsp_stream_parent_class)->finalize (obj);
}

static void
gst_rtsp_stream_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPStream *stream = GST_RTSP_STREAM (object);

  switch (propid) {
    case PROP_CONTROL:
      g_value_take_string (value, gst_rtsp_stream_get_control (stream));
      break;
    case PROP_PROFILES:
      g_value_set_flags (value, gst_rtsp_stream_get_profiles (stream));
      break;
    case PROP_PROTOCOLS:
      g_value_set_flags (value, gst_rtsp_stream_get_protocols (stream));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_stream_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPStream *stream = GST_RTSP_STREAM (object);

  switch (propid) {
    case PROP_CONTROL:
      gst_rtsp_stream_set_control (stream, g_value_get_string (value));
      break;
    case PROP_PROFILES:
      gst_rtsp_stream_set_profiles (stream, g_value_get_flags (value));
      break;
    case PROP_PROTOCOLS:
      gst_rtsp_stream_set_protocols (stream, g_value_get_flags (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

/**
 * gst_rtsp_stream_new:
 * @idx: an index
 * @pad: a #GstPad
 * @payloader: a #GstElement
 *
 * Create a new media stream with index @idx that handles RTP data on
 * @pad and has a payloader element @payloader if @pad is a source pad
 * or a depayloader element @payloader if @pad is a sink pad.
 *
 * Returns: (transfer full): a new #GstRTSPStream
 */
GstRTSPStream *
gst_rtsp_stream_new (guint idx, GstElement * payloader, GstPad * pad)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPStream *stream;

  g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
  g_return_val_if_fail (GST_IS_PAD (pad), NULL);

  stream = g_object_new (GST_TYPE_RTSP_STREAM, NULL);
  priv = stream->priv;
  priv->idx = idx;
  priv->payloader = gst_object_ref (payloader);
  if (GST_PAD_IS_SRC (pad))
    priv->srcpad = gst_object_ref (pad);
  else
    priv->sinkpad = gst_object_ref (pad);

  return stream;
}

/**
 * gst_rtsp_stream_get_index:
 * @stream: a #GstRTSPStream
 *
 * Get the stream index.
 *
 * Return: the stream index.
 */
guint
gst_rtsp_stream_get_index (GstRTSPStream * stream)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);

  return stream->priv->idx;
}

/**
 * gst_rtsp_stream_get_pt:
 * @stream: a #GstRTSPStream
 *
 * Get the stream payload type.
 *
 * Return: the stream payload type.
 */
guint
gst_rtsp_stream_get_pt (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  guint pt;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);

  priv = stream->priv;

  g_object_get (G_OBJECT (priv->payloader), "pt", &pt, NULL);

  return pt;
}

/**
 * gst_rtsp_stream_get_srcpad:
 * @stream: a #GstRTSPStream
 *
 * Get the srcpad associated with @stream.
 *
 * Returns: (transfer full) (nullable): the srcpad. Unref after usage.
 */
GstPad *
gst_rtsp_stream_get_srcpad (GstRTSPStream * stream)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  if (!stream->priv->srcpad)
    return NULL;

  return gst_object_ref (stream->priv->srcpad);
}

/**
 * gst_rtsp_stream_get_sinkpad:
 * @stream: a #GstRTSPStream
 *
 * Get the sinkpad associated with @stream.
 *
 * Returns: (transfer full) (nullable): the sinkpad. Unref after usage.
 */
GstPad *
gst_rtsp_stream_get_sinkpad (GstRTSPStream * stream)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  if (!stream->priv->sinkpad)
    return NULL;

  return gst_object_ref (stream->priv->sinkpad);
}

/**
 * gst_rtsp_stream_get_control:
 * @stream: a #GstRTSPStream
 *
 * Get the control string to identify this stream.
 *
 * Returns: (transfer full) (nullable): the control string. g_free() after usage.
 */
gchar *
gst_rtsp_stream_get_control (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((result = g_strdup (priv->control)) == NULL)
    result = g_strdup_printf ("stream=%u", priv->idx);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_stream_set_control:
 * @stream: a #GstRTSPStream
 * @control: (nullable): a control string
 *
 * Set the control string in @stream.
 */
void
gst_rtsp_stream_set_control (GstRTSPStream * stream, const gchar * control)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  g_free (priv->control);
  priv->control = g_strdup (control);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_has_control:
 * @stream: a #GstRTSPStream
 * @control: (nullable): a control string
 *
 * Check if @stream has the control string @control.
 *
 * Returns: %TRUE is @stream has @control as the control string
 */
gboolean
gst_rtsp_stream_has_control (GstRTSPStream * stream, const gchar * control)
{
  GstRTSPStreamPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if (priv->control)
    res = (g_strcmp0 (priv->control, control) == 0);
  else {
    guint streamid;

    if (sscanf (control, "stream=%u", &streamid) > 0)
      res = (streamid == priv->idx);
    else
      res = FALSE;
  }
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_mtu:
 * @stream: a #GstRTSPStream
 * @mtu: a new MTU
 *
 * Configure the mtu in the payloader of @stream to @mtu.
 */
void
gst_rtsp_stream_set_mtu (GstRTSPStream * stream, guint mtu)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  GST_LOG_OBJECT (stream, "set MTU %u", mtu);

  g_object_set (G_OBJECT (priv->payloader), "mtu", mtu, NULL);
}

/**
 * gst_rtsp_stream_get_mtu:
 * @stream: a #GstRTSPStream
 *
 * Get the configured MTU in the payloader of @stream.
 *
 * Returns: the MTU of the payloader.
 */
guint
gst_rtsp_stream_get_mtu (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  guint mtu;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);

  priv = stream->priv;

  g_object_get (G_OBJECT (priv->payloader), "mtu", &mtu, NULL);

  return mtu;
}

/* Update the dscp qos property on the udp sinks */
static void
update_dscp_qos (GstRTSPStream * stream, GstElement ** udpsink)
{
  GstRTSPStreamPrivate *priv;

  priv = stream->priv;

  if (*udpsink) {
    g_object_set (G_OBJECT (*udpsink), "qos-dscp", priv->dscp_qos, NULL);
  }
}

/**
 * gst_rtsp_stream_set_dscp_qos:
 * @stream: a #GstRTSPStream
 * @dscp_qos: a new dscp qos value (0-63, or -1 to disable)
 *
 * Configure the dscp qos of the outgoing sockets to @dscp_qos.
 */
void
gst_rtsp_stream_set_dscp_qos (GstRTSPStream * stream, gint dscp_qos)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  GST_LOG_OBJECT (stream, "set DSCP QoS %d", dscp_qos);

  if (dscp_qos < -1 || dscp_qos > 63) {
    GST_WARNING_OBJECT (stream, "trying to set illegal dscp qos %d", dscp_qos);
    return;
  }

  priv->dscp_qos = dscp_qos;

  update_dscp_qos (stream, priv->udpsink);
}

/**
 * gst_rtsp_stream_get_dscp_qos:
 * @stream: a #GstRTSPStream
 *
 * Get the configured DSCP QoS in of the outgoing sockets.
 *
 * Returns: the DSCP QoS value of the outgoing sockets, or -1 if disbled.
 */
gint
gst_rtsp_stream_get_dscp_qos (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);

  priv = stream->priv;

  return priv->dscp_qos;
}

/**
 * gst_rtsp_stream_is_transport_supported:
 * @stream: a #GstRTSPStream
 * @transport: (transfer none): a #GstRTSPTransport
 *
 * Check if @transport can be handled by stream
 *
 * Returns: %TRUE if @transport can be handled by @stream.
 */
gboolean
gst_rtsp_stream_is_transport_supported (GstRTSPStream * stream,
    GstRTSPTransport * transport)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (transport != NULL, FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if (transport->trans != GST_RTSP_TRANS_RTP)
    goto unsupported_transmode;

  if (!(transport->profile & priv->profiles))
    goto unsupported_profile;

  if (!(transport->lower_transport & priv->allowed_protocols))
    goto unsupported_ltrans;

  g_mutex_unlock (&priv->lock);

  return TRUE;

  /* ERRORS */
unsupported_transmode:
  {
    GST_DEBUG ("unsupported transport mode %d", transport->trans);
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
unsupported_profile:
  {
    GST_DEBUG ("unsupported profile %d", transport->profile);
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
unsupported_ltrans:
  {
    GST_DEBUG ("unsupported lower transport %d", transport->lower_transport);
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_set_profiles:
 * @stream: a #GstRTSPStream
 * @profiles: the new profiles
 *
 * Configure the allowed profiles for @stream.
 */
void
gst_rtsp_stream_set_profiles (GstRTSPStream * stream, GstRTSPProfile profiles)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  priv->profiles = profiles;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_get_profiles:
 * @stream: a #GstRTSPStream
 *
 * Get the allowed profiles of @stream.
 *
 * Returns: a #GstRTSPProfile
 */
GstRTSPProfile
gst_rtsp_stream_get_profiles (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPProfile res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_RTSP_PROFILE_UNKNOWN);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  res = priv->profiles;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_protocols:
 * @stream: a #GstRTSPStream
 * @protocols: the new flags
 *
 * Configure the allowed lower transport for @stream.
 */
void
gst_rtsp_stream_set_protocols (GstRTSPStream * stream,
    GstRTSPLowerTrans protocols)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  priv->allowed_protocols = protocols;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_get_protocols:
 * @stream: a #GstRTSPStream
 *
 * Get the allowed protocols of @stream.
 *
 * Returns: a #GstRTSPLowerTrans
 */
GstRTSPLowerTrans
gst_rtsp_stream_get_protocols (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPLowerTrans res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream),
      GST_RTSP_LOWER_TRANS_UNKNOWN);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  res = priv->allowed_protocols;
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_address_pool:
 * @stream: a #GstRTSPStream
 * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
 *
 * configure @pool to be used as the address pool of @stream.
 */
void
gst_rtsp_stream_set_address_pool (GstRTSPStream * stream,
    GstRTSPAddressPool * pool)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPAddressPool *old;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  GST_LOG_OBJECT (stream, "set address pool %p", pool);

  g_mutex_lock (&priv->lock);
  if ((old = priv->pool) != pool)
    priv->pool = pool ? g_object_ref (pool) : NULL;
  else
    old = NULL;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_object_unref (old);
}

/**
 * gst_rtsp_stream_get_address_pool:
 * @stream: a #GstRTSPStream
 *
 * Get the #GstRTSPAddressPool used as the address pool of @stream.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @stream.
 * g_object_unref() after usage.
 */
GstRTSPAddressPool *
gst_rtsp_stream_get_address_pool (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPAddressPool *result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->pool))
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_stream_set_multicast_iface:
 * @stream: a #GstRTSPStream
 * @multicast_iface: (transfer none) (nullable): a multicast interface name
 *
 * configure @multicast_iface to be used for @stream.
 */
void
gst_rtsp_stream_set_multicast_iface (GstRTSPStream * stream,
    const gchar * multicast_iface)
{
  GstRTSPStreamPrivate *priv;
  gchar *old;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  GST_LOG_OBJECT (stream, "set multicast iface %s",
      GST_STR_NULL (multicast_iface));

  g_mutex_lock (&priv->lock);
  if ((old = priv->multicast_iface) != multicast_iface)
    priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
  else
    old = NULL;
  g_mutex_unlock (&priv->lock);

  if (old)
    g_free (old);
}

/**
 * gst_rtsp_stream_get_multicast_iface:
 * @stream: a #GstRTSPStream
 *
 * Get the multicast interface used for @stream.
 *
 * Returns: (transfer full) (nullable): the multicast interface for @stream.
 * g_free() after usage.
 */
gchar *
gst_rtsp_stream_get_multicast_iface (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gchar *result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->multicast_iface))
    result = g_strdup (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_stream_get_multicast_address:
 * @stream: a #GstRTSPStream
 * @family: the #GSocketFamily
 *
 * Get the multicast address of @stream for @family. The original
 * #GstRTSPAddress is cached and copy is returned, so freeing the return value
 * won't release the address from the pool.
 *
 * Returns: (transfer full) (nullable): the #GstRTSPAddress of @stream
 * or %NULL when no address could be allocated. gst_rtsp_address_free()
 * after usage.
 */
GstRTSPAddress *
gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
    GSocketFamily family)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPAddress *result;
  GstRTSPAddress **addrp;
  GstRTSPAddressFlags flags;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&stream->priv->lock);

  if (family == G_SOCKET_FAMILY_IPV6) {
    flags = GST_RTSP_ADDRESS_FLAG_IPV6;
    addrp = &priv->mcast_addr_v6;
  } else {
    flags = GST_RTSP_ADDRESS_FLAG_IPV4;
    addrp = &priv->mcast_addr_v4;
  }

  if (*addrp == NULL) {
    if (priv->pool == NULL)
      goto no_pool;

    flags |= GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST;

    *addrp = gst_rtsp_address_pool_acquire_address (priv->pool, flags, 2);
    if (*addrp == NULL)
      goto no_address;

    /* FIXME: Also reserve the same port with unicast ANY address, since that's
     * where we are going to bind our socket. Probably loop until we find a port
     * available in both mcast and unicast pools. Maybe GstRTSPAddressPool
     * should do it for us when both GST_RTSP_ADDRESS_FLAG_MULTICAST and
     * GST_RTSP_ADDRESS_FLAG_UNICAST are givent. */
  }
  result = gst_rtsp_address_copy (*addrp);

  g_mutex_unlock (&stream->priv->lock);

  return result;

  /* ERRORS */
no_pool:
  {
    GST_ERROR_OBJECT (stream, "no address pool specified");
    g_mutex_unlock (&stream->priv->lock);
    return NULL;
  }
no_address:
  {
    GST_ERROR_OBJECT (stream, "failed to acquire address from pool");
    g_mutex_unlock (&stream->priv->lock);
    return NULL;
  }
}

/**
 * gst_rtsp_stream_reserve_address:
 * @stream: a #GstRTSPStream
 * @address: an address
 * @port: a port
 * @n_ports: n_ports
 * @ttl: a TTL
 *
 * Reserve @address and @port as the address and port of @stream. The original
 * #GstRTSPAddress is cached and copy is returned, so freeing the return value
 * won't release the address from the pool.
 *
 * Returns: (nullable): the #GstRTSPAddress of @stream or %NULL when
 * the address could not be reserved. gst_rtsp_address_free() after
 * usage.
 */
GstRTSPAddress *
gst_rtsp_stream_reserve_address (GstRTSPStream * stream,
    const gchar * address, guint port, guint n_ports, guint ttl)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPAddress *result;
  GInetAddress *addr;
  GSocketFamily family;
  GstRTSPAddress **addrp;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (address != NULL, NULL);
  g_return_val_if_fail (port > 0, NULL);
  g_return_val_if_fail (n_ports > 0, NULL);
  g_return_val_if_fail (ttl > 0, NULL);

  priv = stream->priv;

  addr = g_inet_address_new_from_string (address);
  if (!addr) {
    GST_ERROR ("failed to get inet addr from %s", address);
    family = G_SOCKET_FAMILY_IPV4;
  } else {
    family = g_inet_address_get_family (addr);
    g_object_unref (addr);
  }

  if (family == G_SOCKET_FAMILY_IPV6)
    addrp = &priv->mcast_addr_v6;
  else
    addrp = &priv->mcast_addr_v4;

  g_mutex_lock (&priv->lock);
  if (*addrp == NULL) {
    GstRTSPAddressPoolResult res;

    if (priv->pool == NULL)
      goto no_pool;

    res = gst_rtsp_address_pool_reserve_address (priv->pool, address,
        port, n_ports, ttl, addrp);
    if (res != GST_RTSP_ADDRESS_POOL_OK)
      goto no_address;

    /* FIXME: Also reserve the same port with unicast ANY address, since that's
     * where we are going to bind our socket. */
  } else {
    if (g_ascii_strcasecmp ((*addrp)->address, address) ||
        (*addrp)->port != port || (*addrp)->n_ports != n_ports ||
        (*addrp)->ttl != ttl)
      goto different_address;
  }
  result = gst_rtsp_address_copy (*addrp);
  g_mutex_unlock (&priv->lock);

  return result;

  /* ERRORS */
no_pool:
  {
    GST_ERROR_OBJECT (stream, "no address pool specified");
    g_mutex_unlock (&priv->lock);
    return NULL;
  }
no_address:
  {
    GST_ERROR_OBJECT (stream, "failed to acquire address %s from pool",
        address);
    g_mutex_unlock (&priv->lock);
    return NULL;
  }
different_address:
  {
    GST_ERROR_OBJECT (stream,
        "address %s is not the same as %s that was already reserved",
        address, (*addrp)->address);
    g_mutex_unlock (&priv->lock);
    return NULL;
  }
}

/* must be called with lock */
static void
set_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
    GSocketFamily family)
{
  const gchar *multisink_socket;

  if (family == G_SOCKET_FAMILY_IPV6)
    multisink_socket = "socket-v6";
  else
    multisink_socket = "socket";

  g_object_set (G_OBJECT (udpsink), multisink_socket, socket, NULL);
}

/* must be called with lock */
static void
set_multicast_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
    GSocketFamily family, const gchar * multicast_iface,
    const gchar * addr_str, gint port, gint mcast_ttl)
{
  set_socket_for_udpsink (udpsink, socket, family);

  if (multicast_iface) {
    GST_INFO ("setting multicast-iface %s", multicast_iface);
    g_object_set (G_OBJECT (udpsink), "multicast-iface", multicast_iface, NULL);
  }

  if (mcast_ttl > 0) {
    GST_INFO ("setting ttl-mc %d", mcast_ttl);
    g_object_set (G_OBJECT (udpsink), "ttl-mc", mcast_ttl, NULL);
  }
}


/* must be called with lock */
static void
set_unicast_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
    GSocketFamily family)
{
  set_socket_for_udpsink (udpsink, socket, family);
}

static guint16
get_port_from_socket (GSocket * socket)
{
  guint16 port;
  GSocketAddress *sockaddr;
  GError *err;

  GST_DEBUG ("socket: %p", socket);
  sockaddr = g_socket_get_local_address (socket, &err);
  if (sockaddr == NULL || !G_IS_INET_SOCKET_ADDRESS (sockaddr)) {
    g_clear_object (&sockaddr);
    GST_ERROR ("failed to get sockaddr: %s", err->message);
    g_error_free (err);
    return 0;
  }

  port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));
  g_object_unref (sockaddr);

  return port;
}


static gboolean
create_and_configure_udpsink (GstRTSPStream * stream, GstElement ** udpsink,
    GSocket * socket_v4, GSocket * socket_v6, gboolean multicast,
    gboolean is_rtp, gint mcast_ttl)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  *udpsink = gst_element_factory_make ("multiudpsink", NULL);

  if (!*udpsink)
    goto no_udp_protocol;

  /* configure sinks */

  g_object_set (G_OBJECT (*udpsink), "close-socket", FALSE, NULL);

  g_object_set (G_OBJECT (*udpsink), "send-duplicates", FALSE, NULL);

  if (is_rtp)
    g_object_set (G_OBJECT (*udpsink), "buffer-size", priv->buffer_size, NULL);
  else
    g_object_set (G_OBJECT (*udpsink), "sync", FALSE, NULL);

  /* Needs to be async for RECORD streams, otherwise we will never go to
   * PLAYING because the sinks will wait for data while the udpsrc can't
   * provide data with timestamps in PAUSED. */
  if (!is_rtp || priv->sinkpad)
    g_object_set (G_OBJECT (*udpsink), "async", FALSE, NULL);

  if (multicast) {
    /* join multicast group when adding clients, so we'll start receiving from it.
     * We cannot rely on the udpsrc to join the group since its socket is always a
     * local unicast one. */
    g_object_set (G_OBJECT (*udpsink), "auto-multicast", TRUE, NULL);

    g_object_set (G_OBJECT (*udpsink), "loop", FALSE, NULL);
  }

  /* update the dscp qos field in the sinks */
  update_dscp_qos (stream, udpsink);

  if (priv->server_addr_v4) {
    GST_DEBUG_OBJECT (stream, "udp IPv4, configure udpsinks");
    set_unicast_socket_for_udpsink (*udpsink, socket_v4, G_SOCKET_FAMILY_IPV4);
  }

  if (priv->server_addr_v6) {
    GST_DEBUG_OBJECT (stream, "udp IPv6, configure udpsinks");
    set_unicast_socket_for_udpsink (*udpsink, socket_v6, G_SOCKET_FAMILY_IPV6);
  }

  if (multicast) {
    gint port;
    if (priv->mcast_addr_v4) {
      GST_DEBUG_OBJECT (stream, "mcast IPv4, configure udpsinks");
      port = get_port_from_socket (socket_v4);
      if (!port)
        goto get_port_failed;
      set_multicast_socket_for_udpsink (*udpsink, socket_v4,
          G_SOCKET_FAMILY_IPV4, priv->multicast_iface,
          priv->mcast_addr_v4->address, port, mcast_ttl);
    }

    if (priv->mcast_addr_v6) {
      GST_DEBUG_OBJECT (stream, "mcast IPv6, configure udpsinks");
      port = get_port_from_socket (socket_v6);
      if (!port)
        goto get_port_failed;
      set_multicast_socket_for_udpsink (*udpsink, socket_v6,
          G_SOCKET_FAMILY_IPV6, priv->multicast_iface,
          priv->mcast_addr_v6->address, port, mcast_ttl);
    }

  }

  return TRUE;

  /* ERRORS */
no_udp_protocol:
  {
    GST_ERROR_OBJECT (stream, "failed to create udpsink element");
    return FALSE;
  }
get_port_failed:
  {
    GST_ERROR_OBJECT (stream, "failed to get udp port");
    return FALSE;
  }
}

/* must be called with lock */
static gboolean
create_and_configure_udpsource (GstElement ** udpsrc, GSocket * socket)
{
  GstStateChangeReturn ret;

  g_assert (socket != NULL);

  *udpsrc = gst_element_factory_make ("udpsrc", NULL);
  if (*udpsrc == NULL)
    goto error;

  g_object_set (G_OBJECT (*udpsrc), "socket", socket, NULL);

  /* The udpsrc cannot do the join because its socket is always a local unicast
   * one. The udpsink sharing the same socket will do it for us. */
  g_object_set (G_OBJECT (*udpsrc), "auto-multicast", FALSE, NULL);

  g_object_set (G_OBJECT (*udpsrc), "loop", FALSE, NULL);

  g_object_set (G_OBJECT (*udpsrc), "close-socket", FALSE, NULL);

  ret = gst_element_set_state (*udpsrc, GST_STATE_READY);
  if (ret == GST_STATE_CHANGE_FAILURE)
    goto error;

  return TRUE;

  /* ERRORS */
error:
  {
    if (*udpsrc) {
      gst_element_set_state (*udpsrc, GST_STATE_NULL);
      g_clear_object (udpsrc);
    }
    return FALSE;
  }
}

static gboolean
alloc_ports_one_family (GstRTSPStream * stream, GSocketFamily family,
    GSocket * socket_out[2], GstRTSPAddress ** server_addr_out,
    gboolean multicast, GstRTSPTransport * ct, gboolean use_transport_settings)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GSocket *rtp_socket = NULL;
  GSocket *rtcp_socket = NULL;
  gint tmp_rtp, tmp_rtcp;
  guint count;
  GList *rejected_addresses = NULL;
  GstRTSPAddress *addr = NULL;
  GInetAddress *inetaddr = NULL;
  GSocketAddress *rtp_sockaddr = NULL;
  GSocketAddress *rtcp_sockaddr = NULL;
  GstRTSPAddressPool *pool;
  gboolean transport_settings_defined = FALSE;

  pool = priv->pool;
  count = 0;

  /* Start with random port */
  tmp_rtp = 0;
  tmp_rtcp = 0;

  if (use_transport_settings) {
    if (!multicast)
      goto no_mcast;

    if (ct == NULL)
      goto no_transport;

    /* multicast and transport specific case */
    if (ct->destination != NULL) {
      tmp_rtp = ct->port.min;
      tmp_rtcp = ct->port.max;

      /* check if the provided address is a multicast address */
      inetaddr = g_inet_address_new_from_string (ct->destination);
      if (inetaddr == NULL)
        goto destination_error;
      if (!g_inet_address_get_is_multicast (inetaddr))
        goto destination_no_mcast;


      if (!priv->bind_mcast_address) {
        g_clear_object (&inetaddr);
        inetaddr = g_inet_address_new_any (family);
      }

      GST_DEBUG_OBJECT (stream, "use transport settings");
      transport_settings_defined = TRUE;
    }
  }

  if (priv->enable_rtcp) {
    rtcp_socket = g_socket_new (family, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    if (!rtcp_socket)
      goto no_udp_protocol;
    g_socket_set_multicast_loopback (rtcp_socket, FALSE);
  }

  /* try to allocate UDP ports, the RTP port should be an even
   * number and the RTCP port (if enabled) should be the next (uneven) port */
again:

  if (rtp_socket == NULL) {
    rtp_socket = g_socket_new (family, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    if (!rtp_socket)
      goto no_udp_protocol;
    g_socket_set_multicast_loopback (rtp_socket, FALSE);
  }

  if (!transport_settings_defined) {
    if ((pool && gst_rtsp_address_pool_has_unicast_addresses (pool))
        || multicast) {
      GstRTSPAddressFlags flags;

      if (addr) {
        g_assert (*server_addr_out == NULL);
        rejected_addresses = g_list_prepend (rejected_addresses, addr);
      }

      if (!pool)
        goto no_pool;

      flags = GST_RTSP_ADDRESS_FLAG_EVEN_PORT;
      if (multicast)
        flags |= GST_RTSP_ADDRESS_FLAG_MULTICAST;
      else
        flags |= GST_RTSP_ADDRESS_FLAG_UNICAST;

      if (family == G_SOCKET_FAMILY_IPV6)
        flags |= GST_RTSP_ADDRESS_FLAG_IPV6;
      else
        flags |= GST_RTSP_ADDRESS_FLAG_IPV4;

      if (*server_addr_out)
        addr = *server_addr_out;
      else
        addr = gst_rtsp_address_pool_acquire_address (pool, flags,
            priv->enable_rtcp ? 2 : 1);

      if (addr == NULL)
        goto no_address;

      tmp_rtp = addr->port;

      g_clear_object (&inetaddr);
      /* FIXME: Does it really work with the IP_MULTICAST_ALL socket option and
       * socket control message set in udpsrc? */
      if (priv->bind_mcast_address || !multicast)
        inetaddr = g_inet_address_new_from_string (addr->address);
      else
        inetaddr = g_inet_address_new_any (family);
    } else {
      if (tmp_rtp != 0) {
        tmp_rtp += 2;
        if (++count > 20)
          goto no_ports;
      }

      if (inetaddr == NULL)
        inetaddr = g_inet_address_new_any (family);
    }
  }

  rtp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtp);
  if (!g_socket_bind (rtp_socket, rtp_sockaddr, FALSE, NULL)) {
    GST_DEBUG_OBJECT (stream, "rtp bind() failed, will try again");
    g_object_unref (rtp_sockaddr);
    if (transport_settings_defined) {
      goto transport_settings_error;
    } else if (*server_addr_out && ((pool
                && gst_rtsp_address_pool_has_unicast_addresses (pool))
            || multicast)) {
      goto no_address;
    } else {
      goto again;
    }
  }
  g_object_unref (rtp_sockaddr);

  rtp_sockaddr = g_socket_get_local_address (rtp_socket, NULL);
  if (rtp_sockaddr == NULL || !G_IS_INET_SOCKET_ADDRESS (rtp_sockaddr)) {
    g_clear_object (&rtp_sockaddr);
    goto socket_error;
  }

  if (!transport_settings_defined) {
    tmp_rtp =
        g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtp_sockaddr));

    /* check if port is even. RFC 3550 encorages the use of an even/odd port
     * pair, however it's not a strict requirement so this check is not done
     * for the client selected ports. */
    if ((tmp_rtp & 1) != 0) {
      /* port not even, close and allocate another */
      tmp_rtp++;
      g_object_unref (rtp_sockaddr);
      g_clear_object (&rtp_socket);
      goto again;
    }
  }
  g_object_unref (rtp_sockaddr);

  /* set port */
  if (priv->enable_rtcp) {
    tmp_rtcp = tmp_rtp + 1;

    rtcp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtcp);
    if (!g_socket_bind (rtcp_socket, rtcp_sockaddr, FALSE, NULL)) {
      GST_DEBUG_OBJECT (stream, "rctp bind() failed, will try again");
      g_object_unref (rtcp_sockaddr);
      g_clear_object (&rtp_socket);
      if (transport_settings_defined)
        goto transport_settings_error;
      goto again;
    }
    g_object_unref (rtcp_sockaddr);
  }

  if (!addr) {
    addr = g_new0 (GstRTSPAddress, 1);
    addr->port = tmp_rtp;
    addr->n_ports = 2;
    if (transport_settings_defined)
      addr->address = g_strdup (ct->destination);
    else
      addr->address = g_inet_address_to_string (inetaddr);
    addr->ttl = ct->ttl;
  }

  g_clear_object (&inetaddr);

  if (multicast && (ct->ttl > 0) && (ct->ttl <= priv->max_mcast_ttl)) {
    GST_DEBUG ("setting mcast ttl to %d", ct->ttl);
    g_socket_set_multicast_ttl (rtp_socket, ct->ttl);
    if (rtcp_socket)
      g_socket_set_multicast_ttl (rtcp_socket, ct->ttl);
  }

  socket_out[0] = rtp_socket;
  socket_out[1] = rtcp_socket;
  *server_addr_out = addr;

  if (priv->enable_rtcp) {
    GST_DEBUG_OBJECT (stream, "allocated address: %s and ports: %d, %d",
        addr->address, tmp_rtp, tmp_rtcp);
  } else {
    GST_DEBUG_OBJECT (stream, "allocated address: %s and port: %d",
        addr->address, tmp_rtp);
  }

  g_list_free_full (rejected_addresses, (GDestroyNotify) gst_rtsp_address_free);

  return TRUE;

  /* ERRORS */
no_mcast:
  {
    GST_ERROR_OBJECT (stream, "failed to allocate UDP ports: wrong transport");
    goto cleanup;
  }
no_transport:
  {
    GST_ERROR_OBJECT (stream, "failed to allocate UDP ports: no transport");
    goto cleanup;
  }
destination_error:
  {
    GST_ERROR_OBJECT (stream,
        "failed to allocate UDP ports: destination error");
    goto cleanup;
  }
destination_no_mcast:
  {
    GST_ERROR_OBJECT (stream,
        "failed to allocate UDP ports: destination not multicast address");
    goto cleanup;
  }
no_udp_protocol:
  {
    GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: protocol error");
    goto cleanup;
  }
no_pool:
  {
    GST_WARNING_OBJECT (stream,
        "failed to allocate UDP ports: no address pool specified");
    goto cleanup;
  }
no_address:
  {
    GST_WARNING_OBJECT (stream, "failed to acquire address from pool");
    goto cleanup;
  }
no_ports:
  {
    GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: no ports");
    goto cleanup;
  }
transport_settings_error:
  {
    GST_ERROR_OBJECT (stream,
        "failed to allocate UDP ports with requested transport settings");
    goto cleanup;
  }
socket_error:
  {
    GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: socket error");
    goto cleanup;
  }
cleanup:
  {
    if (inetaddr)
      g_object_unref (inetaddr);
    g_list_free_full (rejected_addresses,
        (GDestroyNotify) gst_rtsp_address_free);
    if (addr)
      gst_rtsp_address_free (addr);
    if (rtp_socket)
      g_object_unref (rtp_socket);
    if (rtcp_socket)
      g_object_unref (rtcp_socket);
    return FALSE;
  }
}

/* must be called with lock */
static gboolean
add_mcast_client_addr (GstRTSPStream * stream, const gchar * destination,
    guint rtp_port, guint rtcp_port)
{
  GstRTSPStreamPrivate *priv;
  GList *walk;
  UdpClientAddrInfo *client;
  GInetAddress *inet;

  priv = stream->priv;

  if (destination == NULL)
    return FALSE;

  inet = g_inet_address_new_from_string (destination);
  if (inet == NULL)
    goto invalid_address;

  if (!g_inet_address_get_is_multicast (inet)) {
    g_object_unref (inet);
    goto invalid_address;
  }
  g_object_unref (inet);

  for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
    UdpClientAddrInfo *cli = walk->data;

    if ((g_strcmp0 (cli->address, destination) == 0) &&
        (cli->rtp_port == rtp_port)) {
      GST_DEBUG ("requested destination already exists: %s:%u-%u",
          destination, rtp_port, rtcp_port);
      cli->add_count++;
      return TRUE;
    }
  }

  client = g_new0 (UdpClientAddrInfo, 1);
  client->address = g_strdup (destination);
  client->rtp_port = rtp_port;
  client->add_count = 1;
  priv->mcast_clients = g_list_prepend (priv->mcast_clients, client);

  GST_DEBUG ("added mcast client %s:%u-%u", destination, rtp_port, rtcp_port);

  return TRUE;

invalid_address:
  {
    GST_WARNING_OBJECT (stream, "Multicast address is invalid: %s",
        destination);
    return FALSE;
  }
}

/* must be called with lock */
static gboolean
remove_mcast_client_addr (GstRTSPStream * stream, const gchar * destination,
    guint rtp_port, guint rtcp_port)
{
  GstRTSPStreamPrivate *priv;
  GList *walk;

  priv = stream->priv;

  if (destination == NULL)
    goto no_destination;

  for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
    UdpClientAddrInfo *cli = walk->data;

    if ((g_strcmp0 (cli->address, destination) == 0) &&
        (cli->rtp_port == rtp_port)) {
      cli->add_count--;

      if (!cli->add_count) {
        priv->mcast_clients = g_list_remove (priv->mcast_clients, cli);
        free_mcast_client (cli);
      }
      return TRUE;
    }
  }

  GST_WARNING_OBJECT (stream, "Address not found");
  return FALSE;

no_destination:
  {
    GST_WARNING_OBJECT (stream, "No destination has been provided");
    return FALSE;
  }
}


/**
 * gst_rtsp_stream_allocate_udp_sockets:
 * @stream: a #GstRTSPStream
 * @family: protocol family
 * @transport: transport method
 * @use_client_settings: Whether to use client settings or not
 *
 * Allocates RTP and RTCP ports.
 *
 * Returns: %TRUE if the RTP and RTCP sockets have been succeccully allocated.
 */
gboolean
gst_rtsp_stream_allocate_udp_sockets (GstRTSPStream * stream,
    GSocketFamily family, GstRTSPTransport * ct,
    gboolean use_transport_settings)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret = FALSE;
  GstRTSPLowerTrans transport;
  gboolean allocated = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (ct != NULL, FALSE);
  priv = stream->priv;

  transport = ct->lower_transport;

  g_mutex_lock (&priv->lock);

  if (transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
    if (family == G_SOCKET_FAMILY_IPV4 && priv->mcast_socket_v4[0])
      allocated = TRUE;
    else if (family == G_SOCKET_FAMILY_IPV6 && priv->mcast_socket_v6[0])
      allocated = TRUE;
  } else if (transport == GST_RTSP_LOWER_TRANS_UDP) {
    if (family == G_SOCKET_FAMILY_IPV4 && priv->socket_v4[0])
      allocated = TRUE;
    else if (family == G_SOCKET_FAMILY_IPV6 && priv->socket_v6[0])
      allocated = TRUE;
  }

  if (allocated) {
    GST_DEBUG_OBJECT (stream, "Allocated already");
    g_mutex_unlock (&priv->lock);
    return TRUE;
  }

  if (family == G_SOCKET_FAMILY_IPV4) {
    /* IPv4 */
    if (transport == GST_RTSP_LOWER_TRANS_UDP) {
      /* UDP unicast */
      GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_UDP, ipv4");
      ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
          priv->socket_v4, &priv->server_addr_v4, FALSE, ct, FALSE);
    } else {
      /* multicast */
      GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_MCAST_UDP, ipv4");
      ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
          priv->mcast_socket_v4, &priv->mcast_addr_v4, TRUE, ct,
          use_transport_settings);
    }
  } else {
    /* IPv6 */
    if (transport == GST_RTSP_LOWER_TRANS_UDP) {
      /* unicast */
      GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_UDP, ipv6");
      ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
          priv->socket_v6, &priv->server_addr_v6, FALSE, ct, FALSE);

    } else {
      /* multicast */
      GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_MCAST_UDP, ipv6");
      ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
          priv->mcast_socket_v6, &priv->mcast_addr_v6, TRUE, ct,
          use_transport_settings);
    }
  }
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_set_client_side:
 * @stream: a #GstRTSPStream
 * @client_side: TRUE if this #GstRTSPStream is running on the 'client' side of
 * an RTSP connection.
 *
 * Sets the #GstRTSPStream as a 'client side' stream - used for sending
 * streams to an RTSP server via RECORD. This has the practical effect
 * of changing which UDP port numbers are used when setting up the local
 * side of the stream sending to be either the 'server' or 'client' pair
 * of a configured UDP transport.
 */
void
gst_rtsp_stream_set_client_side (GstRTSPStream * stream, gboolean client_side)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  priv->client_side = client_side;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_is_client_side:
 * @stream: a #GstRTSPStream
 *
 * See gst_rtsp_stream_set_client_side()
 *
 * Returns: TRUE if this #GstRTSPStream is client-side.
 */
gboolean
gst_rtsp_stream_is_client_side (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = priv->client_side;
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_get_server_port:
 * @stream: a #GstRTSPStream
 * @server_port: (out): result server port
 * @family: the port family to get
 *
 * Fill @server_port with the port pair used by the server. This function can
 * only be called when @stream has been joined.
 */
void
gst_rtsp_stream_get_server_port (GstRTSPStream * stream,
    GstRTSPRange * server_port, GSocketFamily family)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
  priv = stream->priv;
  g_return_if_fail (priv->joined_bin != NULL);

  if (server_port) {
    server_port->min = 0;
    server_port->max = 0;
  }

  g_mutex_lock (&priv->lock);
  if (family == G_SOCKET_FAMILY_IPV4) {
    if (server_port && priv->server_addr_v4) {
      server_port->min = priv->server_addr_v4->port;
      if (priv->enable_rtcp) {
        server_port->max =
            priv->server_addr_v4->port + priv->server_addr_v4->n_ports - 1;
      }
    }
  } else {
    if (server_port && priv->server_addr_v6) {
      server_port->min = priv->server_addr_v6->port;
      if (priv->enable_rtcp) {
        server_port->max =
            priv->server_addr_v6->port + priv->server_addr_v6->n_ports - 1;
      }
    }
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_get_rtpsession:
 * @stream: a #GstRTSPStream
 *
 * Get the RTP session of this stream.
 *
 * Returns: (transfer full) (nullable): The RTP session of this stream. Unref after usage.
 */
GObject *
gst_rtsp_stream_get_rtpsession (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GObject *session;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((session = priv->session))
    g_object_ref (session);
  g_mutex_unlock (&priv->lock);

  return session;
}

/**
 * gst_rtsp_stream_get_srtp_encoder:
 * @stream: a #GstRTSPStream
 *
 * Get the SRTP encoder for this stream.
 *
 * Returns: (transfer full) (nullable): The SRTP encoder for this stream. Unref after usage.
 */
GstElement *
gst_rtsp_stream_get_srtp_encoder (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstElement *encoder;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((encoder = priv->srtpenc))
    g_object_ref (encoder);
  g_mutex_unlock (&priv->lock);

  return encoder;
}

/**
 * gst_rtsp_stream_get_ssrc:
 * @stream: a #GstRTSPStream
 * @ssrc: (out): result ssrc
 *
 * Get the SSRC used by the RTP session of this stream. This function can only
 * be called when @stream has been joined.
 */
void
gst_rtsp_stream_get_ssrc (GstRTSPStream * stream, guint * ssrc)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
  priv = stream->priv;
  g_return_if_fail (priv->joined_bin != NULL);

  g_mutex_lock (&priv->lock);
  if (ssrc && priv->session)
    g_object_get (priv->session, "internal-ssrc", ssrc, NULL);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_set_retransmission_time:
 * @stream: a #GstRTSPStream
 * @time: a #GstClockTime
 *
 * Set the amount of time to store retransmission packets.
 */
void
gst_rtsp_stream_set_retransmission_time (GstRTSPStream * stream,
    GstClockTime time)
{
  GST_DEBUG_OBJECT (stream, "set retransmission time %" G_GUINT64_FORMAT, time);

  g_mutex_lock (&stream->priv->lock);
  stream->priv->rtx_time = time;
  if (stream->priv->rtxsend)
    g_object_set (stream->priv->rtxsend, "max-size-time",
        GST_TIME_AS_MSECONDS (time), NULL);
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_get_retransmission_time:
 * @stream: a #GstRTSPStream
 *
 * Get the amount of time to store retransmission data.
 *
 * Returns: the amount of time to store retransmission data.
 */
GstClockTime
gst_rtsp_stream_get_retransmission_time (GstRTSPStream * stream)
{
  GstClockTime ret;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);

  g_mutex_lock (&stream->priv->lock);
  ret = stream->priv->rtx_time;
  g_mutex_unlock (&stream->priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_set_retransmission_pt:
 * @stream: a #GstRTSPStream
 * @rtx_pt: a #guint
 *
 * Set the payload type (pt) for retransmission of this stream.
 */
void
gst_rtsp_stream_set_retransmission_pt (GstRTSPStream * stream, guint rtx_pt)
{
  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  GST_DEBUG_OBJECT (stream, "set retransmission pt %u", rtx_pt);

  g_mutex_lock (&stream->priv->lock);
  stream->priv->rtx_pt = rtx_pt;
  if (stream->priv->rtxsend) {
    guint pt = gst_rtsp_stream_get_pt (stream);
    gchar *pt_s = g_strdup_printf ("%d", pt);
    GstStructure *rtx_pt_map = gst_structure_new ("application/x-rtp-pt-map",
        pt_s, G_TYPE_UINT, rtx_pt, NULL);
    g_object_set (stream->priv->rtxsend, "payload-type-map", rtx_pt_map, NULL);
    g_free (pt_s);
    gst_structure_free (rtx_pt_map);
  }
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_get_retransmission_pt:
 * @stream: a #GstRTSPStream
 *
 * Get the payload-type used for retransmission of this stream
 *
 * Returns: The retransmission PT.
 */
guint
gst_rtsp_stream_get_retransmission_pt (GstRTSPStream * stream)
{
  guint rtx_pt;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);

  g_mutex_lock (&stream->priv->lock);
  rtx_pt = stream->priv->rtx_pt;
  g_mutex_unlock (&stream->priv->lock);

  return rtx_pt;
}

/**
 * gst_rtsp_stream_set_buffer_size:
 * @stream: a #GstRTSPStream
 * @size: the buffer size
 *
 * Set the size of the UDP transmission buffer (in bytes)
 * Needs to be set before the stream is joined to a bin.
 *
 * Since: 1.6
 */
void
gst_rtsp_stream_set_buffer_size (GstRTSPStream * stream, guint size)
{
  g_mutex_lock (&stream->priv->lock);
  stream->priv->buffer_size = size;
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_get_buffer_size:
 * @stream: a #GstRTSPStream
 *
 * Get the size of the UDP transmission buffer (in bytes)
 *
 * Returns: the size of the UDP TX buffer
 *
 * Since: 1.6
 */
guint
gst_rtsp_stream_get_buffer_size (GstRTSPStream * stream)
{
  guint buffer_size;

  g_mutex_lock (&stream->priv->lock);
  buffer_size = stream->priv->buffer_size;
  g_mutex_unlock (&stream->priv->lock);

  return buffer_size;
}

/**
 * gst_rtsp_stream_set_max_mcast_ttl:
 * @stream: a #GstRTSPStream
 * @ttl: the new multicast ttl value
 *
 * Set the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: %TRUE if the requested ttl has been set successfully.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_set_max_mcast_ttl (GstRTSPStream * stream, guint ttl)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  g_mutex_lock (&stream->priv->lock);
  if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
    GST_WARNING_OBJECT (stream, "The reqested mcast TTL value is not valid.");
    g_mutex_unlock (&stream->priv->lock);
    return FALSE;
  }
  stream->priv->max_mcast_ttl = ttl;
  g_mutex_unlock (&stream->priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_stream_get_max_mcast_ttl:
 * @stream: a #GstRTSPStream
 *
 * Get the the maximum time-to-live value of outgoing multicast packets.
 *
 * Returns: the maximum time-to-live value of outgoing multicast packets.
 *
 * Since: 1.16
 */
guint
gst_rtsp_stream_get_max_mcast_ttl (GstRTSPStream * stream)
{
  guint ttl;

  g_mutex_lock (&stream->priv->lock);
  ttl = stream->priv->max_mcast_ttl;
  g_mutex_unlock (&stream->priv->lock);

  return ttl;
}

/**
 * gst_rtsp_stream_verify_mcast_ttl:
 * @stream: a #GstRTSPStream
 * @ttl: a requested multicast ttl
 *
 * Check if the requested multicast ttl value is allowed.
 *
 * Returns: TRUE if the requested ttl value is allowed.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_verify_mcast_ttl (GstRTSPStream * stream, guint ttl)
{
  gboolean res = FALSE;

  g_mutex_lock (&stream->priv->lock);
  if ((ttl > 0) && (ttl <= stream->priv->max_mcast_ttl))
    res = TRUE;
  g_mutex_unlock (&stream->priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_bind_mcast_address:
 * @stream: a #GstRTSPStream,
 * @bind_mcast_addr: the new value
 *
 * Decide whether the multicast socket should be bound to a multicast address or
 * INADDR_ANY.
 *
 * Since: 1.16
 */
void
gst_rtsp_stream_set_bind_mcast_address (GstRTSPStream * stream,
    gboolean bind_mcast_addr)
{
  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  g_mutex_lock (&stream->priv->lock);
  stream->priv->bind_mcast_address = bind_mcast_addr;
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_is_bind_mcast_address:
 * @stream: a #GstRTSPStream
 *
 * Check if multicast sockets are configured to be bound to multicast addresses.
 *
 * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_is_bind_mcast_address (GstRTSPStream * stream)
{
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  g_mutex_lock (&stream->priv->lock);
  result = stream->priv->bind_mcast_address;
  g_mutex_unlock (&stream->priv->lock);

  return result;
}

void
gst_rtsp_stream_set_enable_rtcp (GstRTSPStream * stream, gboolean enable)
{
  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  g_mutex_lock (&stream->priv->lock);
  stream->priv->enable_rtcp = enable;
  g_mutex_unlock (&stream->priv->lock);
}

/* executed from streaming thread */
static void
caps_notify (GstPad * pad, GParamSpec * unused, GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstCaps *newcaps, *oldcaps;

  newcaps = gst_pad_get_current_caps (pad);

  GST_INFO ("stream %p received caps %p, %" GST_PTR_FORMAT, stream, newcaps,
      newcaps);

  g_mutex_lock (&priv->lock);
  oldcaps = priv->caps;
  priv->caps = newcaps;
  g_mutex_unlock (&priv->lock);

  if (oldcaps)
    gst_caps_unref (oldcaps);
}

static void
dump_structure (const GstStructure * s)
{
  gchar *sstr;

  sstr = gst_structure_to_string (s);
  GST_INFO ("structure: %s", sstr);
  g_free (sstr);
}

static GstRTSPStreamTransport *
find_transport (GstRTSPStream * stream, const gchar * rtcp_from)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GList *walk;
  GstRTSPStreamTransport *result = NULL;
  const gchar *tmp;
  gchar *dest;
  guint port;

  if (rtcp_from == NULL)
    return NULL;

  tmp = g_strrstr (rtcp_from, ":");
  if (tmp == NULL)
    return NULL;

  port = atoi (tmp + 1);
  dest = g_strndup (rtcp_from, tmp - rtcp_from);

  g_mutex_lock (&priv->lock);
  GST_INFO ("finding %s:%d in %d transports", dest, port,
      g_list_length (priv->transports));

  for (walk = priv->transports; walk; walk = g_list_next (walk)) {
    GstRTSPStreamTransport *trans = walk->data;
    const GstRTSPTransport *tr;
    gint min, max;

    tr = gst_rtsp_stream_transport_get_transport (trans);

    if (priv->client_side) {
      /* In client side mode the 'destination' is the RTSP server, so send
       * to those ports */
      min = tr->server_port.min;
      max = tr->server_port.max;
    } else {
      min = tr->client_port.min;
      max = tr->client_port.max;
    }

    if ((g_ascii_strcasecmp (tr->destination, dest) == 0) &&
        (min == port || max == port)) {
      result = trans;
      break;
    }
  }
  if (result)
    g_object_ref (result);
  g_mutex_unlock (&priv->lock);

  g_free (dest);

  return result;
}

static GstRTSPStreamTransport *
check_transport (GObject * source, GstRTSPStream * stream)
{
  GstStructure *stats;
  GstRTSPStreamTransport *trans;

  /* see if we have a stream to match with the origin of the RTCP packet */
  trans = g_object_get_qdata (source, ssrc_stream_map_key);
  if (trans == NULL) {
    g_object_get (source, "stats", &stats, NULL);
    if (stats) {
      const gchar *rtcp_from;

      dump_structure (stats);

      rtcp_from = gst_structure_get_string (stats, "rtcp-from");
      if ((trans = find_transport (stream, rtcp_from))) {
        GST_INFO ("%p: found transport %p for source  %p", stream, trans,
            source);
        g_object_set_qdata_full (source, ssrc_stream_map_key, trans,
            g_object_unref);
      }
      gst_structure_free (stats);
    }
  }
  return trans;
}


static void
on_new_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GstRTSPStreamTransport *trans;

  GST_INFO ("%p: new source %p", stream, source);

  trans = check_transport (source, stream);

  if (trans)
    GST_INFO ("%p: source %p for transport %p", stream, source, trans);
}

static void
on_ssrc_sdes (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GST_INFO ("%p: new SDES %p", stream, source);
}

static void
on_ssrc_active (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GstRTSPStreamTransport *trans;

  trans = check_transport (source, stream);

  if (trans) {
    GST_INFO ("%p: source %p in transport %p is active", stream, source, trans);
    gst_rtsp_stream_transport_keep_alive (trans);
  }
#ifdef DUMP_STATS
  {
    GstStructure *stats;
    g_object_get (source, "stats", &stats, NULL);
    if (stats) {
      dump_structure (stats);
      gst_structure_free (stats);
    }
  }
#endif
}

static void
on_bye_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GST_INFO ("%p: source %p bye", stream, source);
}

static void
on_bye_timeout (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GstRTSPStreamTransport *trans;

  GST_INFO ("%p: source %p bye timeout", stream, source);

  if ((trans = g_object_get_qdata (source, ssrc_stream_map_key))) {
    gst_rtsp_stream_transport_set_timed_out (trans, TRUE);
    g_object_set_qdata (source, ssrc_stream_map_key, NULL);
  }
}

static void
on_timeout (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GstRTSPStreamTransport *trans;

  GST_INFO ("%p: source %p timeout", stream, source);

  if ((trans = g_object_get_qdata (source, ssrc_stream_map_key))) {
    gst_rtsp_stream_transport_set_timed_out (trans, TRUE);
    g_object_set_qdata (source, ssrc_stream_map_key, NULL);
  }
}

static void
on_new_sender_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
{
  GST_INFO ("%p: new sender source %p", stream, source);
#ifndef DUMP_STATS
  {
    GstStructure *stats;
    g_object_get (source, "stats", &stats, NULL);
    if (stats) {
      dump_structure (stats);
      gst_structure_free (stats);
    }
  }
#endif
}

static void
on_sender_ssrc_active (GObject * session, GObject * source,
    GstRTSPStream * stream)
{
#ifndef DUMP_STATS
  {
    GstStructure *stats;
    g_object_get (source, "stats", &stats, NULL);
    if (stats) {
      dump_structure (stats);
      gst_structure_free (stats);
    }
  }
#endif
}

static void
clear_tr_cache (GstRTSPStreamPrivate * priv)
{
  if (priv->tr_cache)
    g_ptr_array_unref (priv->tr_cache);
  priv->tr_cache = NULL;
}

/* With lock taken */
static gboolean
any_transport_ready (GstRTSPStream * stream, gboolean is_rtp)
{
  gboolean ret = TRUE;
  GstRTSPStreamPrivate *priv = stream->priv;
  GPtrArray *transports;
  gint index;

  transports = priv->tr_cache;

  if (!transports)
    goto done;

  for (index = 0; index < transports->len; index++) {
    GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);
    if (!gst_rtsp_stream_transport_check_back_pressure (tr, is_rtp)) {
      ret = TRUE;
      break;
    } else {
      ret = FALSE;
    }
  }

done:
  return ret;
}

/* Must be called *without* priv->lock */
static gboolean
push_data (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
    GstBuffer * buffer, GstBufferList * buffer_list, gboolean is_rtp)
{
  gboolean send_ret = TRUE;

  if (is_rtp) {
    if (buffer)
      send_ret = gst_rtsp_stream_transport_send_rtp (trans, buffer);
    if (buffer_list)
      send_ret = gst_rtsp_stream_transport_send_rtp_list (trans, buffer_list);
  } else {
    if (buffer)
      send_ret = gst_rtsp_stream_transport_send_rtcp (trans, buffer);
    if (buffer_list)
      send_ret = gst_rtsp_stream_transport_send_rtcp_list (trans, buffer_list);
  }

  return send_ret;
}

/* With priv->lock */
static void
ensure_cached_transports (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GList *walk;

  if (priv->tr_cache_cookie != priv->transports_cookie) {
    clear_tr_cache (priv);
    priv->tr_cache =
        g_ptr_array_new_full (priv->n_tcp_transports, g_object_unref);

    for (walk = priv->transports; walk; walk = g_list_next (walk)) {
      GstRTSPStreamTransport *tr = (GstRTSPStreamTransport *) walk->data;
      const GstRTSPTransport *t = gst_rtsp_stream_transport_get_transport (tr);

      if (t->lower_transport != GST_RTSP_LOWER_TRANS_TCP)
        continue;

      g_ptr_array_add (priv->tr_cache, g_object_ref (tr));
    }
    priv->tr_cache_cookie = priv->transports_cookie;
  }
}

/* Must be called *without* priv->lock */
static void
check_transport_backlog (GstRTSPStream * stream, GstRTSPStreamTransport * trans)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  gboolean send_ret = TRUE;

  gst_rtsp_stream_transport_lock_backlog (trans);

  if (!gst_rtsp_stream_transport_backlog_is_empty (trans)) {
    GstBuffer *buffer;
    GstBufferList *buffer_list;
    gboolean is_rtp;
    gboolean popped GST_UNUSED_ASSERT;

    is_rtp = gst_rtsp_stream_transport_backlog_peek_is_rtp (trans);

    if (!gst_rtsp_stream_transport_check_back_pressure (trans, is_rtp)) {
      popped =
          gst_rtsp_stream_transport_backlog_pop (trans, &buffer, &buffer_list,
          &is_rtp);

      g_assert (popped == TRUE);

      send_ret = push_data (stream, trans, buffer, buffer_list, is_rtp);

      gst_clear_buffer (&buffer);
      gst_clear_buffer_list (&buffer_list);
    }
  }

  gst_rtsp_stream_transport_unlock_backlog (trans);

  if (!send_ret) {
    /* remove transport on send error */
    g_mutex_lock (&priv->lock);
    update_transport (stream, trans, FALSE);
    g_mutex_unlock (&priv->lock);
  }
}

/* Must be called with priv->lock */
static void
send_tcp_message (GstRTSPStream * stream, gint idx)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstAppSink *sink;
  GstSample *sample;
  GstBuffer *buffer;
  GstBufferList *buffer_list;
  gboolean is_rtp;
  GPtrArray *transports;

  if (!priv->have_buffer[idx])
    return;

  ensure_cached_transports (stream);

  is_rtp = (idx == 0);

  if (!any_transport_ready (stream, is_rtp))
    return;

  priv->have_buffer[idx] = FALSE;

  if (priv->appsink[idx] == NULL) {
    /* session expired */
    return;
  }

  sink = GST_APP_SINK (priv->appsink[idx]);
  sample = gst_app_sink_pull_sample (sink);
  if (!sample) {
    return;
  }

  buffer = gst_sample_get_buffer (sample);
  buffer_list = gst_sample_get_buffer_list (sample);

  /* We will get one message-sent notification per buffer or
   * complete buffer-list. We handle each buffer-list as a unit */

  transports = priv->tr_cache;
  if (transports)
    g_ptr_array_ref (transports);

  if (transports) {
    gint index;

    for (index = 0; index < transports->len; index++) {
      GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);
      GstBuffer *buf_ref = NULL;
      GstBufferList *buflist_ref = NULL;

      gst_rtsp_stream_transport_lock_backlog (tr);

      if (buffer)
        buf_ref = gst_buffer_ref (buffer);
      if (buffer_list)
        buflist_ref = gst_buffer_list_ref (buffer_list);

      if (!gst_rtsp_stream_transport_backlog_push (tr,
              buf_ref, buflist_ref, is_rtp)) {
        GST_ERROR_OBJECT (stream,
            "Dropping slow transport %" GST_PTR_FORMAT, tr);
        update_transport (stream, tr, FALSE);
      }

      gst_rtsp_stream_transport_unlock_backlog (tr);
    }
  }
  gst_sample_unref (sample);

  g_mutex_unlock (&priv->lock);

  if (transports) {
    gint index;

    for (index = 0; index < transports->len; index++) {
      GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);

      check_transport_backlog (stream, tr);
    }
    g_ptr_array_unref (transports);
  }

  g_mutex_lock (&priv->lock);
}

static gpointer
send_func (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  g_mutex_lock (&priv->send_lock);

  while (priv->continue_sending) {
    int i;
    int idx = -1;
    guint cookie;

    cookie = priv->send_cookie;
    g_mutex_unlock (&priv->send_lock);

    g_mutex_lock (&priv->lock);

    /* iterate from 1 and down, so we prioritize RTCP over RTP */
    for (i = 1; i >= 0; i--) {
      if (priv->have_buffer[i]) {
        /* send message */
        idx = i;
        break;
      }
    }

    if (idx != -1) {
      send_tcp_message (stream, idx);
    }

    g_mutex_unlock (&priv->lock);

    g_mutex_lock (&priv->send_lock);
    while (cookie == priv->send_cookie && priv->continue_sending) {
      g_cond_wait (&priv->send_cond, &priv->send_lock);
    }
  }

  g_mutex_unlock (&priv->send_lock);

  return NULL;
}

static GstFlowReturn
handle_new_sample (GstAppSink * sink, gpointer user_data)
{
  GstRTSPStream *stream = user_data;
  GstRTSPStreamPrivate *priv = stream->priv;
  int i;

  g_mutex_lock (&priv->lock);

  for (i = 0; i < 2; i++) {
    if (GST_ELEMENT_CAST (sink) == priv->appsink[i]) {
      priv->have_buffer[i] = TRUE;
      break;
    }
  }

  g_mutex_unlock (&priv->lock);

  g_mutex_lock (&priv->send_lock);

  if (priv->send_thread == NULL) {
    priv->send_thread = g_thread_new (NULL, (GThreadFunc) send_func, user_data);
  }

  priv->send_cookie++;
  g_cond_signal (&priv->send_cond);
  g_mutex_unlock (&priv->send_lock);

  return GST_FLOW_OK;
}

static GstAppSinkCallbacks sink_cb = {
  NULL,                         /* not interested in EOS */
  NULL,                         /* not interested in preroll samples */
  handle_new_sample,
};

static GstElement *
get_rtp_encoder (GstRTSPStream * stream, guint session)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  if (priv->srtpenc == NULL) {
    gchar *name;

    name = g_strdup_printf ("srtpenc_%u", session);
    priv->srtpenc = gst_element_factory_make ("srtpenc", name);
    g_free (name);

    g_object_set (priv->srtpenc, "random-key", TRUE, NULL);
  }
  return gst_object_ref (priv->srtpenc);
}

static GstElement *
request_rtp_encoder (GstElement * rtpbin, guint session, GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstElement *oldenc, *enc;
  GstPad *pad;
  gchar *name;

  if (priv->idx != session)
    return NULL;

  GST_DEBUG_OBJECT (stream, "make RTP encoder for session %u", session);

  oldenc = priv->srtpenc;
  enc = get_rtp_encoder (stream, session);
  name = g_strdup_printf ("rtp_sink_%d", session);
  pad = gst_element_request_pad_simple (enc, name);
  g_free (name);
  gst_object_unref (pad);

  if (oldenc == NULL)
    g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTP_ENCODER], 0,
        enc);

  return enc;
}

static GstElement *
request_rtcp_encoder (GstElement * rtpbin, guint session,
    GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstElement *oldenc, *enc;
  GstPad *pad;
  gchar *name;

  if (priv->idx != session)
    return NULL;

  GST_DEBUG_OBJECT (stream, "make RTCP encoder for session %u", session);

  oldenc = priv->srtpenc;
  enc = get_rtp_encoder (stream, session);
  name = g_strdup_printf ("rtcp_sink_%d", session);
  pad = gst_element_request_pad_simple (enc, name);
  g_free (name);
  gst_object_unref (pad);

  if (oldenc == NULL)
    g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTCP_ENCODER], 0,
        enc);

  return enc;
}

static GstCaps *
request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstCaps *caps;

  GST_DEBUG ("request key %08x", ssrc);

  g_mutex_lock (&priv->lock);
  if ((caps = g_hash_table_lookup (priv->keys, GINT_TO_POINTER (ssrc))))
    gst_caps_ref (caps);
  g_mutex_unlock (&priv->lock);

  return caps;
}

static GstElement *
request_rtp_rtcp_decoder (GstElement * rtpbin, guint session,
    GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  if (priv->idx != session)
    return NULL;

  if (priv->srtpdec == NULL) {
    gchar *name;

    name = g_strdup_printf ("srtpdec_%u", session);
    priv->srtpdec = gst_element_factory_make ("srtpdec", name);
    g_free (name);

    g_signal_connect (priv->srtpdec, "request-key",
        (GCallback) request_key, stream);

    g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTP_RTCP_DECODER],
        0, priv->srtpdec);

  }
  return gst_object_ref (priv->srtpdec);
}

/**
 * gst_rtsp_stream_request_aux_sender:
 * @stream: a #GstRTSPStream
 * @sessid: the session id
 *
 * Creating a rtxsend bin
 *
 * Returns: (transfer full) (nullable): a #GstElement.
 *
 * Since: 1.6
 */
GstElement *
gst_rtsp_stream_request_aux_sender (GstRTSPStream * stream, guint sessid)
{
  GstElement *bin;
  GstPad *pad;
  GstStructure *pt_map;
  gchar *name;
  guint pt, rtx_pt;
  gchar *pt_s;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  pt = gst_rtsp_stream_get_pt (stream);
  pt_s = g_strdup_printf ("%u", pt);
  rtx_pt = stream->priv->rtx_pt;

  GST_INFO ("creating rtxsend with pt %u to %u", pt, rtx_pt);

  bin = gst_bin_new (NULL);
  stream->priv->rtxsend = gst_element_factory_make ("rtprtxsend", NULL);
  pt_map = gst_structure_new ("application/x-rtp-pt-map",
      pt_s, G_TYPE_UINT, rtx_pt, NULL);
  g_object_set (stream->priv->rtxsend, "payload-type-map", pt_map,
      "max-size-time", GST_TIME_AS_MSECONDS (stream->priv->rtx_time), NULL);
  g_free (pt_s);
  gst_structure_free (pt_map);
  gst_bin_add (GST_BIN (bin), gst_object_ref (stream->priv->rtxsend));

  pad = gst_element_get_static_pad (stream->priv->rtxsend, "src");
  name = g_strdup_printf ("src_%u", sessid);
  gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
  g_free (name);
  gst_object_unref (pad);

  pad = gst_element_get_static_pad (stream->priv->rtxsend, "sink");
  name = g_strdup_printf ("sink_%u", sessid);
  gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
  g_free (name);
  gst_object_unref (pad);

  return bin;
}

static void
add_rtx_pt (gpointer key, GstCaps * caps, GstStructure * pt_map)
{
  guint pt = GPOINTER_TO_INT (key);
  const GstStructure *s = gst_caps_get_structure (caps, 0);
  const gchar *apt;

  if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "RTX") &&
      (apt = gst_structure_get_string (s, "apt"))) {
    gst_structure_set (pt_map, apt, G_TYPE_UINT, pt, NULL);
  }
}

/* Call with priv->lock taken */
static void
update_rtx_receive_pt_map (GstRTSPStream * stream)
{
  GstStructure *pt_map;

  if (!stream->priv->rtxreceive)
    goto done;

  pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
  g_hash_table_foreach (stream->priv->ptmap, (GHFunc) add_rtx_pt, pt_map);
  g_object_set (stream->priv->rtxreceive, "payload-type-map", pt_map, NULL);
  gst_structure_free (pt_map);

done:
  return;
}

static void
retrieve_ulpfec_pt (gpointer key, GstCaps * caps, GstElement * ulpfec_decoder)
{
  guint pt = GPOINTER_TO_INT (key);
  const GstStructure *s = gst_caps_get_structure (caps, 0);

  if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
    g_object_set (ulpfec_decoder, "pt", pt, NULL);
}

static void
update_ulpfec_decoder_pt (GstRTSPStream * stream)
{
  if (!stream->priv->ulpfec_decoder)
    goto done;

  g_hash_table_foreach (stream->priv->ptmap, (GHFunc) retrieve_ulpfec_pt,
      stream->priv->ulpfec_decoder);

done:
  return;
}

/**
 * gst_rtsp_stream_request_aux_receiver:
 * @stream: a #GstRTSPStream
 * @sessid: the session id
 *
 * Creating a rtxreceive bin
 *
 * Returns: (transfer full) (nullable): a #GstElement.
 *
 * Since: 1.16
 */
GstElement *
gst_rtsp_stream_request_aux_receiver (GstRTSPStream * stream, guint sessid)
{
  GstElement *bin;
  GstPad *pad;
  gchar *name;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  bin = gst_bin_new (NULL);
  stream->priv->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
  update_rtx_receive_pt_map (stream);
  update_ulpfec_decoder_pt (stream);
  gst_bin_add (GST_BIN (bin), gst_object_ref (stream->priv->rtxreceive));

  pad = gst_element_get_static_pad (stream->priv->rtxreceive, "src");
  name = g_strdup_printf ("src_%u", sessid);
  gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
  g_free (name);
  gst_object_unref (pad);

  pad = gst_element_get_static_pad (stream->priv->rtxreceive, "sink");
  name = g_strdup_printf ("sink_%u", sessid);
  gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
  g_free (name);
  gst_object_unref (pad);

  return bin;
}

/**
 * gst_rtsp_stream_set_pt_map:
 * @stream: a #GstRTSPStream
 * @pt: the pt
 * @caps: a #GstCaps
 *
 * Configure a pt map between @pt and @caps.
 */
void
gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  if (!GST_IS_CAPS (caps))
    return;

  g_mutex_lock (&priv->lock);
  g_hash_table_insert (priv->ptmap, GINT_TO_POINTER (pt), gst_caps_ref (caps));
  update_rtx_receive_pt_map (stream);
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_set_publish_clock_mode:
 * @stream: a #GstRTSPStream
 * @mode: the clock publish mode
 *
 * Sets if and how the stream clock should be published according to RFC7273.
 *
 * Since: 1.8
 */
void
gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream,
    GstRTSPPublishClockMode mode)
{
  GstRTSPStreamPrivate *priv;

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  priv->publish_clock_mode = mode;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_get_publish_clock_mode:
 * @stream: a #GstRTSPStream
 *
 * Gets if and how the stream clock should be published according to RFC7273.
 *
 * Returns: The GstRTSPPublishClockMode
 *
 * Since: 1.8
 */
GstRTSPPublishClockMode
gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPPublishClockMode ret;

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = priv->publish_clock_mode;
  g_mutex_unlock (&priv->lock);

  return ret;
}

static GstCaps *
request_pt_map (GstElement * rtpbin, guint session, guint pt,
    GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstCaps *caps = NULL;

  g_mutex_lock (&priv->lock);

  if (priv->idx == session) {
    caps = g_hash_table_lookup (priv->ptmap, GINT_TO_POINTER (pt));
    if (caps) {
      GST_DEBUG ("Stream %p, pt %u: caps %" GST_PTR_FORMAT, stream, pt, caps);
      gst_caps_ref (caps);
    } else {
      GST_DEBUG ("Stream %p, pt %u: no caps", stream, pt);
    }
  }

  g_mutex_unlock (&priv->lock);

  return caps;
}

static void
pad_added (GstElement * rtpbin, GstPad * pad, GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  gchar *name;
  GstPadLinkReturn ret;
  guint sessid;

  GST_DEBUG ("Stream %p added pad %s:%s for pad %s:%s", stream,
      GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));

  name = gst_pad_get_name (pad);
  if (sscanf (name, "recv_rtp_src_%u", &sessid) != 1) {
    g_free (name);
    return;
  }
  g_free (name);

  if (priv->idx != sessid)
    return;

  if (gst_pad_is_linked (priv->sinkpad)) {
    GST_WARNING ("Stream %p: Pad %s:%s is linked already", stream,
        GST_DEBUG_PAD_NAME (priv->sinkpad));
    return;
  }

  /* link the RTP pad to the session manager, it should not really fail unless
   * this is not really an RTP pad */
  ret = gst_pad_link (pad, priv->sinkpad);
  if (ret != GST_PAD_LINK_OK)
    goto link_failed;
  priv->recv_rtp_src = gst_object_ref (pad);

  return;

/* ERRORS */
link_failed:
  {
    GST_ERROR ("Stream %p: Failed to link pads %s:%s and %s:%s", stream,
        GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));
  }
}

static void
on_npt_stop (GstElement * rtpbin, guint session, guint ssrc,
    GstRTSPStream * stream)
{
  /* TODO: What to do here other than this? */
  GST_DEBUG ("Stream %p: Got EOS", stream);
  gst_pad_send_event (stream->priv->sinkpad, gst_event_new_eos ());
}

typedef struct _ProbeData ProbeData;

struct _ProbeData
{
  GstRTSPStream *stream;
  /* existing sink, already linked to tee */
  GstElement *sink1;
  /* new sink, about to be linked */
  GstElement *sink2;
  /* new queue element, that will be linked to tee and sink1 */
  GstElement **queue1;
  /* new queue element, that will be linked to tee and sink2 */
  GstElement **queue2;
  GstPad *sink_pad;
  GstPad *tee_pad;
  guint index;
};

static void
free_cb_data (gpointer user_data)
{
  ProbeData *data = user_data;

  gst_object_unref (data->stream);
  gst_object_unref (data->sink1);
  gst_object_unref (data->sink2);
  gst_object_unref (data->sink_pad);
  gst_object_unref (data->tee_pad);
  g_free (data);
}


static void
create_and_plug_queue_to_unlinked_stream (GstRTSPStream * stream,
    GstElement * tee, GstElement * sink, GstElement ** queue)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstPad *tee_pad;
  GstPad *queue_pad;
  GstPad *sink_pad;

  /* create queue for the new stream */
  *queue = gst_element_factory_make ("queue", NULL);
  g_object_set (*queue, "max-size-buffers", 1, "max-size-bytes", 0,
      "max-size-time", G_GINT64_CONSTANT (0), NULL);
  gst_bin_add (priv->joined_bin, *queue);

  /* link tee to queue */
  tee_pad = gst_element_request_pad_simple (tee, "src_%u");
  queue_pad = gst_element_get_static_pad (*queue, "sink");
  gst_pad_link (tee_pad, queue_pad);
  gst_object_unref (queue_pad);
  gst_object_unref (tee_pad);

  /* link queue to sink */
  queue_pad = gst_element_get_static_pad (*queue, "src");
  sink_pad = gst_element_get_static_pad (sink, "sink");
  gst_pad_link (queue_pad, sink_pad);
  gst_object_unref (queue_pad);
  gst_object_unref (sink_pad);

  gst_element_sync_state_with_parent (sink);
  gst_element_sync_state_with_parent (*queue);
}

static GstPadProbeReturn
create_and_plug_queue_to_linked_stream_probe_cb (GstPad * inpad,
    GstPadProbeInfo * info, gpointer user_data)
{
  GstRTSPStreamPrivate *priv;
  ProbeData *data = user_data;
  GstRTSPStream *stream;
  GstElement **queue1;
  GstElement **queue2;
  GstPad *sink_pad;
  GstPad *tee_pad;
  GstPad *queue_pad;
  guint index;
  gboolean unlinked GST_UNUSED_ASSERT;

  stream = data->stream;
  priv = stream->priv;
  queue1 = data->queue1;
  queue2 = data->queue2;
  sink_pad = data->sink_pad;
  tee_pad = data->tee_pad;
  index = data->index;

  /* unlink tee and the existing sink:
   *   .-----.    .---------.
   *   | tee |    |  sink1  |
   * sink   src->sink       |
   *   '-----'    '---------'
   */
  unlinked = gst_pad_unlink (tee_pad, sink_pad);
  g_assert (unlinked);

  /* add queue to the already existing stream */
  *queue1 = gst_element_factory_make ("queue", NULL);
  g_object_set (*queue1, "max-size-buffers", 1, "max-size-bytes", 0,
      "max-size-time", G_GINT64_CONSTANT (0), NULL);
  gst_bin_add (priv->joined_bin, *queue1);

  /* link tee, queue and sink:
   *   .-----.    .---------.    .---------.
   *   | tee |    |  queue1 |    | sink1   |
   * sink   src->sink      src->sink       |
   *   '-----'    '---------'    '---------'
   */
  queue_pad = gst_element_get_static_pad (*queue1, "sink");
  gst_pad_link (tee_pad, queue_pad);
  gst_object_unref (queue_pad);
  queue_pad = gst_element_get_static_pad (*queue1, "src");
  gst_pad_link (queue_pad, sink_pad);
  gst_object_unref (queue_pad);

  gst_element_sync_state_with_parent (*queue1);

  /* create queue and link it to tee and the new sink */
  create_and_plug_queue_to_unlinked_stream (stream,
      priv->tee[index], data->sink2, queue2);

  /* the final stream:
   *
   *    .-----.    .---------.    .---------.
   *    | tee |    |  queue1 |    | sink1   |
   *  sink   src->sink      src->sink       |
   *    |     |    '---------'    '---------'
   *    |     |    .---------.    .---------.
   *    |     |    |  queue2 |    | sink2   |
   *    |    src->sink      src->sink       |
   *    '-----'    '---------'    '---------'
   */

  return GST_PAD_PROBE_REMOVE;
}

static void
create_and_plug_queue_to_linked_stream (GstRTSPStream * stream,
    GstElement * sink1, GstElement * sink2, guint index, GstElement ** queue1,
    GstElement ** queue2)
{
  ProbeData *data;

  data = g_new0 (ProbeData, 1);
  data->stream = gst_object_ref (stream);
  data->sink1 = gst_object_ref (sink1);
  data->sink2 = gst_object_ref (sink2);
  data->queue1 = queue1;
  data->queue2 = queue2;
  data->index = index;

  data->sink_pad = gst_element_get_static_pad (sink1, "sink");
  g_assert (data->sink_pad);
  data->tee_pad = gst_pad_get_peer (data->sink_pad);
  g_assert (data->tee_pad);

  gst_pad_add_probe (data->tee_pad, GST_PAD_PROBE_TYPE_IDLE,
      create_and_plug_queue_to_linked_stream_probe_cb, data, free_cb_data);
}

static void
plug_udp_sink (GstRTSPStream * stream, GstElement * sink_to_plug,
    GstElement ** queue_to_plug, guint index, gboolean is_mcast)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GstElement *existing_sink;

  if (is_mcast)
    existing_sink = priv->udpsink[index];
  else
    existing_sink = priv->mcast_udpsink[index];

  GST_DEBUG_OBJECT (stream, "plug %s sink", is_mcast ? "mcast" : "udp");

  /* add sink to the bin */
  gst_bin_add (priv->joined_bin, sink_to_plug);

  if (priv->appsink[index] && existing_sink) {

    /* queues are already added for the existing stream, add one for
       the newly added udp stream */
    create_and_plug_queue_to_unlinked_stream (stream, priv->tee[index],
        sink_to_plug, queue_to_plug);

  } else if (priv->appsink[index] || existing_sink) {
    GstElement **queue;
    GstElement *element;

    /* add queue to the already existing stream plus the newly created udp
       stream */
    if (priv->appsink[index]) {
      element = priv->appsink[index];
      queue = &priv->appqueue[index];
    } else {
      element = existing_sink;
      if (is_mcast)
        queue = &priv->udpqueue[index];
      else
        queue = &priv->mcast_udpqueue[index];
    }

    create_and_plug_queue_to_linked_stream (stream, element, sink_to_plug,
        index, queue, queue_to_plug);

  } else {
    GstPad *tee_pad;
    GstPad *sink_pad;

    GST_DEBUG_OBJECT (stream, "creating first stream");

    /* no need to add queues */
    tee_pad = gst_element_request_pad_simple (priv->tee[index], "src_%u");
    sink_pad = gst_element_get_static_pad (sink_to_plug, "sink");
    gst_pad_link (tee_pad, sink_pad);
    gst_object_unref (tee_pad);
    gst_object_unref (sink_pad);
  }

  gst_element_sync_state_with_parent (sink_to_plug);
}

static void
plug_tcp_sink (GstRTSPStream * stream, guint index)
{
  GstRTSPStreamPrivate *priv = stream->priv;

  GST_DEBUG_OBJECT (stream, "plug tcp sink");

  /* add sink to the bin */
  gst_bin_add (priv->joined_bin, priv->appsink[index]);

  if (priv->mcast_udpsink[index] && priv->udpsink[index]) {

    /* queues are already added for the existing stream, add one for
       the newly added tcp stream */
    create_and_plug_queue_to_unlinked_stream (stream,
        priv->tee[index], priv->appsink[index], &priv->appqueue[index]);

  } else if (priv->mcast_udpsink[index] || priv->udpsink[index]) {
    GstElement **queue;
    GstElement *element;

    /* add queue to the already existing stream plus the newly created tcp
       stream */
    if (priv->mcast_udpsink[index]) {
      element = priv->mcast_udpsink[index];
      queue = &priv->mcast_udpqueue[index];
    } else {
      element = priv->udpsink[index];
      queue = &priv->udpqueue[index];
    }

    create_and_plug_queue_to_linked_stream (stream, element,
        priv->appsink[index], index, queue, &priv->appqueue[index]);

  } else {
    GstPad *tee_pad;
    GstPad *sink_pad;

    /* no need to add queues */
    tee_pad = gst_element_request_pad_simple (priv->tee[index], "src_%u");
    sink_pad = gst_element_get_static_pad (priv->appsink[index], "sink");
    gst_pad_link (tee_pad, sink_pad);
    gst_object_unref (tee_pad);
    gst_object_unref (sink_pad);
  }

  gst_element_sync_state_with_parent (priv->appsink[index]);
}

static void
plug_sink (GstRTSPStream * stream, const GstRTSPTransport * transport,
    guint index)
{
  GstRTSPStreamPrivate *priv;
  gboolean is_tcp, is_udp, is_mcast;
  priv = stream->priv;

  is_tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
  is_udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
  is_mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;

  if (is_udp)
    plug_udp_sink (stream, priv->udpsink[index],
        &priv->udpqueue[index], index, FALSE);

  else if (is_mcast)
    plug_udp_sink (stream, priv->mcast_udpsink[index],
        &priv->mcast_udpqueue[index], index, TRUE);

  else if (is_tcp)
    plug_tcp_sink (stream, index);
}

/* must be called with lock */
static gboolean
create_sender_part (GstRTSPStream * stream, const GstRTSPTransport * transport)
{
  GstRTSPStreamPrivate *priv;
  GstPad *pad;
  GstBin *bin;
  gboolean is_tcp, is_udp, is_mcast;
  gint mcast_ttl = 0;
  gint i;

  GST_DEBUG_OBJECT (stream, "create sender part");
  priv = stream->priv;
  bin = priv->joined_bin;

  is_tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
  is_udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
  is_mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;

  if (is_mcast)
    mcast_ttl = transport->ttl;

  GST_DEBUG_OBJECT (stream, "tcp: %d, udp: %d, mcast: %d (ttl: %d)", is_tcp,
      is_udp, is_mcast, mcast_ttl);

  if (is_udp && !priv->server_addr_v4 && !priv->server_addr_v6) {
    GST_WARNING_OBJECT (stream, "no sockets assigned for UDP");
    return FALSE;
  }

  if (is_mcast && !priv->mcast_addr_v4 && !priv->mcast_addr_v6) {
    GST_WARNING_OBJECT (stream, "no sockets assigned for UDP multicast");
    return FALSE;
  }

  if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->payloader),
          "onvif-no-rate-control"))
    g_object_set (priv->payloader, "onvif-no-rate-control",
        !priv->do_rate_control, NULL);

  for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
    gboolean link_tee = FALSE;
    /* For the sender we create this bit of pipeline for both
     * RTP and RTCP (when enabled).
     * Initially there will be only one active transport for
     * the stream, so the pipeline will look like this:
     *
     * .--------.      .-----.    .---------.
     * | rtpbin |      | tee |    |  sink   |
     * |       send->sink   src->sink       |
     * '--------'      '-----'    '---------'
     *
     * For each new transport, the already existing branch will
     * be reconfigured by adding a queue element:
     *
     * .--------.      .-----.    .---------.    .---------.
     * | rtpbin |      | tee |    |  queue  |    | udpsink |
     * |       send->sink   src->sink      src->sink       |
     * '--------'      |     |    '---------'    '---------'
     *                 |     |    .---------.    .---------.
     *                 |     |    |  queue  |    | udpsink |
     *                 |    src->sink      src->sink       |
     *                 |     |    '---------'    '---------'
     *                 |     |    .---------.    .---------.
     *                 |     |    |  queue  |    | appsink |
     *                 |    src->sink      src->sink       |
     *                 '-----'    '---------'    '---------'
     */

    /* Only link the RTP send src if we're going to send RTP, link
     * the RTCP send src always */
    if (!priv->srcpad && i == 0)
      continue;

    if (!priv->tee[i]) {
      /* make tee for RTP/RTCP */
      priv->tee[i] = gst_element_factory_make ("tee", NULL);
      gst_bin_add (bin, priv->tee[i]);
      link_tee = TRUE;
    }

    if (is_udp && !priv->udpsink[i]) {
      /* we create only one pair of udpsinks for IPv4 and IPv6 */
      create_and_configure_udpsink (stream, &priv->udpsink[i],
          priv->socket_v4[i], priv->socket_v6[i], FALSE, (i == 0), mcast_ttl);
      plug_sink (stream, transport, i);
    } else if (is_mcast && !priv->mcast_udpsink[i]) {
      /* we create only one pair of mcast-udpsinks for IPv4 and IPv6 */
      create_and_configure_udpsink (stream, &priv->mcast_udpsink[i],
          priv->mcast_socket_v4[i], priv->mcast_socket_v6[i], TRUE, (i == 0),
          mcast_ttl);
      plug_sink (stream, transport, i);
    } else if (is_tcp && !priv->appsink[i]) {
      /* make appsink */
      priv->appsink[i] = gst_element_factory_make ("appsink", NULL);
      g_object_set (priv->appsink[i], "emit-signals", FALSE, "buffer-list",
          TRUE, "max-buffers", 1, NULL);

      if (i == 0)
        g_object_set (priv->appsink[i], "sync", priv->do_rate_control, NULL);

      /* we need to set sync and preroll to FALSE for the sink to avoid
       * deadlock. This is only needed for sink sending RTCP data. */
      if (i == 1)
        g_object_set (priv->appsink[i], "async", FALSE, "sync", FALSE, NULL);

      gst_app_sink_set_callbacks (GST_APP_SINK_CAST (priv->appsink[i]),
          &sink_cb, stream, NULL);
      plug_sink (stream, transport, i);
    }

    if (link_tee) {
      /* and link to rtpbin send pad */
      gst_element_sync_state_with_parent (priv->tee[i]);
      pad = gst_element_get_static_pad (priv->tee[i], "sink");
      gst_pad_link (priv->send_src[i], pad);
      gst_object_unref (pad);
    }
  }

  return TRUE;
}

/* must be called with lock */
static void
plug_src (GstRTSPStream * stream, GstBin * bin, GstElement * src,
    GstElement * funnel)
{
  GstRTSPStreamPrivate *priv;
  GstPad *pad, *selpad;
  gulong id = 0;

  priv = stream->priv;

  /* add src */
  gst_bin_add (bin, src);

  pad = gst_element_get_static_pad (src, "src");
  if (priv->srcpad) {
    /* block pad so src can't push data while it's not yet linked */
    id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK |
        GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL, NULL);
    /* we set and keep these to playing so that they don't cause NO_PREROLL return
     * values. This is only relevant for PLAY pipelines */
    gst_element_set_state (src, GST_STATE_PLAYING);
    gst_element_set_locked_state (src, TRUE);
  }

  /* and link to the funnel */
  selpad = gst_element_request_pad_simple (funnel, "sink_%u");
  gst_pad_link (pad, selpad);
  if (id != 0)
    gst_pad_remove_probe (pad, id);
  gst_object_unref (pad);
  gst_object_unref (selpad);
}

/* must be called with lock */
static gboolean
create_receiver_part (GstRTSPStream * stream, const GstRTSPTransport *
    transport)
{
  gboolean ret = FALSE;
  GstRTSPStreamPrivate *priv;
  GstPad *pad;
  GstBin *bin;
  gboolean tcp;
  gboolean udp;
  gboolean mcast;
  gboolean secure;
  gint i;
  GstCaps *rtp_caps;
  GstCaps *rtcp_caps;

  GST_DEBUG_OBJECT (stream, "create receiver part");
  priv = stream->priv;
  bin = priv->joined_bin;

  tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
  udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
  mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;
  secure = (priv->profiles & GST_RTSP_PROFILE_SAVP)
      || (priv->profiles & GST_RTSP_PROFILE_SAVPF);

  if (secure) {
    rtp_caps = gst_caps_new_empty_simple ("application/x-srtp");
    rtcp_caps = gst_caps_new_empty_simple ("application/x-srtcp");
  } else {
    rtp_caps = gst_caps_new_empty_simple ("application/x-rtp");
    rtcp_caps = gst_caps_new_empty_simple ("application/x-rtcp");
  }

  GST_DEBUG_OBJECT (stream,
      "RTP caps: %" GST_PTR_FORMAT " RTCP caps: %" GST_PTR_FORMAT, rtp_caps,
      rtcp_caps);

  for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
    /* For the receiver we create this bit of pipeline for both
     * RTP and RTCP (when enabled). We receive RTP/RTCP on appsrc and udpsrc
     * and it is all funneled into the rtpbin receive pad.
     *
     *
     * .--------.     .--------.    .--------.
     * | udpsrc |     | funnel |    | rtpbin |
     * | RTP    src->sink      src->sink     |
     * '--------'     |        |    |        |
     * .--------.     |        |    |        |
     * | appsrc |     |        |    |        |
     * | RTP    src->sink      |    |        |
     * '--------'     '--------'    |        |
     *                              |        |
     * .--------.     .--------.    |        |
     * | udpsrc |     | funnel |    |        |
     * | RTCP   src->sink      src->sink     |
     * '--------'     |        |    '--------'
     * .--------.     |        |
     * | appsrc |     |        |
     * | RTCP   src->sink      |
     * '--------'     '--------'
     */

    if (!priv->sinkpad && i == 0) {
      /* Only connect recv RTP sink if we expect to receive RTP. Connect recv
       * RTCP sink always */
      continue;
    }

    /* make funnel for the RTP/RTCP receivers */
    if (!priv->funnel[i]) {
      priv->funnel[i] = gst_element_factory_make ("funnel", NULL);
      gst_bin_add (bin, priv->funnel[i]);

      pad = gst_element_get_static_pad (priv->funnel[i], "src");
      gst_pad_link (pad, priv->recv_sink[i]);
      gst_object_unref (pad);
    }

    if (udp && !priv->udpsrc_v4[i] && priv->server_addr_v4) {
      GST_DEBUG_OBJECT (stream, "udp IPv4, create and configure udpsources");
      if (!create_and_configure_udpsource (&priv->udpsrc_v4[i],
              priv->socket_v4[i]))
        goto done;

      if (i == 0) {
        g_object_set (priv->udpsrc_v4[i], "caps", rtp_caps, NULL);
      } else {
        g_object_set (priv->udpsrc_v4[i], "caps", rtcp_caps, NULL);

        /* block early rtcp packets, pipeline not ready */
        g_assert (priv->block_early_rtcp_pad == NULL);
        priv->block_early_rtcp_pad = gst_element_get_static_pad
            (priv->udpsrc_v4[i], "src");
        priv->block_early_rtcp_probe = gst_pad_add_probe
            (priv->block_early_rtcp_pad,
            GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL,
            NULL);
      }

      plug_src (stream, bin, priv->udpsrc_v4[i], priv->funnel[i]);
    }

    if (udp && !priv->udpsrc_v6[i] && priv->server_addr_v6) {
      GST_DEBUG_OBJECT (stream, "udp IPv6, create and configure udpsources");
      if (!create_and_configure_udpsource (&priv->udpsrc_v6[i],
              priv->socket_v6[i]))
        goto done;

      if (i == 0) {
        g_object_set (priv->udpsrc_v6[i], "caps", rtp_caps, NULL);
      } else {
        g_object_set (priv->udpsrc_v6[i], "caps", rtcp_caps, NULL);

        /* block early rtcp packets, pipeline not ready */
        g_assert (priv->block_early_rtcp_pad_ipv6 == NULL);
        priv->block_early_rtcp_pad_ipv6 = gst_element_get_static_pad
            (priv->udpsrc_v6[i], "src");
        priv->block_early_rtcp_probe_ipv6 = gst_pad_add_probe
            (priv->block_early_rtcp_pad_ipv6,
            GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL,
            NULL);
      }

      plug_src (stream, bin, priv->udpsrc_v6[i], priv->funnel[i]);
    }

    if (mcast && !priv->mcast_udpsrc_v4[i] && priv->mcast_addr_v4) {
      GST_DEBUG_OBJECT (stream, "mcast IPv4, create and configure udpsources");
      if (!create_and_configure_udpsource (&priv->mcast_udpsrc_v4[i],
              priv->mcast_socket_v4[i]))
        goto done;

      if (i == 0) {
        g_object_set (priv->mcast_udpsrc_v4[i], "caps", rtp_caps, NULL);
      } else {
        g_object_set (priv->mcast_udpsrc_v4[i], "caps", rtcp_caps, NULL);
      }

      plug_src (stream, bin, priv->mcast_udpsrc_v4[i], priv->funnel[i]);
    }

    if (mcast && !priv->mcast_udpsrc_v6[i] && priv->mcast_addr_v6) {
      GST_DEBUG_OBJECT (stream, "mcast IPv6, create and configure udpsources");
      if (!create_and_configure_udpsource (&priv->mcast_udpsrc_v6[i],
              priv->mcast_socket_v6[i]))
        goto done;

      if (i == 0) {
        g_object_set (priv->mcast_udpsrc_v6[i], "caps", rtp_caps, NULL);
      } else {
        g_object_set (priv->mcast_udpsrc_v6[i], "caps", rtcp_caps, NULL);
      }

      plug_src (stream, bin, priv->mcast_udpsrc_v6[i], priv->funnel[i]);
    }

    if (tcp && !priv->appsrc[i]) {
      /* make and add appsrc */
      priv->appsrc[i] = gst_element_factory_make ("appsrc", NULL);
      priv->appsrc_base_time[i] = -1;
      g_object_set (priv->appsrc[i], "format", GST_FORMAT_TIME, "is-live",
          TRUE, NULL);
      plug_src (stream, bin, priv->appsrc[i], priv->funnel[i]);
    }

    gst_element_sync_state_with_parent (priv->funnel[i]);
  }

  ret = TRUE;

done:
  gst_caps_unref (rtp_caps);
  gst_caps_unref (rtcp_caps);
  return ret;
}

gboolean
gst_rtsp_stream_is_tcp_receiver (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret = FALSE;

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = (priv->sinkpad != NULL && priv->appsrc[0] != NULL);
  g_mutex_unlock (&priv->lock);

  return ret;
}

static gboolean
check_mcast_client_addr (GstRTSPStream * stream, const GstRTSPTransport * tr)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GList *walk;

  if (priv->mcast_clients == NULL)
    goto no_addr;

  if (tr == NULL)
    goto no_transport;

  if (tr->destination == NULL)
    goto no_destination;

  for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
    UdpClientAddrInfo *cli = walk->data;

    if ((g_strcmp0 (cli->address, tr->destination) == 0) &&
        (cli->rtp_port == tr->port.min))
      return TRUE;
  }

  return FALSE;

no_addr:
  {
    GST_WARNING_OBJECT (stream, "Adding mcast transport, but no mcast address "
        "has been reserved");
    return FALSE;
  }
no_transport:
  {
    GST_WARNING_OBJECT (stream, "Adding mcast transport, but no transport "
        "has been provided");
    return FALSE;
  }
no_destination:
  {
    GST_WARNING_OBJECT (stream, "Adding mcast transport, but it doesn't match "
        "the reserved address");
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_join_bin:
 * @stream: a #GstRTSPStream
 * @bin: (transfer none): a #GstBin to join
 * @rtpbin: (transfer none): a rtpbin element in @bin
 * @state: the target state of the new elements
 *
 * Join the #GstBin @bin that contains the element @rtpbin.
 *
 * @stream will link to @rtpbin, which must be inside @bin. The elements
 * added to @bin will be set to the state given in @state.
 *
 * Returns: %TRUE on success.
 */
gboolean
gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
    GstElement * rtpbin, GstState state)
{
  GstRTSPStreamPrivate *priv;
  guint idx;
  gchar *name;
  GstPadLinkReturn ret;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
  g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if (priv->joined_bin != NULL)
    goto was_joined;

  /* create a session with the same index as the stream */
  idx = priv->idx;

  GST_INFO ("stream %p joining bin as session %u", stream, idx);

  if (priv->profiles & GST_RTSP_PROFILE_SAVP
      || priv->profiles & GST_RTSP_PROFILE_SAVPF) {
    /* For SRTP */
    g_signal_connect (rtpbin, "request-rtp-encoder",
        (GCallback) request_rtp_encoder, stream);
    g_signal_connect (rtpbin, "request-rtcp-encoder",
        (GCallback) request_rtcp_encoder, stream);
    g_signal_connect (rtpbin, "request-rtp-decoder",
        (GCallback) request_rtp_rtcp_decoder, stream);
    g_signal_connect (rtpbin, "request-rtcp-decoder",
        (GCallback) request_rtp_rtcp_decoder, stream);
  }

  if (priv->sinkpad) {
    g_signal_connect (rtpbin, "request-pt-map",
        (GCallback) request_pt_map, stream);
  }

  /* get pads from the RTP session element for sending and receiving
   * RTP/RTCP*/
  if (priv->srcpad) {
    /* get a pad for sending RTP */
    name = g_strdup_printf ("send_rtp_sink_%u", idx);
    priv->send_rtp_sink = gst_element_request_pad_simple (rtpbin, name);
    g_free (name);

    /* link the RTP pad to the session manager, it should not really fail unless
     * this is not really an RTP pad */
    ret = gst_pad_link (priv->srcpad, priv->send_rtp_sink);
    if (ret != GST_PAD_LINK_OK)
      goto link_failed;

    name = g_strdup_printf ("send_rtp_src_%u", idx);
    priv->send_src[0] = gst_element_get_static_pad (rtpbin, name);
    g_free (name);
  } else {
    /* RECORD case: need to connect our sinkpad from here */
    g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added, stream);
    /* EOS */
    g_signal_connect (rtpbin, "on-npt-stop", (GCallback) on_npt_stop, stream);

    name = g_strdup_printf ("recv_rtp_sink_%u", idx);
    priv->recv_sink[0] = gst_element_request_pad_simple (rtpbin, name);
    g_free (name);
  }

  if (priv->enable_rtcp) {
    name = g_strdup_printf ("send_rtcp_src_%u", idx);
    priv->send_src[1] = gst_element_request_pad_simple (rtpbin, name);
    g_free (name);

    name = g_strdup_printf ("recv_rtcp_sink_%u", idx);
    priv->recv_sink[1] = gst_element_request_pad_simple (rtpbin, name);
    g_free (name);
  }

  /* get the session */
  g_signal_emit_by_name (rtpbin, "get-internal-session", idx, &priv->session);

  g_signal_connect (priv->session, "on-new-ssrc", (GCallback) on_new_ssrc,
      stream);
  g_signal_connect (priv->session, "on-ssrc-sdes", (GCallback) on_ssrc_sdes,
      stream);
  g_signal_connect (priv->session, "on-ssrc-active",
      (GCallback) on_ssrc_active, stream);
  g_signal_connect (priv->session, "on-bye-ssrc", (GCallback) on_bye_ssrc,
      stream);
  g_signal_connect (priv->session, "on-bye-timeout",
      (GCallback) on_bye_timeout, stream);
  g_signal_connect (priv->session, "on-timeout", (GCallback) on_timeout,
      stream);

  /* signal for sender ssrc */
  g_signal_connect (priv->session, "on-new-sender-ssrc",
      (GCallback) on_new_sender_ssrc, stream);
  g_signal_connect (priv->session, "on-sender-ssrc-active",
      (GCallback) on_sender_ssrc_active, stream);

  g_object_set (priv->session, "disable-sr-timestamp", !priv->do_rate_control,
      NULL);

  if (priv->srcpad) {
    /* be notified of caps changes */
    priv->caps_sig = g_signal_connect (priv->send_src[0], "notify::caps",
        (GCallback) caps_notify, stream);
    priv->caps = gst_pad_get_current_caps (priv->send_src[0]);
  }

  priv->joined_bin = bin;
  GST_DEBUG_OBJECT (stream, "successfully joined bin");
  g_mutex_unlock (&priv->lock);

  return TRUE;

  /* ERRORS */
was_joined:
  {
    g_mutex_unlock (&priv->lock);
    return TRUE;
  }
link_failed:
  {
    GST_WARNING ("failed to link stream %u", idx);
    gst_object_unref (priv->send_rtp_sink);
    priv->send_rtp_sink = NULL;
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

static void
clear_element (GstBin * bin, GstElement ** elementptr)
{
  if (*elementptr) {
    gst_element_set_locked_state (*elementptr, FALSE);
    gst_element_set_state (*elementptr, GST_STATE_NULL);
    if (GST_ELEMENT_PARENT (*elementptr))
      gst_bin_remove (bin, *elementptr);
    else
      gst_object_unref (*elementptr);
    *elementptr = NULL;
  }
}

/**
 * gst_rtsp_stream_leave_bin:
 * @stream: a #GstRTSPStream
 * @bin: (transfer none): a #GstBin
 * @rtpbin: (transfer none): a rtpbin #GstElement
 *
 * Remove the elements of @stream from @bin.
 *
 * Return: %TRUE on success.
 */
gboolean
gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
    GstElement * rtpbin)
{
  GstRTSPStreamPrivate *priv;
  gint i;
  GThread *send_thread;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
  g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->send_lock);
  priv->continue_sending = FALSE;
  priv->send_cookie++;
  send_thread = g_steal_pointer (&priv->send_thread);
  g_cond_signal (&priv->send_cond);
  g_mutex_unlock (&priv->send_lock);

  if (send_thread) {
    g_thread_join (send_thread);
  }

  g_mutex_lock (&priv->lock);
  if (priv->joined_bin == NULL)
    goto was_not_joined;
  if (priv->joined_bin != bin)
    goto wrong_bin;

  priv->joined_bin = NULL;

  /* all transports must be removed by now */
  if (priv->transports != NULL)
    goto transports_not_removed;

  if (priv->send_pool) {
    GThreadPool *slask;

    slask = priv->send_pool;
    priv->send_pool = NULL;
    g_mutex_unlock (&priv->lock);
    g_thread_pool_free (slask, TRUE, TRUE);
    g_mutex_lock (&priv->lock);
  }

  clear_tr_cache (priv);

  GST_INFO ("stream %p leaving bin", stream);

  if (priv->srcpad) {
    gst_pad_unlink (priv->srcpad, priv->send_rtp_sink);

    g_signal_handler_disconnect (priv->send_src[0], priv->caps_sig);
    gst_element_release_request_pad (rtpbin, priv->send_rtp_sink);
    gst_object_unref (priv->send_rtp_sink);
    priv->send_rtp_sink = NULL;
  } else if (priv->recv_rtp_src) {
    gst_pad_unlink (priv->recv_rtp_src, priv->sinkpad);
    gst_object_unref (priv->recv_rtp_src);
    priv->recv_rtp_src = NULL;
  }

  for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
    clear_element (bin, &priv->udpsrc_v4[i]);
    clear_element (bin, &priv->udpsrc_v6[i]);
    clear_element (bin, &priv->udpqueue[i]);
    clear_element (bin, &priv->udpsink[i]);

    clear_element (bin, &priv->mcast_udpsrc_v4[i]);
    clear_element (bin, &priv->mcast_udpsrc_v6[i]);
    clear_element (bin, &priv->mcast_udpqueue[i]);
    clear_element (bin, &priv->mcast_udpsink[i]);

    clear_element (bin, &priv->appsrc[i]);
    clear_element (bin, &priv->appqueue[i]);
    clear_element (bin, &priv->appsink[i]);

    clear_element (bin, &priv->tee[i]);
    clear_element (bin, &priv->funnel[i]);

    if (priv->sinkpad || i == 1) {
      gst_element_release_request_pad (rtpbin, priv->recv_sink[i]);
      gst_object_unref (priv->recv_sink[i]);
      priv->recv_sink[i] = NULL;
    }
  }

  if (priv->srcpad) {
    gst_object_unref (priv->send_src[0]);
    priv->send_src[0] = NULL;
  }

  if (priv->enable_rtcp) {
    gst_element_release_request_pad (rtpbin, priv->send_src[1]);
    gst_object_unref (priv->send_src[1]);
    priv->send_src[1] = NULL;
  }

  g_object_unref (priv->session);
  priv->session = NULL;
  if (priv->caps)
    gst_caps_unref (priv->caps);
  priv->caps = NULL;

  if (priv->srtpenc)
    gst_object_unref (priv->srtpenc);
  if (priv->srtpdec)
    gst_object_unref (priv->srtpdec);

  if (priv->mcast_addr_v4)
    gst_rtsp_address_free (priv->mcast_addr_v4);
  priv->mcast_addr_v4 = NULL;
  if (priv->mcast_addr_v6)
    gst_rtsp_address_free (priv->mcast_addr_v6);
  priv->mcast_addr_v6 = NULL;
  if (priv->server_addr_v4)
    gst_rtsp_address_free (priv->server_addr_v4);
  priv->server_addr_v4 = NULL;
  if (priv->server_addr_v6)
    gst_rtsp_address_free (priv->server_addr_v6);
  priv->server_addr_v6 = NULL;

  for (i = 0; i < 2; i++) {
    g_clear_object (&priv->socket_v4[i]);
    g_clear_object (&priv->socket_v6[i]);
    g_clear_object (&priv->mcast_socket_v4[i]);
    g_clear_object (&priv->mcast_socket_v6[i]);
  }

  g_mutex_unlock (&priv->lock);

  return TRUE;

was_not_joined:
  {
    g_mutex_unlock (&priv->lock);
    return TRUE;
  }
transports_not_removed:
  {
    GST_ERROR_OBJECT (stream, "can't leave bin (transports not removed)");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
wrong_bin:
  {
    GST_ERROR_OBJECT (stream, "leaving the wrong bin");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_get_joined_bin:
 * @stream: a #GstRTSPStream
 *
 * Get the previous joined bin with gst_rtsp_stream_join_bin() or NULL.
 *
 * Return: (transfer full) (nullable): the joined bin or NULL.
 */
GstBin *
gst_rtsp_stream_get_joined_bin (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstBin *bin = NULL;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  bin = priv->joined_bin ? gst_object_ref (priv->joined_bin) : NULL;
  g_mutex_unlock (&priv->lock);

  return bin;
}

/**
 * gst_rtsp_stream_get_rtpinfo:
 * @stream: a #GstRTSPStream
 * @rtptime: (allow-none) (out caller-allocates): result RTP timestamp
 * @seq: (allow-none) (out caller-allocates): result RTP seqnum
 * @clock_rate: (allow-none) (out caller-allocates): the clock rate
 * @running_time: (out caller-allocates): result running-time
 *
 * Retrieve the current rtptime, seq and running-time. This is used to
 * construct a RTPInfo reply header.
 *
 * Returns: %TRUE when rtptime, seq and running-time could be determined.
 */
gboolean
gst_rtsp_stream_get_rtpinfo (GstRTSPStream * stream,
    guint * rtptime, guint * seq, guint * clock_rate,
    GstClockTime * running_time)
{
  GstRTSPStreamPrivate *priv;
  GstStructure *stats;
  GObjectClass *payobjclass;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;

  payobjclass = G_OBJECT_GET_CLASS (priv->payloader);

  g_mutex_lock (&priv->lock);

  /* First try to extract the information from the last buffer on the sinks.
   * This will have a more accurate sequence number and timestamp, as between
   * the payloader and the sink there can be some queues
   */
  if (priv->udpsink[0] || priv->mcast_udpsink[0] || priv->appsink[0]) {
    GstSample *last_sample;

    if (priv->udpsink[0])
      g_object_get (priv->udpsink[0], "last-sample", &last_sample, NULL);
    else if (priv->mcast_udpsink[0])
      g_object_get (priv->mcast_udpsink[0], "last-sample", &last_sample, NULL);
    else
      g_object_get (priv->appsink[0], "last-sample", &last_sample, NULL);

    if (last_sample && !priv->blocking) {
      GstCaps *caps;
      GstBuffer *buffer;
      GstSegment *segment;
      GstStructure *s;
      GstRTPBuffer rtp_buffer = GST_RTP_BUFFER_INIT;

      caps = gst_sample_get_caps (last_sample);
      buffer = gst_sample_get_buffer (last_sample);
      segment = gst_sample_get_segment (last_sample);
      s = gst_caps_get_structure (caps, 0);

      if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp_buffer)) {
        guint ssrc_buf = gst_rtp_buffer_get_ssrc (&rtp_buffer);
        guint ssrc_stream = 0;
        if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT) &&
            gst_structure_get_uint (s, "ssrc", &ssrc_stream) &&
            ssrc_buf != ssrc_stream) {
          /* Skip buffers from auxiliary streams. */
          GST_DEBUG_OBJECT (stream,
              "not a buffer from the payloader, SSRC: %08x", ssrc_buf);

          gst_rtp_buffer_unmap (&rtp_buffer);
          gst_sample_unref (last_sample);
          goto stats;
        }

        if (seq) {
          *seq = gst_rtp_buffer_get_seq (&rtp_buffer);
        }

        if (rtptime) {
          *rtptime = gst_rtp_buffer_get_timestamp (&rtp_buffer);
        }

        gst_rtp_buffer_unmap (&rtp_buffer);

        if (running_time) {
          *running_time =
              gst_segment_to_running_time (segment, GST_FORMAT_TIME,
              GST_BUFFER_TIMESTAMP (buffer));
        }

        if (clock_rate) {
          gst_structure_get_int (s, "clock-rate", (gint *) clock_rate);

          if (*clock_rate == 0 && running_time)
            *running_time = GST_CLOCK_TIME_NONE;
        }
        gst_sample_unref (last_sample);

        goto done;
      } else {
        gst_sample_unref (last_sample);
      }
    } else if (priv->blocking) {
      if (last_sample != NULL)
        gst_sample_unref (last_sample);
      if (seq) {
        if (!priv->blocked_buffer)
          goto stats;
        *seq = priv->blocked_seqnum;
      }

      if (rtptime) {
        if (!priv->blocked_buffer)
          goto stats;
        *rtptime = priv->blocked_rtptime;
      }

      if (running_time) {
        if (!GST_CLOCK_TIME_IS_VALID (priv->blocked_running_time))
          goto stats;
        *running_time = priv->blocked_running_time;
      }

      if (clock_rate) {
        *clock_rate = priv->blocked_clock_rate;

        if (*clock_rate == 0 && running_time)
          *running_time = GST_CLOCK_TIME_NONE;
      }

      goto done;
    }
  }

stats:
  if (g_object_class_find_property (payobjclass, "stats")) {
    g_object_get (priv->payloader, "stats", &stats, NULL);
    if (stats == NULL)
      goto no_stats;

    if (seq)
      gst_structure_get_uint (stats, "seqnum-offset", seq);

    if (rtptime)
      gst_structure_get_uint (stats, "timestamp", rtptime);

    if (running_time)
      gst_structure_get_clock_time (stats, "running-time", running_time);

    if (clock_rate) {
      gst_structure_get_uint (stats, "clock-rate", clock_rate);
      if (*clock_rate == 0 && running_time)
        *running_time = GST_CLOCK_TIME_NONE;
    }
    gst_structure_free (stats);
  } else {
    if (!g_object_class_find_property (payobjclass, "seqnum") ||
        !g_object_class_find_property (payobjclass, "timestamp"))
      goto no_stats;

    if (seq)
      g_object_get (priv->payloader, "seqnum", seq, NULL);

    if (rtptime)
      g_object_get (priv->payloader, "timestamp", rtptime, NULL);

    if (running_time)
      *running_time = GST_CLOCK_TIME_NONE;
  }

done:
  g_mutex_unlock (&priv->lock);

  return TRUE;

  /* ERRORS */
no_stats:
  {
    GST_WARNING ("Could not get payloader stats");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_get_rates:
 * @stream: a #GstRTSPStream
 * @rate: (optional) (out caller-allocates): the configured rate
 * @applied_rate: (optional) (out caller-allocates): the configured applied_rate
 *
 * Retrieve the current rate and/or applied_rate.
 *
 * Returns: %TRUE if rate and/or applied_rate could be determined.
 * Since: 1.18
 */
gboolean
gst_rtsp_stream_get_rates (GstRTSPStream * stream, gdouble * rate,
    gdouble * applied_rate)
{
  GstRTSPStreamPrivate *priv;
  GstEvent *event;
  const GstSegment *segment;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  if (!rate && !applied_rate) {
    GST_WARNING_OBJECT (stream, "rate and applied_rate are both NULL");
    return FALSE;
  }

  priv = stream->priv;

  g_mutex_lock (&priv->lock);

  if (!priv->send_rtp_sink)
    goto no_rtp_sink_pad;

  event = gst_pad_get_sticky_event (priv->send_rtp_sink, GST_EVENT_SEGMENT, 0);
  if (!event)
    goto no_sticky_event;

  gst_event_parse_segment (event, &segment);
  if (rate)
    *rate = segment->rate;
  if (applied_rate)
    *applied_rate = segment->applied_rate;

  gst_event_unref (event);
  g_mutex_unlock (&priv->lock);

  return TRUE;

/* ERRORS */
no_rtp_sink_pad:
  {
    GST_WARNING_OBJECT (stream, "no send_rtp_sink pad yet");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
no_sticky_event:
  {
    GST_WARNING_OBJECT (stream, "no segment event on send_rtp_sink pad");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }

}

/**
 * gst_rtsp_stream_get_caps:
 * @stream: a #GstRTSPStream
 *
 * Retrieve the current caps of @stream.
 *
 * Returns: (transfer full) (nullable): the #GstCaps of @stream.
 * use gst_caps_unref() after usage.
 */
GstCaps *
gst_rtsp_stream_get_caps (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstCaps *result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  if ((result = priv->caps))
    gst_caps_ref (result);
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_stream_recv_rtp:
 * @stream: a #GstRTSPStream
 * @buffer: (transfer full): a #GstBuffer
 *
 * Handle an RTP buffer for the stream. This method is usually called when a
 * message has been received from a client using the TCP transport.
 *
 * This function takes ownership of @buffer.
 *
 * Returns: a GstFlowReturn.
 */
GstFlowReturn
gst_rtsp_stream_recv_rtp (GstRTSPStream * stream, GstBuffer * buffer)
{
  GstRTSPStreamPrivate *priv;
  GstFlowReturn ret;
  GstElement *element;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_FLOW_ERROR);
  priv = stream->priv;
  g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
  g_return_val_if_fail (priv->joined_bin != NULL, FALSE);

  g_mutex_lock (&priv->lock);
  if (priv->appsrc[0])
    element = gst_object_ref (priv->appsrc[0]);
  else
    element = NULL;
  g_mutex_unlock (&priv->lock);

  if (element) {
    if (priv->appsrc_base_time[0] == -1) {
      /* Take current running_time. This timestamp will be put on
       * the first buffer of each stream because we are a live source and so we
       * timestamp with the running_time. When we are dealing with TCP, we also
       * only timestamp the first buffer (using the DISCONT flag) because a server
       * typically bursts data, for which we don't want to compensate by speeding
       * up the media. The other timestamps will be interpollated from this one
       * using the RTP timestamps. */
      GST_OBJECT_LOCK (element);
      if (GST_ELEMENT_CLOCK (element)) {
        GstClockTime now;
        GstClockTime base_time;

        now = gst_clock_get_time (GST_ELEMENT_CLOCK (element));
        base_time = GST_ELEMENT_CAST (element)->base_time;

        priv->appsrc_base_time[0] = now - base_time;
        GST_BUFFER_TIMESTAMP (buffer) = priv->appsrc_base_time[0];
        GST_DEBUG ("stream %p: first buffer at time %" GST_TIME_FORMAT
            ", base %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (now),
            GST_TIME_ARGS (base_time));
      }
      GST_OBJECT_UNLOCK (element);
    }

    ret = gst_app_src_push_buffer (GST_APP_SRC_CAST (element), buffer);
    gst_object_unref (element);
  } else {
    ret = GST_FLOW_OK;
  }
  return ret;
}

/**
 * gst_rtsp_stream_recv_rtcp:
 * @stream: a #GstRTSPStream
 * @buffer: (transfer full): a #GstBuffer
 *
 * Handle an RTCP buffer for the stream. This method is usually called when a
 * message has been received from a client using the TCP transport.
 *
 * This function takes ownership of @buffer.
 *
 * Returns: a GstFlowReturn.
 */
GstFlowReturn
gst_rtsp_stream_recv_rtcp (GstRTSPStream * stream, GstBuffer * buffer)
{
  GstRTSPStreamPrivate *priv;
  GstFlowReturn ret;
  GstElement *element;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_FLOW_ERROR);
  priv = stream->priv;
  g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);

  if (priv->joined_bin == NULL) {
    gst_buffer_unref (buffer);
    return GST_FLOW_NOT_LINKED;
  }
  g_mutex_lock (&priv->lock);
  if (priv->appsrc[1])
    element = gst_object_ref (priv->appsrc[1]);
  else
    element = NULL;
  g_mutex_unlock (&priv->lock);

  if (element) {
    if (priv->appsrc_base_time[1] == -1) {
      /* Take current running_time. This timestamp will be put on
       * the first buffer of each stream because we are a live source and so we
       * timestamp with the running_time. When we are dealing with TCP, we also
       * only timestamp the first buffer (using the DISCONT flag) because a server
       * typically bursts data, for which we don't want to compensate by speeding
       * up the media. The other timestamps will be interpollated from this one
       * using the RTP timestamps. */
      GST_OBJECT_LOCK (element);
      if (GST_ELEMENT_CLOCK (element)) {
        GstClockTime now;
        GstClockTime base_time;

        now = gst_clock_get_time (GST_ELEMENT_CLOCK (element));
        base_time = GST_ELEMENT_CAST (element)->base_time;

        priv->appsrc_base_time[1] = now - base_time;
        GST_BUFFER_TIMESTAMP (buffer) = priv->appsrc_base_time[1];
        GST_DEBUG ("stream %p: first buffer at time %" GST_TIME_FORMAT
            ", base %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (now),
            GST_TIME_ARGS (base_time));
      }
      GST_OBJECT_UNLOCK (element);
    }

    ret = gst_app_src_push_buffer (GST_APP_SRC_CAST (element), buffer);
    gst_object_unref (element);
  } else {
    ret = GST_FLOW_OK;
    gst_buffer_unref (buffer);
  }
  return ret;
}

/* must be called with lock */
static inline void
add_client (GstElement * rtp_sink, GstElement * rtcp_sink, const gchar * host,
    gint rtp_port, gint rtcp_port)
{
  if (rtp_sink != NULL)
    g_signal_emit_by_name (rtp_sink, "add", host, rtp_port, NULL);
  if (rtcp_sink != NULL)
    g_signal_emit_by_name (rtcp_sink, "add", host, rtcp_port, NULL);
}

/* must be called with lock */
static void
remove_client (GstElement * rtp_sink, GstElement * rtcp_sink,
    const gchar * host, gint rtp_port, gint rtcp_port)
{
  if (rtp_sink != NULL)
    g_signal_emit_by_name (rtp_sink, "remove", host, rtp_port, NULL);
  if (rtcp_sink != NULL)
    g_signal_emit_by_name (rtcp_sink, "remove", host, rtcp_port, NULL);
}

/* must be called with lock */
static gboolean
update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
    gboolean add)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  const GstRTSPTransport *tr;
  gchar *dest;
  gint min, max;
  GList *tr_element;

  tr = gst_rtsp_stream_transport_get_transport (trans);
  dest = tr->destination;

  tr_element = g_list_find (priv->transports, trans);

  if (add && tr_element)
    return TRUE;
  else if (!add && !tr_element)
    return FALSE;

  switch (tr->lower_transport) {
    case GST_RTSP_LOWER_TRANS_UDP_MCAST:
    {
      min = tr->port.min;
      max = tr->port.max;

      if (add) {
        GST_INFO ("adding %s:%d-%d", dest, min, max);
        if (!check_mcast_client_addr (stream, tr))
          goto mcast_error;
        add_client (priv->mcast_udpsink[0], priv->mcast_udpsink[1], dest, min,
            max);

        if (tr->ttl > 0) {
          GST_INFO ("setting ttl-mc %d", tr->ttl);
          if (priv->mcast_udpsink[0])
            g_object_set (G_OBJECT (priv->mcast_udpsink[0]), "ttl-mc", tr->ttl,
                NULL);
          if (priv->mcast_udpsink[1])
            g_object_set (G_OBJECT (priv->mcast_udpsink[1]), "ttl-mc", tr->ttl,
                NULL);
        }
        priv->transports = g_list_prepend (priv->transports, trans);
      } else {
        GST_INFO ("removing %s:%d-%d", dest, min, max);
        if (!remove_mcast_client_addr (stream, dest, min, max))
          GST_WARNING_OBJECT (stream,
              "Failed to remove multicast address: %s:%d-%d", dest, min, max);
        priv->transports = g_list_delete_link (priv->transports, tr_element);
        remove_client (priv->mcast_udpsink[0], priv->mcast_udpsink[1], dest,
            min, max);
      }
      break;
    }
    case GST_RTSP_LOWER_TRANS_UDP:
    {
      if (priv->client_side) {
        /* In client side mode the 'destination' is the RTSP server, so send
         * to those ports */
        min = tr->server_port.min;
        max = tr->server_port.max;
      } else {
        min = tr->client_port.min;
        max = tr->client_port.max;
      }

      if (add) {
        GST_INFO ("adding %s:%d-%d", dest, min, max);
        add_client (priv->udpsink[0], priv->udpsink[1], dest, min, max);
        priv->transports = g_list_prepend (priv->transports, trans);
      } else {
        GST_INFO ("removing %s:%d-%d", dest, min, max);
        priv->transports = g_list_delete_link (priv->transports, tr_element);
        remove_client (priv->udpsink[0], priv->udpsink[1], dest, min, max);
      }
      priv->transports_cookie++;
      break;
    }
    case GST_RTSP_LOWER_TRANS_TCP:
      if (add) {
        GST_INFO ("adding TCP %s", tr->destination);
        priv->transports = g_list_prepend (priv->transports, trans);
        priv->n_tcp_transports++;
      } else {
        GST_INFO ("removing TCP %s", tr->destination);
        priv->transports = g_list_delete_link (priv->transports, tr_element);

        gst_rtsp_stream_transport_lock_backlog (trans);
        gst_rtsp_stream_transport_clear_backlog (trans);
        gst_rtsp_stream_transport_unlock_backlog (trans);

        priv->n_tcp_transports--;
      }
      priv->transports_cookie++;
      break;
    default:
      goto unknown_transport;
  }
  return TRUE;

  /* ERRORS */
unknown_transport:
  {
    GST_INFO ("Unknown transport %d", tr->lower_transport);
    return FALSE;
  }
mcast_error:
  {
    return FALSE;
  }
}

static void
on_message_sent (GstRTSPStreamTransport * trans, gpointer user_data)
{
  GstRTSPStream *stream = GST_RTSP_STREAM (user_data);
  GstRTSPStreamPrivate *priv = stream->priv;

  GST_DEBUG_OBJECT (stream, "message send complete");

  check_transport_backlog (stream, trans);

  g_mutex_lock (&priv->send_lock);
  priv->send_cookie++;
  g_cond_signal (&priv->send_cond);
  g_mutex_unlock (&priv->send_lock);
}

/**
 * gst_rtsp_stream_add_transport:
 * @stream: a #GstRTSPStream
 * @trans: (transfer none): a #GstRTSPStreamTransport
 *
 * Add the transport in @trans to @stream. The media of @stream will
 * then also be send to the values configured in @trans. Adding the
 * same transport twice will not add it a second time.
 *
 * @stream must be joined to a bin.
 *
 * @trans must contain a valid #GstRTSPTransport.
 *
 * Returns: %TRUE if @trans was added
 */
gboolean
gst_rtsp_stream_add_transport (GstRTSPStream * stream,
    GstRTSPStreamTransport * trans)
{
  GstRTSPStreamPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  priv = stream->priv;
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);
  g_return_val_if_fail (priv->joined_bin != NULL, FALSE);

  g_mutex_lock (&priv->lock);
  res = update_transport (stream, trans, TRUE);
  if (res)
    gst_rtsp_stream_transport_set_message_sent_full (trans, on_message_sent,
        stream, NULL);
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_remove_transport:
 * @stream: a #GstRTSPStream
 * @trans: (transfer none): a #GstRTSPStreamTransport
 *
 * Remove the transport in @trans from @stream. The media of @stream will
 * not be sent to the values configured in @trans.
 *
 * @stream must be joined to a bin.
 *
 * @trans must contain a valid #GstRTSPTransport.
 *
 * Returns: %TRUE if @trans was removed
 */
gboolean
gst_rtsp_stream_remove_transport (GstRTSPStream * stream,
    GstRTSPStreamTransport * trans)
{
  GstRTSPStreamPrivate *priv;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  priv = stream->priv;
  g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);
  g_return_val_if_fail (priv->joined_bin != NULL, FALSE);

  g_mutex_lock (&priv->lock);
  res = update_transport (stream, trans, FALSE);
  g_mutex_unlock (&priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_update_crypto:
 * @stream: a #GstRTSPStream
 * @ssrc: the SSRC
 * @crypto: (transfer none) (allow-none): a #GstCaps with crypto info
 *
 * Update the new crypto information for @ssrc in @stream. If information
 * for @ssrc did not exist, it will be added. If information
 * for @ssrc existed, it will be replaced. If @crypto is %NULL, it will
 * be removed from @stream.
 *
 * Returns: %TRUE if @crypto could be updated
 */
gboolean
gst_rtsp_stream_update_crypto (GstRTSPStream * stream,
    guint ssrc, GstCaps * crypto)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (crypto == NULL || GST_IS_CAPS (crypto), FALSE);

  priv = stream->priv;

  GST_DEBUG_OBJECT (stream, "update key for %08x", ssrc);

  g_mutex_lock (&priv->lock);
  if (crypto)
    g_hash_table_insert (priv->keys, GINT_TO_POINTER (ssrc),
        gst_caps_ref (crypto));
  else
    g_hash_table_remove (priv->keys, GINT_TO_POINTER (ssrc));
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_stream_get_rtp_socket:
 * @stream: a #GstRTSPStream
 * @family: the socket family
 *
 * Get the RTP socket from @stream for a @family.
 *
 * @stream must be joined to a bin.
 *
 * Returns: (transfer full) (nullable): the RTP socket or %NULL if no
 * socket could be allocated for @family. Unref after usage
 */
GSocket *
gst_rtsp_stream_get_rtp_socket (GstRTSPStream * stream, GSocketFamily family)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GSocket *socket;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
      family == G_SOCKET_FAMILY_IPV6, NULL);

  g_mutex_lock (&priv->lock);
  if (family == G_SOCKET_FAMILY_IPV6)
    socket = priv->socket_v6[0];
  else
    socket = priv->socket_v4[0];

  if (socket != NULL)
    socket = g_object_ref (socket);
  g_mutex_unlock (&priv->lock);

  return socket;
}

/**
 * gst_rtsp_stream_get_rtcp_socket:
 * @stream: a #GstRTSPStream
 * @family: the socket family
 *
 * Get the RTCP socket from @stream for a @family.
 *
 * @stream must be joined to a bin.
 *
 * Returns: (transfer full) (nullable): the RTCP socket or %NULL if no
 * socket could be allocated for @family. Unref after usage
 */
GSocket *
gst_rtsp_stream_get_rtcp_socket (GstRTSPStream * stream, GSocketFamily family)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GSocket *socket;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
      family == G_SOCKET_FAMILY_IPV6, NULL);

  g_mutex_lock (&priv->lock);
  if (family == G_SOCKET_FAMILY_IPV6)
    socket = priv->socket_v6[1];
  else
    socket = priv->socket_v4[1];

  if (socket != NULL)
    socket = g_object_ref (socket);
  g_mutex_unlock (&priv->lock);

  return socket;
}

/**
 * gst_rtsp_stream_get_rtp_multicast_socket:
 * @stream: a #GstRTSPStream
 * @family: the socket family
 *
 * Get the multicast RTP socket from @stream for a @family.
 *
 * Returns: (transfer full) (nullable): the multicast RTP socket or %NULL if no
 *
 * socket could be allocated for @family. Unref after usage
 */
GSocket *
gst_rtsp_stream_get_rtp_multicast_socket (GstRTSPStream * stream,
    GSocketFamily family)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GSocket *socket;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
      family == G_SOCKET_FAMILY_IPV6, NULL);

  g_mutex_lock (&priv->lock);
  if (family == G_SOCKET_FAMILY_IPV6)
    socket = priv->mcast_socket_v6[0];
  else
    socket = priv->mcast_socket_v4[0];

  if (socket != NULL)
    socket = g_object_ref (socket);
  g_mutex_unlock (&priv->lock);

  return socket;
}

/**
 * gst_rtsp_stream_get_rtcp_multicast_socket:
 * @stream: a #GstRTSPStream
 * @family: the socket family
 *
 * Get the multicast RTCP socket from @stream for a @family.
 *
 * Returns: (transfer full) (nullable): the multicast RTCP socket or %NULL if no
 * socket could be allocated for @family. Unref after usage
 *
 * Since: 1.14
 */
GSocket *
gst_rtsp_stream_get_rtcp_multicast_socket (GstRTSPStream * stream,
    GSocketFamily family)
{
  GstRTSPStreamPrivate *priv = stream->priv;
  GSocket *socket;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
      family == G_SOCKET_FAMILY_IPV6, NULL);

  g_mutex_lock (&priv->lock);
  if (family == G_SOCKET_FAMILY_IPV6)
    socket = priv->mcast_socket_v6[1];
  else
    socket = priv->mcast_socket_v4[1];

  if (socket != NULL)
    socket = g_object_ref (socket);
  g_mutex_unlock (&priv->lock);

  return socket;
}

/**
 * gst_rtsp_stream_add_multicast_client_address:
 * @stream: a #GstRTSPStream
 * @destination: (transfer none): a multicast address to add
 * @rtp_port: RTP port
 * @rtcp_port: RTCP port
 * @family: socket family
 *
 * Add multicast client address to stream. At this point, the sockets that
 * will stream RTP and RTCP data to @destination are supposed to be
 * allocated.
 *
 * Returns: %TRUE if @destination can be addedd and handled by @stream.
 *
 * Since: 1.16
 */
gboolean
gst_rtsp_stream_add_multicast_client_address (GstRTSPStream * stream,
    const gchar * destination, guint rtp_port, guint rtcp_port,
    GSocketFamily family)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
  g_return_val_if_fail (destination != NULL, FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  if ((family == G_SOCKET_FAMILY_IPV4) && (priv->mcast_socket_v4[0] == NULL))
    goto socket_error;
  else if ((family == G_SOCKET_FAMILY_IPV6) &&
      (priv->mcast_socket_v6[0] == NULL))
    goto socket_error;

  if (!add_mcast_client_addr (stream, destination, rtp_port, rtcp_port))
    goto add_addr_error;
  g_mutex_unlock (&priv->lock);

  return TRUE;

socket_error:
  {
    GST_WARNING_OBJECT (stream,
        "Failed to add multicast address: no udp socket");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
add_addr_error:
  {
    GST_WARNING_OBJECT (stream,
        "Failed to add multicast address: invalid address");
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_get_multicast_client_addresses
 * @stream: a #GstRTSPStream
 *
 * Get all multicast client addresses that RTP data will be sent to
 *
 * Returns: A comma separated list of host:port pairs with destinations
 *
 * Since: 1.16
 */
gchar *
gst_rtsp_stream_get_multicast_client_addresses (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GString *str;
  GList *clients;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;
  str = g_string_new ("");

  g_mutex_lock (&priv->lock);
  clients = priv->mcast_clients;
  while (clients != NULL) {
    UdpClientAddrInfo *client;

    client = (UdpClientAddrInfo *) clients->data;
    clients = g_list_next (clients);
    g_string_append_printf (str, "%s:%d%s", client->address, client->rtp_port,
        (clients != NULL ? "," : ""));
  }
  g_mutex_unlock (&priv->lock);

  return g_string_free (str, FALSE);
}

/**
 * gst_rtsp_stream_set_seqnum:
 * @stream: a #GstRTSPStream
 * @seqnum: a new sequence number
 *
 * Configure the sequence number in the payloader of @stream to @seqnum.
 */
void
gst_rtsp_stream_set_seqnum_offset (GstRTSPStream * stream, guint16 seqnum)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  priv = stream->priv;

  g_object_set (G_OBJECT (priv->payloader), "seqnum-offset", seqnum, NULL);
}

/**
 * gst_rtsp_stream_get_seqnum:
 * @stream: a #GstRTSPStream
 *
 * Get the configured sequence number in the payloader of @stream.
 *
 * Returns: the sequence number of the payloader.
 */
guint16
gst_rtsp_stream_get_current_seqnum (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  guint seqnum;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);

  priv = stream->priv;

  g_object_get (G_OBJECT (priv->payloader), "seqnum", &seqnum, NULL);

  return seqnum;
}

/**
 * gst_rtsp_stream_transport_filter:
 * @stream: a #GstRTSPStream
 * @func: (scope call) (allow-none) (closure user_data): a callback
 * @user_data: user data passed to @func
 *
 * Call @func for each transport managed by @stream. The result value of @func
 * determines what happens to the transport. @func will be called with @stream
 * locked so no further actions on @stream can be performed from @func.
 *
 * If @func returns #GST_RTSP_FILTER_REMOVE, the transport will be removed from
 * @stream.
 *
 * If @func returns #GST_RTSP_FILTER_KEEP, the transport will remain in @stream.
 *
 * If @func returns #GST_RTSP_FILTER_REF, the transport will remain in @stream but
 * will also be added with an additional ref to the result #GList of this
 * function..
 *
 * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each transport.
 *
 * Returns: (element-type GstRTSPStreamTransport) (transfer full): a #GList with all
 * transports for which @func returned #GST_RTSP_FILTER_REF. After usage, each
 * element in the #GList should be unreffed before the list is freed.
 */
GList *
gst_rtsp_stream_transport_filter (GstRTSPStream * stream,
    GstRTSPStreamTransportFilterFunc func, gpointer user_data)
{
  GstRTSPStreamPrivate *priv;
  GList *result, *walk, *next;
  GHashTable *visited = NULL;
  guint cookie;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  priv = stream->priv;

  result = NULL;
  if (func)
    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);

  g_mutex_lock (&priv->lock);
restart:
  cookie = priv->transports_cookie;
  for (walk = priv->transports; walk; walk = next) {
    GstRTSPStreamTransport *trans = walk->data;
    GstRTSPFilterResult res;
    gboolean changed;

    next = g_list_next (walk);

    if (func) {
      /* only visit each transport once */
      if (g_hash_table_contains (visited, trans))
        continue;

      g_hash_table_add (visited, g_object_ref (trans));
      g_mutex_unlock (&priv->lock);

      res = func (stream, trans, user_data);

      g_mutex_lock (&priv->lock);
    } else
      res = GST_RTSP_FILTER_REF;

    changed = (cookie != priv->transports_cookie);

    switch (res) {
      case GST_RTSP_FILTER_REMOVE:
        update_transport (stream, trans, FALSE);
        break;
      case GST_RTSP_FILTER_REF:
        result = g_list_prepend (result, g_object_ref (trans));
        break;
      case GST_RTSP_FILTER_KEEP:
      default:
        break;
    }
    if (changed)
      goto restart;
  }
  g_mutex_unlock (&priv->lock);

  if (func)
    g_hash_table_unref (visited);

  return result;
}

static GstPadProbeReturn
rtp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPStream *stream;
  GstBuffer *buffer = NULL;
  GstPadProbeReturn ret = GST_PAD_PROBE_OK;
  GstEvent *event;

  stream = user_data;
  priv = stream->priv;

  g_mutex_lock (&priv->lock);

  if ((info->type & GST_PAD_PROBE_TYPE_BUFFER)) {
    GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;

    buffer = gst_pad_probe_info_get_buffer (info);
    if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp)) {
      priv->blocked_buffer = TRUE;
      priv->blocked_seqnum = gst_rtp_buffer_get_seq (&rtp);
      priv->blocked_rtptime = gst_rtp_buffer_get_timestamp (&rtp);
      gst_rtp_buffer_unmap (&rtp);
    }
    priv->position = GST_BUFFER_TIMESTAMP (buffer);
    if (priv->drop_delta_units) {
      if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) {
        g_assert (!priv->blocking);
        GST_DEBUG_OBJECT (pad, "dropping delta-unit buffer");
        ret = GST_PAD_PROBE_DROP;
        goto done;
      }
    }
  } else if ((info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) {
    GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;

    GstBufferList *list = gst_pad_probe_info_get_buffer_list (info);
    buffer = gst_buffer_list_get (list, 0);
    if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp)) {
      priv->blocked_buffer = TRUE;
      priv->blocked_seqnum = gst_rtp_buffer_get_seq (&rtp);
      priv->blocked_rtptime = gst_rtp_buffer_get_timestamp (&rtp);
      gst_rtp_buffer_unmap (&rtp);
    }
    priv->position = GST_BUFFER_TIMESTAMP (buffer);
    if (priv->drop_delta_units) {
      if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) {
        g_assert (!priv->blocking);
        GST_DEBUG_OBJECT (pad, "dropping delta-unit buffer");
        ret = GST_PAD_PROBE_DROP;
        goto done;
      }
    }
  } else if ((info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) {
    if (GST_EVENT_TYPE (info->data) == GST_EVENT_GAP) {
      gst_event_parse_gap (info->data, &priv->position, NULL);
    } else {
      ret = GST_PAD_PROBE_PASS;
      GST_WARNING ("Passing event.");
      goto done;
    }
  } else {
    g_assert_not_reached ();
  }

  event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
  if (event) {
    const GstSegment *segment;

    gst_event_parse_segment (event, &segment);
    priv->blocked_running_time =
        gst_segment_to_stream_time (segment, GST_FORMAT_TIME, priv->position);
    gst_event_unref (event);
  }

  event = gst_pad_get_sticky_event (pad, GST_EVENT_CAPS, 0);
  if (event) {
    GstCaps *caps;
    GstStructure *s;

    gst_event_parse_caps (event, &caps);
    s = gst_caps_get_structure (caps, 0);
    gst_structure_get_int (s, "clock-rate", &priv->blocked_clock_rate);
    gst_event_unref (event);
  }

  /* make sure to block on the correct frame type */
  if (priv->drop_delta_units) {
    g_assert (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT));
  }

  priv->blocking = TRUE;

  GST_DEBUG_OBJECT (pad, "Now blocking");

  GST_DEBUG_OBJECT (stream, "position: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (priv->position));

  gst_element_post_message (priv->payloader,
      gst_message_new_element (GST_OBJECT_CAST (priv->payloader),
          gst_structure_new ("GstRTSPStreamBlocking", "is_complete",
              G_TYPE_BOOLEAN, priv->is_complete, NULL)));
done:
  g_mutex_unlock (&priv->lock);
  return ret;
}

/* this probe will drop a single buffer. It is used when an old buffer is
 * blocking the pipeline, such as between a DESCRIBE and a PLAY request. */
static GstPadProbeReturn
drop_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPStream *stream;
  /* drop an old buffer stuck in a blocked pipeline */
  GstPadProbeReturn ret = GST_PAD_PROBE_DROP;

  stream = user_data;
  priv = stream->priv;

  g_mutex_lock (&priv->lock);

  if ((info->type & GST_PAD_PROBE_TYPE_BUFFER ||
          info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) {
    /* if a buffer has been dropped then remove this probe */
    if (priv->remove_drop_probe) {
      priv->remove_drop_probe = FALSE;
      ret = GST_PAD_PROBE_REMOVE;
    } else {
      priv->blocking = FALSE;
      priv->remove_drop_probe = TRUE;
    }
  } else {
    ret = GST_PAD_PROBE_PASS;
  }
  g_mutex_unlock (&priv->lock);
  return ret;
}

static GstPadProbeReturn
rtcp_pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstRTSPStreamPrivate *priv;
  GstRTSPStream *stream;
  GstPadProbeReturn ret = GST_PAD_PROBE_OK;

  stream = user_data;
  priv = stream->priv;

  g_mutex_lock (&priv->lock);

  if ((info->type & GST_PAD_PROBE_TYPE_BUFFER) ||
      (info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) {
    GST_DEBUG_OBJECT (pad, "Now blocking on buffer");
  } else if ((info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) {
    if (GST_EVENT_TYPE (info->data) == GST_EVENT_GAP) {
      GST_DEBUG_OBJECT (pad, "Now blocking on gap event");
      ret = GST_PAD_PROBE_OK;
    } else {
      ret = GST_PAD_PROBE_PASS;
      g_mutex_unlock (&priv->lock);
      goto done;
    }
  } else {
    g_assert_not_reached ();
  }

  g_mutex_unlock (&priv->lock);

done:
  return ret;
}

static void
install_drop_probe (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;

  priv = stream->priv;

  /* if receiver */
  if (priv->sinkpad)
    return;

  /* install for data channel only */
  if (priv->send_src[0]) {
    gst_pad_add_probe (priv->send_src[0],
        GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER |
        GST_PAD_PROBE_TYPE_BUFFER_LIST |
        GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, drop_probe,
        g_object_ref (stream), g_object_unref);
  }
}

static void
set_blocked (GstRTSPStream * stream, gboolean blocked)
{
  GstRTSPStreamPrivate *priv;
  int i;

  GST_DEBUG_OBJECT (stream, "blocked: %d", blocked);

  priv = stream->priv;

  if (blocked) {
    /* if receiver */
    if (priv->sinkpad) {
      priv->blocking = TRUE;
      return;
    }
    for (i = 0; i < 2; i++) {
      if (priv->blocked_id[i] != 0)
        continue;
      if (priv->send_src[i]) {
        priv->blocking = FALSE;
        priv->blocked_buffer = FALSE;
        priv->blocked_running_time = GST_CLOCK_TIME_NONE;
        priv->blocked_clock_rate = 0;

        if (i == 0) {
          priv->blocked_id[i] = gst_pad_add_probe (priv->send_src[i],
              GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER |
              GST_PAD_PROBE_TYPE_BUFFER_LIST |
              GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, rtp_pad_blocking,
              g_object_ref (stream), g_object_unref);
        } else {
          priv->blocked_id[i] = gst_pad_add_probe (priv->send_src[i],
              GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER |
              GST_PAD_PROBE_TYPE_BUFFER_LIST |
              GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, rtcp_pad_blocking,
              g_object_ref (stream), g_object_unref);
        }
      }
    }
  } else {
    for (i = 0; i < 2; i++) {
      if (priv->blocked_id[i] != 0) {
        gst_pad_remove_probe (priv->send_src[i], priv->blocked_id[i]);
        priv->blocked_id[i] = 0;
      }
    }
    priv->blocking = FALSE;
  }
}

/**
 * gst_rtsp_stream_set_blocked:
 * @stream: a #GstRTSPStream
 * @blocked: boolean indicating we should block or unblock
 *
 * Blocks or unblocks the dataflow on @stream.
 *
 * Returns: %TRUE on success
 */
gboolean
gst_rtsp_stream_set_blocked (GstRTSPStream * stream, gboolean blocked)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  set_blocked (stream, blocked);
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_stream_install_drop_probe:
 * @stream: a #GstRTSPStream
 *
 * This probe can be installed when the currently blocking buffer should be
 * dropped. When it has successfully dropped the buffer, it will remove itself.
 * The goal is to avoid sending old data, typically when there has been a delay
 * between a DESCRIBE and a PLAY request.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.24
 */
gboolean
gst_rtsp_stream_install_drop_probe (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  install_drop_probe (stream);
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_stream_ublock_linked:
 * @stream: a #GstRTSPStream
 *
 * Unblocks the dataflow on @stream if it is linked.
 *
 * Returns: %TRUE on success
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_unblock_linked (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  if (priv->send_src[0] && gst_pad_is_linked (priv->send_src[0]))
    set_blocked (stream, FALSE);
  g_mutex_unlock (&priv->lock);

  return TRUE;
}

/**
 * gst_rtsp_stream_is_blocking:
 * @stream: a #GstRTSPStream
 *
 * Check if @stream is blocking on a #GstBuffer.
 *
 * Returns: %TRUE if @stream is blocking
 */
gboolean
gst_rtsp_stream_is_blocking (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  result = priv->blocking;
  g_mutex_unlock (&priv->lock);

  return result;
}

/**
 * gst_rtsp_stream_query_position:
 * @stream: a #GstRTSPStream
 * @position: (out): current position of a #GstRTSPStream
 *
 * Query the position of the stream in %GST_FORMAT_TIME. This only considers
 * the RTP parts of the pipeline and not the RTCP parts.
 *
 * Returns: %TRUE if the position could be queried
 */
gboolean
gst_rtsp_stream_query_position (GstRTSPStream * stream, gint64 * position)
{
  GstRTSPStreamPrivate *priv;
  GstElement *sink;
  GstPad *pad = NULL;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  /* query position: if no sinks have been added yet,
   * we obtain the position from the pad otherwise we query the sinks */

  priv = stream->priv;

  g_mutex_lock (&priv->lock);

  if (priv->blocking && GST_CLOCK_TIME_IS_VALID (priv->blocked_running_time)) {
    *position = priv->blocked_running_time;
    g_mutex_unlock (&priv->lock);
    return TRUE;
  }

  /* depending on the transport type, it should query corresponding sink */
  if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP)
    sink = priv->udpsink[0];
  else if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST)
    sink = priv->mcast_udpsink[0];
  else
    sink = priv->appsink[0];

  if (sink) {
    gst_object_ref (sink);
  } else if (priv->send_src[0]) {
    pad = gst_object_ref (priv->send_src[0]);
  } else {
    g_mutex_unlock (&priv->lock);
    GST_WARNING_OBJECT (stream, "Couldn't obtain position: erroneous pipeline");
    return FALSE;
  }
  g_mutex_unlock (&priv->lock);

  if (sink) {
    if (!gst_element_query_position (sink, GST_FORMAT_TIME, position)) {
      GST_WARNING_OBJECT (stream,
          "Couldn't obtain position: position query failed");
      gst_object_unref (sink);
      return FALSE;
    }
    gst_object_unref (sink);
  } else if (pad) {
    GstEvent *event;
    const GstSegment *segment;

    event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
    if (!event) {
      GST_WARNING_OBJECT (stream, "Couldn't obtain position: no segment event");
      gst_object_unref (pad);
      return FALSE;
    }

    gst_event_parse_segment (event, &segment);
    if (segment->format != GST_FORMAT_TIME) {
      *position = -1;
    } else {
      g_mutex_lock (&priv->lock);
      *position = priv->position;
      g_mutex_unlock (&priv->lock);
      *position =
          gst_segment_to_stream_time (segment, GST_FORMAT_TIME, *position);
    }
    gst_event_unref (event);
    gst_object_unref (pad);
  }

  return TRUE;
}

/**
 * gst_rtsp_stream_query_stop:
 * @stream: a #GstRTSPStream
 * @stop: (out): current stop of a #GstRTSPStream
 *
 * Query the stop of the stream in %GST_FORMAT_TIME. This only considers
 * the RTP parts of the pipeline and not the RTCP parts.
 *
 * Returns: %TRUE if the stop could be queried
 */
gboolean
gst_rtsp_stream_query_stop (GstRTSPStream * stream, gint64 * stop)
{
  GstRTSPStreamPrivate *priv;
  GstElement *sink;
  GstPad *pad = NULL;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  /* query stop position: if no sinks have been added yet,
   * we obtain the stop position from the pad otherwise we query the sinks */

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  /* depending on the transport type, it should query corresponding sink */
  if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP)
    sink = priv->udpsink[0];
  else if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST)
    sink = priv->mcast_udpsink[0];
  else
    sink = priv->appsink[0];

  if (sink) {
    gst_object_ref (sink);
  } else if (priv->send_src[0]) {
    pad = gst_object_ref (priv->send_src[0]);
  } else {
    g_mutex_unlock (&priv->lock);
    GST_WARNING_OBJECT (stream, "Couldn't obtain stop: erroneous pipeline");
    return FALSE;
  }
  g_mutex_unlock (&priv->lock);

  if (sink) {
    GstQuery *query;
    GstFormat format;
    gdouble rate;
    gint64 start_value;
    gint64 stop_value;

    query = gst_query_new_segment (GST_FORMAT_TIME);
    if (!gst_element_query (sink, query)) {
      GST_WARNING_OBJECT (stream, "Couldn't obtain stop: element query failed");
      gst_query_unref (query);
      gst_object_unref (sink);
      return FALSE;
    }
    gst_query_parse_segment (query, &rate, &format, &start_value, &stop_value);
    if (format != GST_FORMAT_TIME)
      *stop = -1;
    else
      *stop = rate > 0.0 ? stop_value : start_value;
    gst_query_unref (query);
    gst_object_unref (sink);
  } else if (pad) {
    GstEvent *event;
    const GstSegment *segment;

    event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
    if (!event) {
      GST_WARNING_OBJECT (stream, "Couldn't obtain stop: no segment event");
      gst_object_unref (pad);
      return FALSE;
    }
    gst_event_parse_segment (event, &segment);
    if (segment->format != GST_FORMAT_TIME) {
      *stop = -1;
    } else {
      *stop = segment->stop;
      if (*stop == -1)
        *stop = segment->duration;
      else
        *stop = gst_segment_to_stream_time (segment, GST_FORMAT_TIME, *stop);
    }
    gst_event_unref (event);
    gst_object_unref (pad);
  }

  return TRUE;
}

/**
 * gst_rtsp_stream_seekable:
 * @stream: a #GstRTSPStream
 *
 * Checks whether the individual @stream is seekable.
 *
 * Returns: %TRUE if @stream is seekable, else %FALSE.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_seekable (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  GstPad *pad = NULL;
  GstQuery *query = NULL;
  gboolean seekable = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  /* query stop position: if no sinks have been added yet,
   * we obtain the stop position from the pad otherwise we query the sinks */

  priv = stream->priv;

  g_mutex_lock (&priv->lock);
  /* depending on the transport type, it should query corresponding sink */
  if (priv->srcpad) {
    pad = gst_object_ref (priv->srcpad);
  } else {
    g_mutex_unlock (&priv->lock);
    GST_WARNING_OBJECT (stream, "Pad not available, can't query seekability");
    goto beach;
  }
  g_mutex_unlock (&priv->lock);

  query = gst_query_new_seeking (GST_FORMAT_TIME);
  if (!gst_pad_query (pad, query)) {
    GST_WARNING_OBJECT (stream, "seeking query failed");
    goto beach;
  }
  gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);

beach:
  if (pad)
    gst_object_unref (pad);
  if (query)
    gst_query_unref (query);

  GST_DEBUG_OBJECT (stream, "Returning %d", seekable);

  return seekable;
}

/**
 * gst_rtsp_stream_complete_stream:
 * @stream: a #GstRTSPStream
 * @transport: a #GstRTSPTransport
 *
 * Add a receiver and sender part to the pipeline based on the transport from
 * SETUP.
 *
 * Returns: %TRUE if the stream has been successfully updated.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_complete_stream (GstRTSPStream * stream,
    const GstRTSPTransport * transport)
{
  GstRTSPStreamPrivate *priv;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  GST_DEBUG_OBJECT (stream, "complete stream");

  g_mutex_lock (&priv->lock);

  if (!(priv->allowed_protocols & transport->lower_transport))
    goto unallowed_transport;

  if (!create_receiver_part (stream, transport))
    goto create_receiver_error;

  /* in the RECORD case, we only add RTCP sender part */
  if (!create_sender_part (stream, transport))
    goto create_sender_error;

  priv->configured_protocols |= transport->lower_transport;

  priv->is_complete = TRUE;
  g_mutex_unlock (&priv->lock);

  GST_DEBUG_OBJECT (stream, "pipeline successfully updated");
  return TRUE;

create_receiver_error:
create_sender_error:
unallowed_transport:
  {
    g_mutex_unlock (&priv->lock);
    return FALSE;
  }
}

/**
 * gst_rtsp_stream_is_complete:
 * @stream: a #GstRTSPStream
 *
 * Checks whether the stream is complete, contains the receiver and the sender
 * parts. As the stream contains sink(s) element(s), it's possible to perform
 * seek operations on it.
 *
 * Returns: %TRUE if the stream contains at least one sink element.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_is_complete (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = priv->is_complete;
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_is_sender:
 * @stream: a #GstRTSPStream
 *
 * Checks whether the stream is a sender.
 *
 * Returns: %TRUE if the stream is a sender and %FALSE otherwise.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_is_sender (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = (priv->srcpad != NULL);
  g_mutex_unlock (&priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_is_receiver:
 * @stream: a #GstRTSPStream
 *
 * Checks whether the stream is a receiver.
 *
 * Returns: %TRUE if the stream is a receiver and %FALSE otherwise.
 *
 * Since: 1.14
 */
gboolean
gst_rtsp_stream_is_receiver (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;
  gboolean ret = FALSE;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  ret = (priv->sinkpad != NULL);
  g_mutex_unlock (&priv->lock);

  return ret;
}

#define AES_128_KEY_LEN 16
#define AES_256_KEY_LEN 32

#define HMAC_32_KEY_LEN 4
#define HMAC_80_KEY_LEN 10

static gboolean
mikey_apply_policy (GstCaps * caps, GstMIKEYMessage * msg, guint8 policy)
{
  const gchar *srtp_cipher;
  const gchar *srtp_auth;
  const GstMIKEYPayload *sp;
  guint i;

  /* loop over Security policy until we find one containing policy */
  for (i = 0;; i++) {
    if ((sp = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, i)) == NULL)
      break;

    if (((GstMIKEYPayloadSP *) sp)->policy == policy)
      break;
  }

  /* the default ciphers */
  srtp_cipher = "aes-128-icm";
  srtp_auth = "hmac-sha1-80";

  /* now override the defaults with what is in the Security Policy */
  if (sp != NULL) {
    guint len;
    guint enc_alg = GST_MIKEY_ENC_AES_CM_128;

    /* collect all the params and go over them */
    len = gst_mikey_payload_sp_get_n_params (sp);
    for (i = 0; i < len; i++) {
      const GstMIKEYPayloadSPParam *param =
          gst_mikey_payload_sp_get_param (sp, i);

      switch (param->type) {
        case GST_MIKEY_SP_SRTP_ENC_ALG:
          enc_alg = param->val[0];
          switch (param->val[0]) {
            case GST_MIKEY_ENC_NULL:
              srtp_cipher = "null";
              break;
            case GST_MIKEY_ENC_AES_CM_128:
            case GST_MIKEY_ENC_AES_KW_128:
              srtp_cipher = "aes-128-icm";
              break;
            case GST_MIKEY_ENC_AES_GCM_128:
              srtp_cipher = "aes-128-gcm";
              break;
            default:
              break;
          }
          break;
        case GST_MIKEY_SP_SRTP_ENC_KEY_LEN:
          switch (param->val[0]) {
            case AES_128_KEY_LEN:
              if (enc_alg == GST_MIKEY_ENC_AES_CM_128 ||
                  enc_alg == GST_MIKEY_ENC_AES_KW_128) {
                srtp_cipher = "aes-128-icm";
              } else if (enc_alg == GST_MIKEY_ENC_AES_GCM_128) {
                srtp_cipher = "aes-128-gcm";
              }
              break;
            case AES_256_KEY_LEN:
              if (enc_alg == GST_MIKEY_ENC_AES_CM_128 ||
                  enc_alg == GST_MIKEY_ENC_AES_KW_128) {
                srtp_cipher = "aes-256-icm";
              } else if (enc_alg == GST_MIKEY_ENC_AES_GCM_128) {
                srtp_cipher = "aes-256-gcm";
              }
              break;
            default:
              break;
          }
          break;
        case GST_MIKEY_SP_SRTP_AUTH_ALG:
          switch (param->val[0]) {
            case GST_MIKEY_MAC_NULL:
              srtp_auth = "null";
              break;
            case GST_MIKEY_MAC_HMAC_SHA_1_160:
              srtp_auth = "hmac-sha1-80";
              break;
            default:
              break;
          }
          break;
        case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN:
          switch (param->val[0]) {
            case HMAC_32_KEY_LEN:
              srtp_auth = "hmac-sha1-32";
              break;
            case HMAC_80_KEY_LEN:
              srtp_auth = "hmac-sha1-80";
              break;
            default:
              break;
          }
          break;
        case GST_MIKEY_SP_SRTP_SRTP_ENC:
          break;
        case GST_MIKEY_SP_SRTP_SRTCP_ENC:
          break;
        default:
          break;
      }
    }
  }
  /* now configure the SRTP parameters */
  gst_caps_set_simple (caps,
      "srtp-cipher", G_TYPE_STRING, srtp_cipher,
      "srtp-auth", G_TYPE_STRING, srtp_auth,
      "srtcp-cipher", G_TYPE_STRING, srtp_cipher,
      "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL);

  return TRUE;
}

static gboolean
handle_mikey_data (GstRTSPStream * stream, guint8 * data, gsize size)
{
  GstMIKEYMessage *msg;
  guint i, n_cs;
  GstCaps *caps = NULL;
  GstMIKEYPayloadKEMAC *kemac;
  const GstMIKEYPayloadKeyData *pkd;
  GstBuffer *key;

  /* the MIKEY message contains a CSB or crypto session bundle. It is a
   * set of Crypto Sessions protected with the same master key.
   * In the context of SRTP, an RTP and its RTCP stream is part of a
   * crypto session */
  if ((msg = gst_mikey_message_new_from_data (data, size, NULL, NULL)) == NULL)
    goto parse_failed;

  /* we can only handle SRTP crypto sessions for now */
  if (msg->map_type != GST_MIKEY_MAP_TYPE_SRTP)
    goto invalid_map_type;

  /* get the number of crypto sessions. This maps SSRC to its
   * security parameters */
  n_cs = gst_mikey_message_get_n_cs (msg);
  if (n_cs == 0)
    goto no_crypto_sessions;

  /* we also need keys */
  if (!(kemac = (GstMIKEYPayloadKEMAC *) gst_mikey_message_find_payload
          (msg, GST_MIKEY_PT_KEMAC, 0)))
    goto no_keys;

  /* we don't support encrypted keys */
  if (kemac->enc_alg != GST_MIKEY_ENC_NULL
      || kemac->mac_alg != GST_MIKEY_MAC_NULL)
    goto unsupported_encryption;

  /* get Key data sub-payload */
  pkd = (const GstMIKEYPayloadKeyData *)
      gst_mikey_payload_kemac_get_sub (&kemac->pt, 0);

  key = gst_buffer_new_memdup (pkd->key_data, pkd->key_len);

  /* go over all crypto sessions and create the security policy for each
   * SSRC */
  for (i = 0; i < n_cs; i++) {
    const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i);

    caps = gst_caps_new_simple ("application/x-srtp",
        "ssrc", G_TYPE_UINT, map->ssrc,
        "roc", G_TYPE_UINT, map->roc, "srtp-key", GST_TYPE_BUFFER, key, NULL);
    mikey_apply_policy (caps, msg, map->policy);

    gst_rtsp_stream_update_crypto (stream, map->ssrc, caps);
    gst_caps_unref (caps);
  }
  gst_mikey_message_unref (msg);
  gst_buffer_unref (key);

  return TRUE;

  /* ERRORS */
parse_failed:
  {
    GST_DEBUG_OBJECT (stream, "failed to parse MIKEY message");
    return FALSE;
  }
invalid_map_type:
  {
    GST_DEBUG_OBJECT (stream, "invalid map type %d", msg->map_type);
    goto cleanup_message;
  }
no_crypto_sessions:
  {
    GST_DEBUG_OBJECT (stream, "no crypto sessions");
    goto cleanup_message;
  }
no_keys:
  {
    GST_DEBUG_OBJECT (stream, "no keys found");
    goto cleanup_message;
  }
unsupported_encryption:
  {
    GST_DEBUG_OBJECT (stream, "unsupported key encryption");
    goto cleanup_message;
  }
cleanup_message:
  {
    gst_mikey_message_unref (msg);
    return FALSE;
  }
}

#define IS_STRIP_CHAR(c) (g_ascii_isspace ((guchar)(c)) || ((c) == '\"'))

static void
strip_chars (gchar * str)
{
  gchar *s;
  gsize len;

  len = strlen (str);
  while (len--) {
    if (!IS_STRIP_CHAR (str[len]))
      break;
    str[len] = '\0';
  }
  for (s = str; *s && IS_STRIP_CHAR (*s); s++);
  memmove (str, s, len + 1);
}

/**
 * gst_rtsp_stream_handle_keymgmt:
 * @stream: a #GstRTSPStream
 * @keymgmt: a keymgmt header
 *
 * Parse and handle a KeyMgmt header.
 *
 * Since: 1.16
 */
/* KeyMgmt = "KeyMgmt" ":" key-mgmt-spec 0*("," key-mgmt-spec)
 * key-mgmt-spec = "prot" "=" KMPID ";" ["uri" "=" %x22 URI %x22 ";"]
 */
gboolean
gst_rtsp_stream_handle_keymgmt (GstRTSPStream * stream, const gchar * keymgmt)
{
  gchar **specs;
  gint i, j;

  specs = g_strsplit (keymgmt, ",", 0);
  for (i = 0; specs[i]; i++) {
    gchar **split;

    split = g_strsplit (specs[i], ";", 0);
    for (j = 0; split[j]; j++) {
      g_strstrip (split[j]);
      if (g_str_has_prefix (split[j], "prot=")) {
        g_strstrip (split[j] + 5);
        if (!g_str_equal (split[j] + 5, "mikey"))
          break;
        GST_DEBUG ("found mikey");
      } else if (g_str_has_prefix (split[j], "uri=")) {
        strip_chars (split[j] + 4);
        GST_DEBUG ("found uri '%s'", split[j] + 4);
      } else if (g_str_has_prefix (split[j], "data=")) {
        guchar *data;
        gsize size;
        strip_chars (split[j] + 5);
        GST_DEBUG ("found data '%s'", split[j] + 5);
        data = g_base64_decode_inplace (split[j] + 5, &size);
        handle_mikey_data (stream, data, size);
      }
    }
    g_strfreev (split);
  }
  g_strfreev (specs);
  return TRUE;
}


/**
 * gst_rtsp_stream_get_ulpfec_pt:
 *
 * Returns: the payload type used for ULPFEC protection packets
 *
 * Since: 1.16
 */
guint
gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream * stream)
{
  guint res;

  g_mutex_lock (&stream->priv->lock);
  res = stream->priv->ulpfec_pt;
  g_mutex_unlock (&stream->priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_ulpfec_pt:
 *
 * Set the payload type to be used for ULPFEC protection packets
 *
 * Since: 1.16
 */
void
gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream * stream, guint pt)
{
  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  g_mutex_lock (&stream->priv->lock);
  stream->priv->ulpfec_pt = pt;
  if (stream->priv->ulpfec_encoder) {
    g_object_set (stream->priv->ulpfec_encoder, "pt", pt, NULL);
  }
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_request_ulpfec_decoder:
 *
 * Creating a rtpulpfecdec element
 *
 * Returns: (transfer full) (nullable): a #GstElement.
 *
 * Since: 1.16
 */
GstElement *
gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream * stream,
    GstElement * rtpbin, guint sessid)
{
  GObject *internal_storage = NULL;

  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
  stream->priv->ulpfec_decoder =
      gst_object_ref (gst_element_factory_make ("rtpulpfecdec", NULL));

  g_signal_emit_by_name (G_OBJECT (rtpbin), "get-internal-storage", sessid,
      &internal_storage);
  g_object_set (stream->priv->ulpfec_decoder, "storage", internal_storage,
      NULL);
  g_object_unref (internal_storage);
  update_ulpfec_decoder_pt (stream);

  return stream->priv->ulpfec_decoder;
}

/**
 * gst_rtsp_stream_request_ulpfec_encoder:
 *
 * Creating a rtpulpfecenc element
 *
 * Returns: (transfer full) (nullable): a #GstElement.
 *
 * Since: 1.16
 */
GstElement *
gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream * stream, guint sessid)
{
  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);

  if (!stream->priv->ulpfec_percentage)
    return NULL;

  stream->priv->ulpfec_encoder =
      gst_object_ref (gst_element_factory_make ("rtpulpfecenc", NULL));

  g_object_set (stream->priv->ulpfec_encoder, "pt", stream->priv->ulpfec_pt,
      "percentage", stream->priv->ulpfec_percentage, NULL);

  return stream->priv->ulpfec_encoder;
}

/**
 * gst_rtsp_stream_set_ulpfec_percentage:
 *
 * Sets the amount of redundancy to apply when creating ULPFEC
 * protection packets.
 *
 * Since: 1.16
 */
void
gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream * stream, guint percentage)
{
  g_return_if_fail (GST_IS_RTSP_STREAM (stream));

  g_mutex_lock (&stream->priv->lock);
  stream->priv->ulpfec_percentage = percentage;
  if (stream->priv->ulpfec_encoder) {
    g_object_set (stream->priv->ulpfec_encoder, "percentage", percentage, NULL);
  }
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_get_ulpfec_percentage:
 *
 * Returns: the amount of redundancy applied when creating ULPFEC
 * protection packets.
 *
 * Since: 1.16
 */
guint
gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream * stream)
{
  guint res;

  g_mutex_lock (&stream->priv->lock);
  res = stream->priv->ulpfec_percentage;
  g_mutex_unlock (&stream->priv->lock);

  return res;
}

/**
 * gst_rtsp_stream_set_rate_control:
 *
 * Define whether @stream will follow the Rate-Control=no behaviour as specified
 * in the ONVIF replay spec.
 *
 * Since: 1.18
 */
void
gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled)
{
  GST_DEBUG_OBJECT (stream, "%s rate control",
      enabled ? "Enabling" : "Disabling");

  g_mutex_lock (&stream->priv->lock);
  stream->priv->do_rate_control = enabled;
  if (stream->priv->appsink[0])
    g_object_set (stream->priv->appsink[0], "sync", enabled, NULL);
  if (stream->priv->payloader
      && g_object_class_find_property (G_OBJECT_GET_CLASS (stream->
              priv->payloader), "onvif-no-rate-control"))
    g_object_set (stream->priv->payloader, "onvif-no-rate-control", !enabled,
        NULL);
  if (stream->priv->session) {
    g_object_set (stream->priv->session, "disable-sr-timestamp", !enabled,
        NULL);
  }
  g_mutex_unlock (&stream->priv->lock);
}

/**
 * gst_rtsp_stream_get_rate_control:
 *
 * Returns: whether @stream will follow the Rate-Control=no behaviour as specified
 * in the ONVIF replay spec.
 *
 * Since: 1.18
 */
gboolean
gst_rtsp_stream_get_rate_control (GstRTSPStream * stream)
{
  gboolean ret;

  g_mutex_lock (&stream->priv->lock);
  ret = stream->priv->do_rate_control;
  g_mutex_unlock (&stream->priv->lock);

  return ret;
}

/**
 * gst_rtsp_stream_unblock_rtcp:
 *
 * Remove blocking probe from the RTCP source. When creating an UDP source for
 * RTCP it is initially blocked until this function is called.
 * This functions should be called once the pipeline is ready for handling RTCP
 * packets.
 *
 * Since: 1.20
 */
void
gst_rtsp_stream_unblock_rtcp (GstRTSPStream * stream)
{
  GstRTSPStreamPrivate *priv;

  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  if (priv->block_early_rtcp_probe != 0) {
    gst_pad_remove_probe
        (priv->block_early_rtcp_pad, priv->block_early_rtcp_probe);
    priv->block_early_rtcp_probe = 0;
    gst_object_unref (priv->block_early_rtcp_pad);
    priv->block_early_rtcp_pad = NULL;
  }
  if (priv->block_early_rtcp_probe_ipv6 != 0) {
    gst_pad_remove_probe
        (priv->block_early_rtcp_pad_ipv6, priv->block_early_rtcp_probe_ipv6);
    priv->block_early_rtcp_probe_ipv6 = 0;
    gst_object_unref (priv->block_early_rtcp_pad_ipv6);
    priv->block_early_rtcp_pad_ipv6 = NULL;
  }
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_stream_set_drop_delta_units:
 * @stream: a #GstRTSPStream
 * @drop: TRUE if delta unit frames are supposed to be dropped.
 *
 * Decide whether the blocking probe is supposed to drop delta units at the
 * beginning of a stream.
 *
 * Since: 1.24
 */
void
gst_rtsp_stream_set_drop_delta_units (GstRTSPStream * stream, gboolean drop)
{
  GstRTSPStreamPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
  priv = stream->priv;
  g_mutex_lock (&priv->lock);
  priv->drop_delta_units = drop;
  g_mutex_unlock (&priv->lock);
}
07070100000062000081A400000000000000000000000169D554BF00004053000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-stream.h  /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>
#include <gst/rtsp/rtsp.h>
#include <gio/gio.h>

#ifndef __GST_RTSP_STREAM_H__
#define __GST_RTSP_STREAM_H__

#include "rtsp-server-prelude.h"

G_BEGIN_DECLS

/* types for the media stream */
#define GST_TYPE_RTSP_STREAM              (gst_rtsp_stream_get_type ())
#define GST_IS_RTSP_STREAM(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_STREAM))
#define GST_IS_RTSP_STREAM_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_STREAM))
#define GST_RTSP_STREAM_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_STREAM, GstRTSPStreamClass))
#define GST_RTSP_STREAM(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_STREAM, GstRTSPStream))
#define GST_RTSP_STREAM_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_STREAM, GstRTSPStreamClass))
#define GST_RTSP_STREAM_CAST(obj)         ((GstRTSPStream*)(obj))
#define GST_RTSP_STREAM_CLASS_CAST(klass) ((GstRTSPStreamClass*)(klass))

typedef struct _GstRTSPStream GstRTSPStream;
typedef struct _GstRTSPStreamClass GstRTSPStreamClass;
typedef struct _GstRTSPStreamPrivate GstRTSPStreamPrivate;

#include "rtsp-stream-transport.h"
#include "rtsp-address-pool.h"
#include "rtsp-session.h"
#include "rtsp-media.h"

/**
 * GstRTSPStream:
 *
 * The definition of a media stream.
 */
struct _GstRTSPStream {
  GObject       parent;

  /*< private >*/
  GstRTSPStreamPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

struct _GstRTSPStreamClass {
  GObjectClass parent_class;

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType             gst_rtsp_stream_get_type         (void);

GST_RTSP_SERVER_API
GstRTSPStream *   gst_rtsp_stream_new              (guint idx, GstElement *payloader,
                                                    GstPad *pad) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_index        (GstRTSPStream *stream);

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_pt           (GstRTSPStream *stream);

GST_RTSP_SERVER_API
GstPad *          gst_rtsp_stream_get_srcpad       (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstPad *          gst_rtsp_stream_get_sinkpad      (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_control      (GstRTSPStream *stream, const gchar *control);

GST_RTSP_SERVER_API
gchar *           gst_rtsp_stream_get_control      (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_has_control      (GstRTSPStream *stream, const gchar *control);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_mtu          (GstRTSPStream *stream, guint mtu);

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_mtu          (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_dscp_qos     (GstRTSPStream *stream, gint dscp_qos);

GST_RTSP_SERVER_API
gint              gst_rtsp_stream_get_dscp_qos     (GstRTSPStream *stream);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_is_transport_supported  (GstRTSPStream *stream,
                                                           GstRTSPTransport *transport);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_profiles     (GstRTSPStream *stream, GstRTSPProfile profiles);

GST_RTSP_SERVER_API
GstRTSPProfile    gst_rtsp_stream_get_profiles     (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_protocols    (GstRTSPStream *stream, GstRTSPLowerTrans protocols);

GST_RTSP_SERVER_API
GstRTSPLowerTrans gst_rtsp_stream_get_protocols    (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_address_pool (GstRTSPStream *stream, GstRTSPAddressPool *pool);

GST_RTSP_SERVER_API
GstRTSPAddressPool *
                  gst_rtsp_stream_get_address_pool (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_multicast_iface (GstRTSPStream *stream, const gchar * multicast_iface);

GST_RTSP_SERVER_API
gchar *           gst_rtsp_stream_get_multicast_iface (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPAddress *  gst_rtsp_stream_reserve_address  (GstRTSPStream *stream,
                                                    const gchar * address,
                                                    guint port,
                                                    guint n_ports,
                                                    guint ttl) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_join_bin         (GstRTSPStream *stream,
                                                    GstBin *bin, GstElement *rtpbin,
                                                    GstState state);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_leave_bin        (GstRTSPStream *stream,
                                                    GstBin *bin, GstElement *rtpbin);

GST_RTSP_SERVER_API
GstBin *          gst_rtsp_stream_get_joined_bin   (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_set_blocked      (GstRTSPStream * stream,
                                                    gboolean blocked);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_is_blocking      (GstRTSPStream * stream);


GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_unblock_linked   (GstRTSPStream * stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_client_side (GstRTSPStream *stream, gboolean client_side);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_is_client_side (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_get_server_port  (GstRTSPStream *stream,
                                                    GstRTSPRange *server_port,
                                                    GSocketFamily family);

GST_RTSP_SERVER_API
GstRTSPAddress *  gst_rtsp_stream_get_multicast_address (GstRTSPStream *stream,
                                                         GSocketFamily family) G_GNUC_WARN_UNUSED_RESULT;


GST_RTSP_SERVER_API
GObject *         gst_rtsp_stream_get_rtpsession   (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstElement *      gst_rtsp_stream_get_srtp_encoder (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void              gst_rtsp_stream_get_ssrc         (GstRTSPStream *stream,
                                                    guint *ssrc);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_get_rtpinfo      (GstRTSPStream *stream,
                                                    guint *rtptime, guint *seq,
                                                    guint *clock_rate,
                                                    GstClockTime *running_time);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_get_rates        (GstRTSPStream * stream,
                                                    gdouble * rate,
                                                    gdouble * applied_rate);

GST_RTSP_SERVER_API
GstCaps *         gst_rtsp_stream_get_caps         (GstRTSPStream *stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstFlowReturn     gst_rtsp_stream_recv_rtp         (GstRTSPStream *stream,
                                                    GstBuffer *buffer);

GST_RTSP_SERVER_API
GstFlowReturn     gst_rtsp_stream_recv_rtcp        (GstRTSPStream *stream,
                                                    GstBuffer *buffer);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_add_transport    (GstRTSPStream *stream,
                                                    GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_remove_transport (GstRTSPStream *stream,
                                                    GstRTSPStreamTransport *trans);

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtp_socket   (GstRTSPStream *stream,
                                                    GSocketFamily family) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtcp_socket  (GstRTSPStream *stream,
                                                    GSocketFamily family) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtp_multicast_socket (GstRTSPStream *stream,
                                                            GSocketFamily family) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtcp_multicast_socket (GstRTSPStream *stream,
                                                             GSocketFamily family) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_add_multicast_client_address (GstRTSPStream * stream,
                                                                const gchar * destination,
                                                                guint rtp_port,
                                                                guint rtcp_port,
                                                                GSocketFamily family);

GST_RTSP_SERVER_API
gchar *           gst_rtsp_stream_get_multicast_client_addresses (GstRTSPStream * stream) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_update_crypto    (GstRTSPStream * stream,
                                                    guint ssrc, GstCaps * crypto);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_query_position   (GstRTSPStream * stream,
                                                    gint64 * position);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_query_stop       (GstRTSPStream * stream,
                                                    gint64 * stop);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_seekable         (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_seqnum_offset          (GstRTSPStream *stream, guint16 seqnum);

GST_RTSP_SERVER_API
guint16           gst_rtsp_stream_get_current_seqnum          (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_retransmission_time     (GstRTSPStream *stream, GstClockTime time);

GST_RTSP_SERVER_API
GstClockTime      gst_rtsp_stream_get_retransmission_time     (GstRTSPStream *stream);

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_retransmission_pt       (GstRTSPStream * stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_retransmission_pt       (GstRTSPStream * stream,
                                                               guint rtx_pt);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_buffer_size  (GstRTSPStream *stream, guint size);

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_buffer_size  (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_pt_map                 (GstRTSPStream * stream, guint pt, GstCaps * caps);

GST_RTSP_SERVER_API
GstElement *      gst_rtsp_stream_request_aux_sender         (GstRTSPStream * stream, guint sessid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstElement *      gst_rtsp_stream_request_aux_receiver       (GstRTSPStream * stream, guint sessid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_allocate_udp_sockets       (GstRTSPStream * stream, GSocketFamily family,
                                                              GstRTSPTransport *transport, gboolean use_client_settings);

GST_RTSP_SERVER_API
void                    gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream, GstRTSPPublishClockMode mode);

GST_RTSP_SERVER_API
GstRTSPPublishClockMode gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream);

GST_RTSP_SERVER_API
gboolean                gst_rtsp_stream_set_max_mcast_ttl  (GstRTSPStream *stream, guint ttl);

GST_RTSP_SERVER_API
guint             gst_rtsp_stream_get_max_mcast_ttl  (GstRTSPStream *stream);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_verify_mcast_ttl  (GstRTSPStream *stream, guint ttl);

GST_RTSP_SERVER_API
void              gst_rtsp_stream_set_bind_mcast_address  (GstRTSPStream * stream, gboolean bind_mcast_addr);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_is_bind_mcast_address (GstRTSPStream * stream);

GST_RTSP_SERVER_API
gboolean          gst_rtsp_stream_complete_stream (GstRTSPStream * stream, const GstRTSPTransport * transport);

GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_is_complete (GstRTSPStream * stream);

GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_is_sender (GstRTSPStream * stream);

GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_is_receiver (GstRTSPStream * stream);

GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_handle_keymgmt (GstRTSPStream *stream, const gchar *keymgmt);

/* ULP Forward Error Correction (RFC 5109) */
GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_get_ulpfec_enabled (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void               gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream *stream, guint pt);

GST_RTSP_SERVER_API
guint              gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream *stream);

GST_RTSP_SERVER_API
GstElement *       gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream *stream, GstElement *rtpbin, guint sessid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstElement *       gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream *stream, guint sessid) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void               gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream *stream, guint percentage);

GST_RTSP_SERVER_API
guint              gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream *stream);

GST_RTSP_SERVER_API
void               gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled);

GST_RTSP_SERVER_API
gboolean           gst_rtsp_stream_get_rate_control (GstRTSPStream * stream);

GST_RTSP_SERVER_API
void               gst_rtsp_stream_unblock_rtcp (GstRTSPStream * stream);

/**
 * GstRTSPStreamTransportFilterFunc:
 * @stream: a #GstRTSPStream object
 * @trans: a #GstRTSPStreamTransport in @stream
 * @user_data: user data that has been given to gst_rtsp_stream_transport_filter()
 *
 * This function will be called by the gst_rtsp_stream_transport_filter(). An
 * implementation should return a value of #GstRTSPFilterResult.
 *
 * When this function returns #GST_RTSP_FILTER_REMOVE, @trans will be removed
 * from @stream.
 *
 * A return value of #GST_RTSP_FILTER_KEEP will leave @trans untouched in
 * @stream.
 *
 * A value of #GST_RTSP_FILTER_REF will add @trans to the result #GList of
 * gst_rtsp_stream_transport_filter().
 *
 * Returns: a #GstRTSPFilterResult.
 */
typedef GstRTSPFilterResult (*GstRTSPStreamTransportFilterFunc) (GstRTSPStream *stream,
                                                                 GstRTSPStreamTransport *trans,
                                                                 gpointer user_data);

GST_RTSP_SERVER_API
GList *                gst_rtsp_stream_transport_filter  (GstRTSPStream *stream,
                                                          GstRTSPStreamTransportFilterFunc func,
                                                          gpointer user_data) G_GNUC_WARN_UNUSED_RESULT;

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPStream, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_STREAM_H__ */
 07070100000063000081A400000000000000000000000169D554BF00003DD8000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-thread-pool.c /* GStreamer
 * Copyright (C) 2013 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-thread-pool
 * @short_description: A pool of threads
 * @see_also: #GstRTSPMedia, #GstRTSPClient
 *
 * A #GstRTSPThreadPool manages reusable threads for various server tasks.
 * Currently the defined thread types can be found in #GstRTSPThreadType.
 *
 * Threads of type #GST_RTSP_THREAD_TYPE_CLIENT are used to handle requests from
 * a connected client. With gst_rtsp_thread_pool_get_max_threads() a maximum
 * number of threads can be set after which the pool will start to reuse the
 * same thread for multiple clients.
 *
 * Threads of type #GST_RTSP_THREAD_TYPE_MEDIA will be used to perform the state
 * changes of the media pipelines and handle its bus messages.
 *
 * gst_rtsp_thread_pool_get_thread() can be used to create a #GstRTSPThread
 * object of the right type. The thread object contains a mainloop and context
 * that run in a seperate thread and can be used to attached sources to.
 *
 * gst_rtsp_thread_reuse() can be used to reuse a thread for multiple purposes.
 * If all gst_rtsp_thread_reuse() calls are matched with a
 * gst_rtsp_thread_stop() call, the mainloop will be quit and the thread will
 * stop.
 *
 * To configure the threads, a subclass of this object should be made and the
 * virtual methods should be overriden to implement the desired functionality.
 *
 * Last reviewed on 2013-07-11 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-thread-pool.h"

typedef struct _GstRTSPThreadImpl
{
  GstRTSPThread thread;

  gint reused;
  GSource *source;
  /* FIXME, the source has to be part of GstRTSPThreadImpl, due to a bug in GLib:
   * https://bugzilla.gnome.org/show_bug.cgi?id=720186 */
} GstRTSPThreadImpl;

GST_DEFINE_MINI_OBJECT_TYPE (GstRTSPThread, gst_rtsp_thread);

static void gst_rtsp_thread_init (GstRTSPThreadImpl * impl);

static void
_gst_rtsp_thread_free (GstRTSPThreadImpl * impl)
{
  GST_DEBUG ("free thread %p", impl);

  g_source_unref (impl->source);
  g_main_loop_unref (impl->thread.loop);
  g_main_context_unref (impl->thread.context);
  g_free (impl);
}

static GstRTSPThread *
_gst_rtsp_thread_copy (GstRTSPThreadImpl * impl)
{
  GstRTSPThreadImpl *copy;

  GST_DEBUG ("copy thread %p", impl);

  copy = g_new0 (GstRTSPThreadImpl, 1);
  gst_rtsp_thread_init (copy);
  copy->thread.context = g_main_context_ref (impl->thread.context);
  copy->thread.loop = g_main_loop_ref (impl->thread.loop);

  return GST_RTSP_THREAD (copy);
}

static void
gst_rtsp_thread_init (GstRTSPThreadImpl * impl)
{
  gst_mini_object_init (GST_MINI_OBJECT_CAST (impl), 0,
      GST_TYPE_RTSP_THREAD,
      (GstMiniObjectCopyFunction) _gst_rtsp_thread_copy, NULL,
      (GstMiniObjectFreeFunction) _gst_rtsp_thread_free);

  g_atomic_int_set (&impl->reused, 1);
}

/**
 * gst_rtsp_thread_new:
 * @type: the thread type
 *
 * Create a new thread object that can run a mainloop.
 *
 * Returns: (transfer full): a #GstRTSPThread.
 */
GstRTSPThread *
gst_rtsp_thread_new (GstRTSPThreadType type)
{
  GstRTSPThreadImpl *impl;

  impl = g_new0 (GstRTSPThreadImpl, 1);

  gst_rtsp_thread_init (impl);
  impl->thread.type = type;
  impl->thread.context = g_main_context_new ();
  impl->thread.loop = g_main_loop_new (impl->thread.context, TRUE);

  return GST_RTSP_THREAD (impl);
}

/**
 * gst_rtsp_thread_reuse:
 * @thread: (transfer none): a #GstRTSPThread
 *
 * Reuse the mainloop of @thread
 *
 * Returns: %TRUE if the mainloop could be reused
 */
gboolean
gst_rtsp_thread_reuse (GstRTSPThread * thread)
{
  GstRTSPThreadImpl *impl = (GstRTSPThreadImpl *) thread;
  gboolean res;

  g_return_val_if_fail (GST_IS_RTSP_THREAD (thread), FALSE);

  GST_DEBUG ("reuse thread %p", thread);

  res = g_atomic_int_add (&impl->reused, 1) > 0;
  if (res)
    gst_rtsp_thread_ref (thread);

  return res;
}

static gboolean
do_quit (GstRTSPThread * thread)
{
  GST_DEBUG ("stop mainloop of thread %p", thread);
  g_main_loop_quit (thread->loop);
  return FALSE;
}

/**
 * gst_rtsp_thread_stop:
 * @thread: (transfer full): a #GstRTSPThread
 *
 * Stop and unref @thread. When no threads are using the mainloop, the thread
 * will be stopped and the final ref to @thread will be released.
 */
void
gst_rtsp_thread_stop (GstRTSPThread * thread)
{
  GstRTSPThreadImpl *impl = (GstRTSPThreadImpl *) thread;

  g_return_if_fail (GST_IS_RTSP_THREAD (thread));

  GST_DEBUG ("stop thread %p", thread);

  if (g_atomic_int_dec_and_test (&impl->reused)) {
    GST_DEBUG ("add idle source to quit mainloop of thread %p", thread);
    impl->source = g_idle_source_new ();
    g_source_set_callback (impl->source, (GSourceFunc) do_quit,
        thread, (GDestroyNotify) gst_rtsp_thread_unref);
    g_source_attach (impl->source, thread->context);
  } else
    gst_rtsp_thread_unref (thread);
}

struct _GstRTSPThreadPoolPrivate
{
  GMutex lock;

  gint max_threads;
  /* currently used mainloops */
  GQueue threads;
};

#define DEFAULT_MAX_THREADS 1

enum
{
  PROP_0,
  PROP_MAX_THREADS,
  PROP_LAST
};

GST_DEBUG_CATEGORY_STATIC (rtsp_thread_pool_debug);
#define GST_CAT_DEFAULT rtsp_thread_pool_debug

static GQuark thread_pool;

static void gst_rtsp_thread_pool_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec);
static void gst_rtsp_thread_pool_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_thread_pool_finalize (GObject * obj);

static gpointer do_loop (GstRTSPThread * thread);
static GstRTSPThread *default_get_thread (GstRTSPThreadPool * pool,
    GstRTSPThreadType type, GstRTSPContext * ctx);

G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPThreadPool, gst_rtsp_thread_pool,
    G_TYPE_OBJECT);

static void
gst_rtsp_thread_pool_class_init (GstRTSPThreadPoolClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gst_rtsp_thread_pool_get_property;
  gobject_class->set_property = gst_rtsp_thread_pool_set_property;
  gobject_class->finalize = gst_rtsp_thread_pool_finalize;

  /**
   * GstRTSPThreadPool::max-threads:
   *
   * The maximum amount of threads to use for client connections. A value of
   * 0 means to use only the mainloop, -1 means an unlimited amount of
   * threads.
   */
  g_object_class_install_property (gobject_class, PROP_MAX_THREADS,
      g_param_spec_int ("max-threads", "Max Threads",
          "The maximum amount of threads to use for client connections "
          "(0 = only mainloop, -1 = unlimited)", -1, G_MAXINT,
          DEFAULT_MAX_THREADS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  klass->get_thread = default_get_thread;

  GST_DEBUG_CATEGORY_INIT (rtsp_thread_pool_debug, "rtspthreadpool", 0,
      "GstRTSPThreadPool");

  thread_pool = g_quark_from_string ("gst.rtsp.thread.pool");
}

static void
gst_rtsp_thread_pool_init (GstRTSPThreadPool * pool)
{
  GstRTSPThreadPoolPrivate *priv;

  pool->priv = priv = gst_rtsp_thread_pool_get_instance_private (pool);

  g_mutex_init (&priv->lock);
  priv->max_threads = DEFAULT_MAX_THREADS;
  g_queue_init (&priv->threads);
}

static void
gst_rtsp_thread_pool_finalize (GObject * obj)
{
  GstRTSPThreadPool *pool = GST_RTSP_THREAD_POOL (obj);
  GstRTSPThreadPoolPrivate *priv = pool->priv;

  GST_INFO ("finalize pool %p", pool);

  g_queue_clear (&priv->threads);
  g_mutex_clear (&priv->lock);

  G_OBJECT_CLASS (gst_rtsp_thread_pool_parent_class)->finalize (obj);
}

static void
gst_rtsp_thread_pool_get_property (GObject * object, guint propid,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPThreadPool *pool = GST_RTSP_THREAD_POOL (object);

  switch (propid) {
    case PROP_MAX_THREADS:
      g_value_set_int (value, gst_rtsp_thread_pool_get_max_threads (pool));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static void
gst_rtsp_thread_pool_set_property (GObject * object, guint propid,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPThreadPool *pool = GST_RTSP_THREAD_POOL (object);

  switch (propid) {
    case PROP_MAX_THREADS:
      gst_rtsp_thread_pool_set_max_threads (pool, g_value_get_int (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
  }
}

static gpointer
do_loop (GstRTSPThread * thread)
{
  GstRTSPThreadPoolPrivate *priv;
  GstRTSPThreadPoolClass *klass;
  GstRTSPThreadPool *pool;

  pool = gst_mini_object_get_qdata (GST_MINI_OBJECT (thread), thread_pool);
  priv = pool->priv;

  klass = GST_RTSP_THREAD_POOL_GET_CLASS (pool);

  if (klass->thread_enter)
    klass->thread_enter (pool, thread);

  GST_INFO ("enter mainloop of thread %p", thread);
  g_main_loop_run (thread->loop);
  GST_INFO ("exit mainloop of thread %p", thread);

  if (klass->thread_leave)
    klass->thread_leave (pool, thread);

  g_mutex_lock (&priv->lock);
  g_queue_remove (&priv->threads, thread);
  g_mutex_unlock (&priv->lock);

  gst_rtsp_thread_unref (thread);

  return NULL;
}

/**
 * gst_rtsp_thread_pool_new:
 *
 * Create a new #GstRTSPThreadPool instance.
 *
 * Returns: (transfer full): a new #GstRTSPThreadPool
 */
GstRTSPThreadPool *
gst_rtsp_thread_pool_new (void)
{
  GstRTSPThreadPool *result;

  result = g_object_new (GST_TYPE_RTSP_THREAD_POOL, NULL);

  return result;
}

/**
 * gst_rtsp_thread_pool_set_max_threads:
 * @pool: a #GstRTSPThreadPool
 * @max_threads: maximum threads
 *
 * Set the maximum threads used by the pool to handle client requests.
 * A value of 0 will use the pool mainloop, a value of -1 will use an
 * unlimited number of threads.
 */
void
gst_rtsp_thread_pool_set_max_threads (GstRTSPThreadPool * pool,
    gint max_threads)
{
  GstRTSPThreadPoolPrivate *priv;

  g_return_if_fail (GST_IS_RTSP_THREAD_POOL (pool));

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  priv->max_threads = max_threads;
  g_mutex_unlock (&priv->lock);
}

/**
 * gst_rtsp_thread_pool_get_max_threads:
 * @pool: a #GstRTSPThreadPool
 *
 * Get the maximum number of threads used for client connections.
 * See gst_rtsp_thread_pool_set_max_threads().
 *
 * Returns: the maximum number of threads.
 */
gint
gst_rtsp_thread_pool_get_max_threads (GstRTSPThreadPool * pool)
{
  GstRTSPThreadPoolPrivate *priv;
  gint res;

  g_return_val_if_fail (GST_IS_RTSP_THREAD_POOL (pool), -1);

  priv = pool->priv;

  g_mutex_lock (&priv->lock);
  res = priv->max_threads;
  g_mutex_unlock (&priv->lock);

  return res;
}

static GstRTSPThread *
make_thread (GstRTSPThreadPool * pool, GstRTSPThreadType type,
    GstRTSPContext * ctx)
{
  GstRTSPThreadPoolClass *klass;
  GstRTSPThread *thread;

  klass = GST_RTSP_THREAD_POOL_GET_CLASS (pool);

  thread = gst_rtsp_thread_new (type);
  gst_mini_object_set_qdata (GST_MINI_OBJECT (thread), thread_pool,
      g_object_ref (pool), g_object_unref);

  GST_DEBUG_OBJECT (pool, "new thread %p", thread);

  if (klass->configure_thread)
    klass->configure_thread (pool, thread, ctx);

  return thread;
}

static GstRTSPThread *
default_get_thread (GstRTSPThreadPool * pool,
    GstRTSPThreadType type, GstRTSPContext * ctx)
{
  GstRTSPThreadPoolPrivate *priv = pool->priv;
  GstRTSPThreadPoolClass *klass;
  GstRTSPThread *thread;
  GError *error = NULL;

  klass = GST_RTSP_THREAD_POOL_GET_CLASS (pool);

  switch (type) {
    case GST_RTSP_THREAD_TYPE_CLIENT:
      if (priv->max_threads == 0) {
        /* no threads allowed */
        GST_DEBUG_OBJECT (pool, "no client threads allowed");
        thread = NULL;
      } else {
        g_mutex_lock (&priv->lock);
      retry:
        if (priv->max_threads > 0 &&
            g_queue_get_length (&priv->threads) >= priv->max_threads) {
          /* max threads reached, recycle from queue */
          thread = g_queue_pop_head (&priv->threads);
          GST_DEBUG_OBJECT (pool, "recycle client thread %p", thread);
          if (!gst_rtsp_thread_reuse (thread)) {
            GST_DEBUG_OBJECT (pool, "thread %p stopping, retry", thread);
            /* this can happen if we just decremented the reuse counter of the
             * thread and signaled the mainloop that it should stop. We leave
             * the thread out of the queue now, there is no point to add it
             * again, it will be removed from the mainloop otherwise after it
             * stops. */
            goto retry;
          }
        } else {
          /* make more threads */
          GST_DEBUG_OBJECT (pool, "make new client thread");
          thread = make_thread (pool, type, ctx);

          if (!g_thread_pool_push (klass->pool, gst_rtsp_thread_ref (thread),
                  &error))
            goto thread_error;
        }
        g_queue_push_tail (&priv->threads, thread);
        g_mutex_unlock (&priv->lock);
      }
      break;
    case GST_RTSP_THREAD_TYPE_MEDIA:
      GST_DEBUG_OBJECT (pool, "make new media thread");
      thread = make_thread (pool, type, ctx);

      if (!g_thread_pool_push (klass->pool, gst_rtsp_thread_ref (thread),
              &error))
        goto thread_error;
      break;
    default:
      thread = NULL;
      break;
  }
  return thread;

  /* ERRORS */
thread_error:
  {
    GST_ERROR_OBJECT (pool, "failed to push thread %s", error->message);
    gst_rtsp_thread_unref (thread);
    /* drop also the ref dedicated for the pool */
    gst_rtsp_thread_unref (thread);
    g_clear_error (&error);
    return NULL;
  }
}

/**
 * gst_rtsp_thread_pool_get_thread:
 * @pool: a #GstRTSPThreadPool
 * @type: the #GstRTSPThreadType
 * @ctx: (transfer none): a #GstRTSPContext
 *
 * Get a new #GstRTSPThread for @type and @ctx.
 *
 * Returns: (transfer full) (nullable): a new #GstRTSPThread,
 * gst_rtsp_thread_stop() after usage
 */
GstRTSPThread *
gst_rtsp_thread_pool_get_thread (GstRTSPThreadPool * pool,
    GstRTSPThreadType type, GstRTSPContext * ctx)
{
  GstRTSPThreadPoolClass *klass;
  GstRTSPThread *result = NULL;

  g_return_val_if_fail (GST_IS_RTSP_THREAD_POOL (pool), NULL);

  klass = GST_RTSP_THREAD_POOL_GET_CLASS (pool);

  /* We want to be thread safe as there might be 2 threads wanting to get new
   * #GstRTSPThread at the same time
   */
  if (G_UNLIKELY (!g_atomic_pointer_get (&klass->pool))) {
    GThreadPool *t_pool;
    t_pool = g_thread_pool_new ((GFunc) do_loop, klass, -1, FALSE, NULL);
    if (!g_atomic_pointer_compare_and_exchange (&klass->pool,
            (GThreadPool *) NULL, t_pool))
      g_thread_pool_free (t_pool, FALSE, TRUE);
  }

  if (klass->get_thread)
    result = klass->get_thread (pool, type, ctx);

  return result;
}

/**
 * gst_rtsp_thread_pool_cleanup:
 *
 * Wait for all tasks to be stopped and free all allocated resources. This is
 * mainly used in test suites to ensure proper cleanup of internal data
 * structures.
 */
void
gst_rtsp_thread_pool_cleanup (void)
{
  GstRTSPThreadPoolClass *klass;

  klass =
      GST_RTSP_THREAD_POOL_CLASS (g_type_class_ref
      (gst_rtsp_thread_pool_get_type ()));
  if (klass->pool != NULL) {
    g_thread_pool_free (klass->pool, FALSE, TRUE);
    klass->pool = NULL;
  }
  g_type_class_unref (klass);
}
07070100000064000081A400000000000000000000000169D554BF00001976000000000000000000000000000000000000003A00000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-thread-pool.h /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#ifndef __GST_RTSP_THREAD_POOL_H__
#define __GST_RTSP_THREAD_POOL_H__

typedef struct _GstRTSPThread GstRTSPThread;
typedef struct _GstRTSPThreadPool GstRTSPThreadPool;
typedef struct _GstRTSPThreadPoolClass GstRTSPThreadPoolClass;
typedef struct _GstRTSPThreadPoolPrivate GstRTSPThreadPoolPrivate;

#include "rtsp-client.h"

G_BEGIN_DECLS

#define GST_TYPE_RTSP_THREAD_POOL              (gst_rtsp_thread_pool_get_type ())
#define GST_IS_RTSP_THREAD_POOL(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_THREAD_POOL))
#define GST_IS_RTSP_THREAD_POOL_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_THREAD_POOL))
#define GST_RTSP_THREAD_POOL_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_THREAD_POOL, GstRTSPThreadPoolClass))
#define GST_RTSP_THREAD_POOL(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_THREAD_POOL, GstRTSPThreadPool))
#define GST_RTSP_THREAD_POOL_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_THREAD_POOL, GstRTSPThreadPoolClass))
#define GST_RTSP_THREAD_POOL_CAST(obj)         ((GstRTSPThreadPool*)(obj))
#define GST_RTSP_THREAD_POOL_CLASS_CAST(klass) ((GstRTSPThreadPoolClass*)(klass))

GST_RTSP_SERVER_API
GType gst_rtsp_thread_get_type (void);

#define GST_TYPE_RTSP_THREAD        (gst_rtsp_thread_get_type ())
#define GST_IS_RTSP_THREAD(obj)     (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_RTSP_THREAD))
#define GST_RTSP_THREAD_CAST(obj)   ((GstRTSPThread*)(obj))
#define GST_RTSP_THREAD(obj)        (GST_RTSP_THREAD_CAST(obj))

/**
 * GstRTSPThreadType:
 * @GST_RTSP_THREAD_TYPE_CLIENT: a thread to handle the client communication
 * @GST_RTSP_THREAD_TYPE_MEDIA: a thread to handle media 
 *
 * Different thread types
 */
typedef enum
{
  GST_RTSP_THREAD_TYPE_CLIENT,
  GST_RTSP_THREAD_TYPE_MEDIA
} GstRTSPThreadType;

/**
 * GstRTSPThread:
 * @mini_object: parent #GstMiniObject
 * @type: the thread type
 * @context: a #GMainContext
 * @loop: a #GMainLoop
 *
 * Structure holding info about a mainloop running in a thread
 */
struct _GstRTSPThread {
  GstMiniObject mini_object;

  GstRTSPThreadType type;
  GMainContext *context;
  GMainLoop *loop;
};

GST_RTSP_SERVER_API
GstRTSPThread *   gst_rtsp_thread_new      (GstRTSPThreadType type) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
gboolean          gst_rtsp_thread_reuse    (GstRTSPThread * thread);

GST_RTSP_SERVER_API
void              gst_rtsp_thread_stop     (GstRTSPThread * thread);

/**
 * gst_rtsp_thread_ref:
 * @thread: The thread to refcount
 *
 * Increase the refcount of this thread.
 *
 * Returns: (transfer full): @thread (for convenience when doing assignments)
 */
static inline GstRTSPThread *
gst_rtsp_thread_ref (GstRTSPThread * thread)
{
  return (GstRTSPThread *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (thread));
}

/**
 * gst_rtsp_thread_unref:
 * @thread: (transfer full): the thread to refcount
 *
 * Decrease the refcount of an thread, freeing it if the refcount reaches 0.
 */
static inline void
gst_rtsp_thread_unref (GstRTSPThread * thread)
{
  gst_mini_object_unref (GST_MINI_OBJECT_CAST (thread));
}

/**
 * GstRTSPThreadPool:
 *
 * The thread pool structure.
 */
struct _GstRTSPThreadPool {
  GObject       parent;

  /*< private >*/
  GstRTSPThreadPoolPrivate *priv;
  gpointer _gst_reserved[GST_PADDING];
};

/**
 * GstRTSPThreadPoolClass:
 * @pool: a #GThreadPool used internally
 * @get_thread: this function should make or reuse an existing thread that runs
 *        a mainloop.
 * @configure_thread: configure a thread object. this vmethod is called when
 *       a new thread has been created and should be configured.
 * @thread_enter: called from the thread when it is entered
 * @thread_leave: called from the thread when it is left
 *
 * Class for managing threads.
 */
struct _GstRTSPThreadPoolClass {
  GObjectClass  parent_class;

  GThreadPool *pool;

  GstRTSPThread * (*get_thread)        (GstRTSPThreadPool *pool,
                                        GstRTSPThreadType type,
                                        GstRTSPContext *ctx);
  void            (*configure_thread)  (GstRTSPThreadPool *pool,
                                        GstRTSPThread * thread,
                                        GstRTSPContext *ctx);

  void            (*thread_enter)      (GstRTSPThreadPool *pool,
                                        GstRTSPThread *thread);
  void            (*thread_leave)      (GstRTSPThreadPool *pool,
                                        GstRTSPThread *thread);

  /*< private >*/
  gpointer         _gst_reserved[GST_PADDING];
};

GST_RTSP_SERVER_API
GType               gst_rtsp_thread_pool_get_type        (void);

GST_RTSP_SERVER_API
GstRTSPThreadPool * gst_rtsp_thread_pool_new             (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_thread_pool_set_max_threads (GstRTSPThreadPool * pool, gint max_threads);

GST_RTSP_SERVER_API
gint                gst_rtsp_thread_pool_get_max_threads (GstRTSPThreadPool * pool);

GST_RTSP_SERVER_API
GstRTSPThread *     gst_rtsp_thread_pool_get_thread      (GstRTSPThreadPool *pool,
                                                          GstRTSPThreadType type,
                                                          GstRTSPContext *ctx) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
void                gst_rtsp_thread_pool_cleanup         (void);
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPThread, gst_rtsp_thread_unref)
#endif

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPThreadPool, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_THREAD_POOL_H__ */
  07070100000065000081A400000000000000000000000169D554BF00001FAE000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-token.c   /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * SECTION:rtsp-token
 * @short_description: Roles and permissions for a client
 * @see_also: #GstRTSPClient, #GstRTSPPermissions, #GstRTSPAuth
 *
 * A #GstRTSPToken contains the permissions and roles of the user
 * performing the current request. A token is usually created when a user is
 * authenticated by the #GstRTSPAuth object and is then placed as the current
 * token for the current request.
 *
 * #GstRTSPAuth can use the token and its contents to check authorization for
 * various operations by comparing the token to the #GstRTSPPermissions of the
 * object.
 *
 * The accepted values of the token are entirely defined by the #GstRTSPAuth
 * object that implements the security policy.
 *
 * Last reviewed on 2013-07-15 (1.0.0)
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "rtsp-token.h"

typedef struct _GstRTSPTokenImpl
{
  GstRTSPToken token;

  GstStructure *structure;
} GstRTSPTokenImpl;

#define GST_RTSP_TOKEN_STRUCTURE(t)  (((GstRTSPTokenImpl *)(t))->structure)

//GST_DEBUG_CATEGORY_STATIC (rtsp_token_debug);
//#define GST_CAT_DEFAULT rtsp_token_debug

GST_DEFINE_MINI_OBJECT_TYPE (GstRTSPToken, gst_rtsp_token);

static void gst_rtsp_token_init (GstRTSPTokenImpl * token,
    GstStructure * structure);

static void
_gst_rtsp_token_free (GstRTSPToken * token)
{
  GstRTSPTokenImpl *impl = (GstRTSPTokenImpl *) token;

  gst_structure_set_parent_refcount (impl->structure, NULL);
  gst_structure_free (impl->structure);

  g_free (token);
}

static GstRTSPToken *
_gst_rtsp_token_copy (GstRTSPTokenImpl * token)
{
  GstRTSPTokenImpl *copy;
  GstStructure *structure;

  structure = gst_structure_copy (token->structure);

  copy = g_new0 (GstRTSPTokenImpl, 1);
  gst_rtsp_token_init (copy, structure);

  return (GstRTSPToken *) copy;
}

static void
gst_rtsp_token_init (GstRTSPTokenImpl * token, GstStructure * structure)
{
  gst_mini_object_init (GST_MINI_OBJECT_CAST (token), 0,
      GST_TYPE_RTSP_TOKEN,
      (GstMiniObjectCopyFunction) _gst_rtsp_token_copy, NULL,
      (GstMiniObjectFreeFunction) _gst_rtsp_token_free);

  token->structure = structure;
  gst_structure_set_parent_refcount (token->structure,
      &token->token.mini_object.refcount);
}

/**
 * gst_rtsp_token_new_empty: (rename-to gst_rtsp_token_new)
 *
 * Create a new empty Authorization token.
 *
 * Returns: (transfer full): a new empty authorization token.
 */
GstRTSPToken *
gst_rtsp_token_new_empty (void)
{
  GstRTSPTokenImpl *token;
  GstStructure *s;

  s = gst_structure_new_empty ("GstRTSPToken");
  g_return_val_if_fail (s != NULL, NULL);

  token = g_new0 (GstRTSPTokenImpl, 1);
  gst_rtsp_token_init (token, s);

  return (GstRTSPToken *) token;
}

/**
 * gst_rtsp_token_new: (skip)
 * @firstfield: the first fieldname
 * @...: additional arguments
 *
 * Create a new Authorization token with the given fieldnames and values.
 * Arguments are given similar to gst_structure_new().
 *
 * Returns: (transfer full): a new authorization token.
 */
GstRTSPToken *
gst_rtsp_token_new (const gchar * firstfield, ...)
{
  GstRTSPToken *result;
  va_list var_args;

  va_start (var_args, firstfield);
  result = gst_rtsp_token_new_valist (firstfield, var_args);
  va_end (var_args);

  return result;
}

/**
 * gst_rtsp_token_new_valist: (skip)
 * @firstfield: the first fieldname
 * @var_args: additional arguments
 *
 * Create a new Authorization token with the given fieldnames and values.
 * Arguments are given similar to gst_structure_new_valist().
 *
 * Returns: (transfer full): a new authorization token.
 */
GstRTSPToken *
gst_rtsp_token_new_valist (const gchar * firstfield, va_list var_args)
{
  GstRTSPToken *token;
  GstStructure *s;

  g_return_val_if_fail (firstfield != NULL, NULL);

  token = gst_rtsp_token_new_empty ();
  s = GST_RTSP_TOKEN_STRUCTURE (token);
  gst_structure_set_valist (s, firstfield, var_args);

  return token;
}

/**
 * gst_rtsp_token_set_string:
 * @token: The #GstRTSPToken.
 * @field: field to set
 * @string_value: string value to set
 *
 * Sets a string value on @token.
 *
 * Since: 1.14
 */
void
gst_rtsp_token_set_string (GstRTSPToken * token, const gchar * field,
    const gchar * string_value)
{
  GstStructure *s;

  g_return_if_fail (token != NULL);
  g_return_if_fail (field != NULL);
  g_return_if_fail (string_value != NULL);

  s = gst_rtsp_token_writable_structure (token);
  if (s != NULL)
    gst_structure_set (s, field, G_TYPE_STRING, string_value, NULL);
}

/**
 * gst_rtsp_token_set_bool:
 * @token: The #GstRTSPToken.
 * @field: field to set
 * @bool_value: boolean value to set
 *
 * Sets a boolean value on @token.
 *
 * Since: 1.14
 */
void
gst_rtsp_token_set_bool (GstRTSPToken * token, const gchar * field,
    gboolean bool_value)
{
  GstStructure *s;

  g_return_if_fail (token != NULL);
  g_return_if_fail (field != NULL);

  s = gst_rtsp_token_writable_structure (token);
  if (s != NULL)
    gst_structure_set (s, field, G_TYPE_BOOLEAN, bool_value, NULL);
}

/**
 * gst_rtsp_token_get_structure:
 * @token: The #GstRTSPToken.
 *
 * Access the structure of the token.
 *
 * Returns: (transfer none): The structure of the token. The structure is still
 * owned by the token, which means that you should not free it and that the
 * pointer becomes invalid when you free the token.
 *
 * MT safe.
 */
const GstStructure *
gst_rtsp_token_get_structure (GstRTSPToken * token)
{
  g_return_val_if_fail (GST_IS_RTSP_TOKEN (token), NULL);

  return GST_RTSP_TOKEN_STRUCTURE (token);
}

/**
 * gst_rtsp_token_writable_structure:
 * @token: A writable #GstRTSPToken.
 *
 * Get a writable version of the structure.
 *
 * Returns: (transfer none): The structure of the token. The structure is still
 * owned by the token, which means that you should not free it and that the
 * pointer becomes invalid when you free the token. This function ensures
 * that @token is writable, and if so, will never return %NULL.
 *
 * MT safe.
 */
GstStructure *
gst_rtsp_token_writable_structure (GstRTSPToken * token)
{
  g_return_val_if_fail (GST_IS_RTSP_TOKEN (token), NULL);
  g_return_val_if_fail (gst_mini_object_is_writable (GST_MINI_OBJECT_CAST
          (token)), NULL);

  return GST_RTSP_TOKEN_STRUCTURE (token);
}

/**
 * gst_rtsp_token_get_string:
 * @token: a #GstRTSPToken
 * @field: a field name
 *
 * Get the string value of @field in @token.
 *
 * Returns: (transfer none) (nullable): the string value of @field in
 * @token or %NULL when @field is not defined in @token. The string
 * becomes invalid when you free @token.
 */
const gchar *
gst_rtsp_token_get_string (GstRTSPToken * token, const gchar * field)
{
  return gst_structure_get_string (GST_RTSP_TOKEN_STRUCTURE (token), field);
}

/**
 * gst_rtsp_token_is_allowed:
 * @token: a #GstRTSPToken
 * @field: a field name
 *
 * Check if @token has a boolean @field and if it is set to %TRUE.
 *
 * Returns: %TRUE if @token has a boolean field named @field set to %TRUE.
 */
gboolean
gst_rtsp_token_is_allowed (GstRTSPToken * token, const gchar * field)
{
  gboolean result;

  g_return_val_if_fail (GST_IS_RTSP_TOKEN (token), FALSE);
  g_return_val_if_fail (field != NULL, FALSE);

  if (!gst_structure_get_boolean (GST_RTSP_TOKEN_STRUCTURE (token), field,
          &result))
    result = FALSE;

  return result;
}
  07070100000066000081A400000000000000000000000169D554BF00000EBD000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/gst/rtsp-server/rtsp-token.h   /* GStreamer
 * Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#ifndef __GST_RTSP_TOKEN_H__
#define __GST_RTSP_TOKEN_H__

typedef struct _GstRTSPToken GstRTSPToken;

#include "rtsp-auth.h"

G_BEGIN_DECLS

GST_RTSP_SERVER_API
GType gst_rtsp_token_get_type(void);

#define GST_TYPE_RTSP_TOKEN        (gst_rtsp_token_get_type())
#define GST_IS_RTSP_TOKEN(obj)     (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_RTSP_TOKEN))
#define GST_RTSP_TOKEN_CAST(obj)   ((GstRTSPToken*)(obj))
#define GST_RTSP_TOKEN(obj)        (GST_RTSP_TOKEN_CAST(obj))

/**
 * GstRTSPToken:
 *
 * An opaque object used for checking authorisations.
 * It is generated after successful authentication.
 */
struct _GstRTSPToken {
  GstMiniObject mini_object;
};

/* refcounting */
/**
 * gst_rtsp_token_ref:
 * @token: The token to refcount
 *
 * Increase the refcount of this token.
 *
 * Returns: (transfer full): @token (for convenience when doing assignments)
 */
static inline GstRTSPToken *
gst_rtsp_token_ref (GstRTSPToken * token)
{
  return (GstRTSPToken *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (token));
}

/**
 * gst_rtsp_token_unref:
 * @token: (transfer full): the token to refcount
 *
 * Decrease the refcount of an token, freeing it if the refcount reaches 0.
 */
static inline void
gst_rtsp_token_unref (GstRTSPToken * token)
{
  gst_mini_object_unref (GST_MINI_OBJECT_CAST (token));
}


GST_RTSP_SERVER_API
GstRTSPToken *       gst_rtsp_token_new_empty          (void) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPToken *       gst_rtsp_token_new                (const gchar * firstfield, ...) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
GstRTSPToken *       gst_rtsp_token_new_valist         (const gchar * firstfield, va_list var_args) G_GNUC_WARN_UNUSED_RESULT;

GST_RTSP_SERVER_API
const GstStructure * gst_rtsp_token_get_structure      (GstRTSPToken *token);

GST_RTSP_SERVER_API
GstStructure *       gst_rtsp_token_writable_structure (GstRTSPToken *token);

GST_RTSP_SERVER_API
void                 gst_rtsp_token_set_string         (GstRTSPToken * token,
                                                        const gchar  * field,
                                                        const gchar  * string_value);
GST_RTSP_SERVER_API
const gchar *        gst_rtsp_token_get_string         (GstRTSPToken *token,
                                                        const gchar *field);
GST_RTSP_SERVER_API
void                 gst_rtsp_token_set_bool           (GstRTSPToken * token,
                                                        const gchar  * field,
                                                        gboolean       bool_value);
GST_RTSP_SERVER_API
gboolean             gst_rtsp_token_is_allowed         (GstRTSPToken *token,
                                                        const gchar *field);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPToken, gst_rtsp_token_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_TOKEN_H__ */
   07070100000067000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002500000000gst-rtsp-server-1.28.2/gst/rtsp-sink  07070100000068000081A400000000000000000000000169D554BF00027B03000000000000000000000000000000000000003900000000gst-rtsp-server-1.28.2/gst/rtsp-sink/gstrtspclientsink.c  /* GStreamer
 * Copyright (C) <2005,2006> Wim Taymans <wim at fluendo dot com>
 *               <2006> Lutz Mueller <lutz at topfrose dot de>
 *               <2015> Jan Schmidt <jan at centricular dot com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/*
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 * See further explanation attached in License Statement (distributed in the file
 * LICENSE).
 *
 * 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.
 */
/**
 * SECTION:element-rtspclientsink
 *
 * Makes a connection to an RTSP server and send data via RTSP RECORD.
 * rtspclientsink strictly follows RFC 2326
 *
 * RTSP supports transport over TCP or UDP in unicast or multicast mode. By
 * default rtspclientsink will negotiate a connection in the following order:
 * UDP unicast/UDP multicast/TCP. The order cannot be changed but the allowed
 * protocols can be controlled with the #GstRTSPClientSink:protocols property.
 *
 * rtspclientsink will internally instantiate an RTP session manager element
 * that will handle the RTCP messages to and from the server, jitter removal,
 * and packet reordering.
 * This feature is implemented using the gstrtpbin element.
 *
 * rtspclientsink accepts any stream for which there is an installed payloader,
 * creates the payloader and manages payload-types, as well as RTX setup.
 * The new-payloader signal is fired when a payloader is created, in case
 * an app wants to do custom configuration (such as for MTU).
 *
 * ## Example launch line
 *
 * |[
 * gst-launch-1.0 videotestsrc ! jpegenc ! rtspclientsink location=rtsp://some.server/url
 * ]| Establish a connection to an RTSP server and send JPEG encoded video packets
 */

/* FIXMEs
 * - Handle EOS properly and shutdown. The problem with EOS is we don't know
 *   when the server has received all data, so we don't know when to do teardown.
 *   At the moment, we forward EOS to the app as soon as we stop sending. Is there
 *   a way to know from the receiver that it's got all data? Some session timeout?
 * - Implement extension support for Real / WMS if they support RECORD?
 * - Add support for network clock synchronised streaming?
 * - Fix crypto key nego so SAVP/SAVPF profiles work.
 * - Test (&fix?) HTTP tunnel support
 * - Add an address pool object for GstRTSPStreams to use for multicast
 * - Test multicast UDP transport
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#include <gst/net/gstnet.h>
#include <gst/sdp/gstsdpmessage.h>
#include <gst/sdp/gstmikey.h>
#include <gst/rtp/rtp.h>

#include "gstrtspclientsink.h"

#include "../glib-compat-private.h"

typedef struct _GstRtspClientSinkPad GstRtspClientSinkPad;
typedef GstGhostPadClass GstRtspClientSinkPadClass;

struct _GstRtspClientSinkPad
{
  GstGhostPad parent;
  GstElement *custom_payloader;
  guint ulpfec_percentage;
};

enum
{
  PROP_PAD_0,
  PROP_PAD_PAYLOADER,
  PROP_PAD_ULPFEC_PERCENTAGE
};

#define DEFAULT_PAD_ULPFEC_PERCENTAGE 0

static GType gst_rtsp_client_sink_pad_get_type (void);
G_DEFINE_TYPE (GstRtspClientSinkPad, gst_rtsp_client_sink_pad,
    GST_TYPE_GHOST_PAD);
#define GST_TYPE_RTSP_CLIENT_SINK_PAD (gst_rtsp_client_sink_pad_get_type ())
#define GST_RTSP_CLIENT_SINK_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTSP_CLIENT_SINK_PAD,GstRtspClientSinkPad))

static void
gst_rtsp_client_sink_pad_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstRtspClientSinkPad *pad;

  pad = GST_RTSP_CLIENT_SINK_PAD (object);

  switch (prop_id) {
    case PROP_PAD_PAYLOADER:
      GST_OBJECT_LOCK (pad);
      if (pad->custom_payloader)
        gst_object_unref (pad->custom_payloader);
      pad->custom_payloader = g_value_get_object (value);
      gst_object_ref_sink (pad->custom_payloader);
      GST_OBJECT_UNLOCK (pad);
      break;
    case PROP_PAD_ULPFEC_PERCENTAGE:
      GST_OBJECT_LOCK (pad);
      pad->ulpfec_percentage = g_value_get_uint (value);
      GST_OBJECT_UNLOCK (pad);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_rtsp_client_sink_pad_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstRtspClientSinkPad *pad;

  pad = GST_RTSP_CLIENT_SINK_PAD (object);

  switch (prop_id) {
    case PROP_PAD_PAYLOADER:
      GST_OBJECT_LOCK (pad);
      g_value_set_object (value, pad->custom_payloader);
      GST_OBJECT_UNLOCK (pad);
      break;
    case PROP_PAD_ULPFEC_PERCENTAGE:
      GST_OBJECT_LOCK (pad);
      g_value_set_uint (value, pad->ulpfec_percentage);
      GST_OBJECT_UNLOCK (pad);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_rtsp_client_sink_pad_dispose (GObject * object)
{
  GstRtspClientSinkPad *pad = GST_RTSP_CLIENT_SINK_PAD (object);

  if (pad->custom_payloader)
    gst_object_unref (pad->custom_payloader);

  G_OBJECT_CLASS (gst_rtsp_client_sink_pad_parent_class)->dispose (object);
}

static void
gst_rtsp_client_sink_pad_class_init (GstRtspClientSinkPadClass * klass)
{
  GObjectClass *gobject_klass;

  gobject_klass = (GObjectClass *) klass;

  gobject_klass->set_property = gst_rtsp_client_sink_pad_set_property;
  gobject_klass->get_property = gst_rtsp_client_sink_pad_get_property;
  gobject_klass->dispose = gst_rtsp_client_sink_pad_dispose;

  g_object_class_install_property (gobject_klass, PROP_PAD_PAYLOADER,
      g_param_spec_object ("payloader", "Payloader",
          "The payloader element to use (NULL = default automatically selected)",
          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_klass, PROP_PAD_ULPFEC_PERCENTAGE,
      g_param_spec_uint ("ulpfec-percentage", "ULPFEC percentage",
          "The percentage of ULP redundancy to apply", 0, 100,
          DEFAULT_PAD_ULPFEC_PERCENTAGE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_rtsp_client_sink_pad_init (GstRtspClientSinkPad * pad)
{
}

static GstPad *
gst_rtsp_client_sink_pad_new (const GstPadTemplate * pad_tmpl,
    const gchar * name)
{
  GstRtspClientSinkPad *ret;

  ret =
      g_object_new (GST_TYPE_RTSP_CLIENT_SINK_PAD, "direction", GST_PAD_SINK,
      "template", pad_tmpl, "name", name, NULL);

  return GST_PAD (ret);
}

GST_DEBUG_CATEGORY_STATIC (rtsp_client_sink_debug);
#define GST_CAT_DEFAULT (rtsp_client_sink_debug)

static GstStaticPadTemplate rtptemplate = GST_STATIC_PAD_TEMPLATE ("sink_%u",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);       /* Actual caps come from available set of payloaders */

enum
{
  SIGNAL_HANDLE_REQUEST,
  SIGNAL_NEW_MANAGER,
  SIGNAL_NEW_PAYLOADER,
  SIGNAL_REQUEST_RTCP_KEY,
  SIGNAL_ACCEPT_CERTIFICATE,
  SIGNAL_UPDATE_SDP,
  LAST_SIGNAL
};

enum _GstRTSPClientSinkNtpTimeSource
{
  NTP_TIME_SOURCE_NTP,
  NTP_TIME_SOURCE_UNIX,
  NTP_TIME_SOURCE_RUNNING_TIME,
  NTP_TIME_SOURCE_CLOCK_TIME
};

#define GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE (gst_rtsp_client_sink_ntp_time_source_get_type())
static GType
gst_rtsp_client_sink_ntp_time_source_get_type (void)
{
  static GType ntp_time_source_type = 0;
  static const GEnumValue ntp_time_source_values[] = {
    {NTP_TIME_SOURCE_NTP, "NTP time based on realtime clock", "ntp"},
    {NTP_TIME_SOURCE_UNIX, "UNIX time based on realtime clock", "unix"},
    {NTP_TIME_SOURCE_RUNNING_TIME,
          "Running time based on pipeline clock",
        "running-time"},
    {NTP_TIME_SOURCE_CLOCK_TIME, "Pipeline clock time", "clock-time"},
    {0, NULL, NULL},
  };

  if (!ntp_time_source_type) {
    ntp_time_source_type =
        g_enum_register_static ("GstRTSPClientSinkNtpTimeSource",
        ntp_time_source_values);
  }
  return ntp_time_source_type;
}

#define DEFAULT_LOCATION         NULL
#define DEFAULT_PROTOCOLS        GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP
#define DEFAULT_DEBUG            FALSE
#define DEFAULT_RETRY            20
#define DEFAULT_TIMEOUT          5000000
#define DEFAULT_UDP_BUFFER_SIZE  0x80000
#define DEFAULT_TCP_TIMEOUT      20000000
#define DEFAULT_LATENCY_MS       2000
#define DEFAULT_DO_RTSP_KEEP_ALIVE       TRUE
#define DEFAULT_PROXY            NULL
#define DEFAULT_RTP_BLOCKSIZE    0
#define DEFAULT_USER_ID          NULL
#define DEFAULT_USER_PW          NULL
#define DEFAULT_PORT_RANGE       NULL
#define DEFAULT_UDP_RECONNECT    TRUE
#define DEFAULT_MULTICAST_IFACE  NULL
#define DEFAULT_TLS_VALIDATION_FLAGS     G_TLS_CERTIFICATE_VALIDATE_ALL
#define DEFAULT_TLS_DATABASE     NULL
#define DEFAULT_TLS_INTERACTION     NULL
#define DEFAULT_NTP_TIME_SOURCE  NTP_TIME_SOURCE_NTP
#define DEFAULT_USER_AGENT       "GStreamer/{VERSION}"
#define DEFAULT_PROFILES         GST_RTSP_PROFILE_AVP
#define DEFAULT_RTX_TIME_MS      500
#define DEFAULT_PUBLISH_CLOCK_MODE GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK

enum
{
  PROP_0,
  PROP_LOCATION,
  PROP_PROTOCOLS,
  PROP_DEBUG,
  PROP_RETRY,
  PROP_TIMEOUT,
  PROP_TCP_TIMEOUT,
  PROP_LATENCY,
  PROP_RTX_TIME,
  PROP_DO_RTSP_KEEP_ALIVE,
  PROP_PROXY,
  PROP_PROXY_ID,
  PROP_PROXY_PW,
  PROP_RTP_BLOCKSIZE,
  PROP_USER_ID,
  PROP_USER_PW,
  PROP_PORT_RANGE,
  PROP_UDP_BUFFER_SIZE,
  PROP_UDP_RECONNECT,
  PROP_MULTICAST_IFACE,
  PROP_SDES,
  PROP_TLS_VALIDATION_FLAGS,
  PROP_TLS_DATABASE,
  PROP_TLS_INTERACTION,
  PROP_NTP_TIME_SOURCE,
  PROP_USER_AGENT,
  PROP_PROFILES,
  PROP_PUBLISH_CLOCK_MODE,
};

static void gst_rtsp_client_sink_finalize (GObject * object);

static void gst_rtsp_client_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_rtsp_client_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

static GstClock *gst_rtsp_client_sink_provide_clock (GstElement * element);

static void gst_rtsp_client_sink_uri_handler_init (gpointer g_iface,
    gpointer iface_data);

static gboolean gst_rtsp_client_sink_set_proxy (GstRTSPClientSink * rtsp,
    const gchar * proxy);
static void gst_rtsp_client_sink_set_tcp_timeout (GstRTSPClientSink *
    rtsp_client_sink, guint64 timeout);

static GstStateChangeReturn gst_rtsp_client_sink_change_state (GstElement *
    element, GstStateChange transition);
static void gst_rtsp_client_sink_handle_message (GstBin * bin,
    GstMessage * message);

static gboolean gst_rtsp_client_sink_setup_auth (GstRTSPClientSink * sink,
    GstRTSPMessage * response);

static gboolean gst_rtsp_client_sink_loop_send_cmd (GstRTSPClientSink * sink,
    gint cmd, gint mask);

static GstRTSPResult gst_rtsp_client_sink_open (GstRTSPClientSink * sink,
    gboolean async);
static GstRTSPResult gst_rtsp_client_sink_record (GstRTSPClientSink * sink,
    gboolean async);
static GstRTSPResult gst_rtsp_client_sink_pause (GstRTSPClientSink * sink,
    gboolean async);
static GstRTSPResult gst_rtsp_client_sink_close (GstRTSPClientSink * sink,
    gboolean async, gboolean only_close);
static gboolean gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink);

static gboolean gst_rtsp_client_sink_uri_set_uri (GstURIHandler * handler,
    const gchar * uri, GError ** error);
static gchar *gst_rtsp_client_sink_uri_get_uri (GstURIHandler * handler);

static gboolean gst_rtsp_client_sink_loop (GstRTSPClientSink * sink);
static void gst_rtsp_client_sink_connection_flush (GstRTSPClientSink * sink,
    gboolean flush);

static GstPad *gst_rtsp_client_sink_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
static void gst_rtsp_client_sink_release_pad (GstElement * element,
    GstPad * pad);

/* commands we send to out loop to notify it of events */
#define CMD_OPEN	(1 << 0)
#define CMD_RECORD	(1 << 1)
#define CMD_PAUSE	(1 << 2)
#define CMD_CLOSE	(1 << 3)
#define CMD_WAIT	(1 << 4)
#define CMD_RECONNECT	(1 << 5)
#define CMD_LOOP	(1 << 6)

/* mask for all commands */
#define CMD_ALL         ((CMD_LOOP << 1) - 1)

#define GST_ELEMENT_PROGRESS(el, type, code, text)      \
G_STMT_START {                                          \
  gchar *__txt = _gst_element_error_printf text;        \
  gst_element_post_message (GST_ELEMENT_CAST (el),      \
      gst_message_new_progress (GST_OBJECT_CAST (el),   \
          GST_PROGRESS_TYPE_ ##type, code, __txt));     \
  g_free (__txt);                                       \
} G_STMT_END

static guint gst_rtsp_client_sink_signals[LAST_SIGNAL] = { 0 };

/*********************************
 * GstChildProxy implementation  *
 *********************************/
static GObject *
gst_rtsp_client_sink_child_proxy_get_child_by_index (GstChildProxy *
    child_proxy, guint index)
{
  GObject *obj;
  GstRTSPClientSink *cs = GST_RTSP_CLIENT_SINK (child_proxy);

  GST_OBJECT_LOCK (cs);
  if ((obj = g_list_nth_data (GST_ELEMENT (cs)->sinkpads, index)))
    g_object_ref (obj);
  GST_OBJECT_UNLOCK (cs);

  return obj;
}

static guint
gst_rtsp_client_sink_child_proxy_get_children_count (GstChildProxy *
    child_proxy)
{
  guint count = 0;

  GST_OBJECT_LOCK (child_proxy);
  count = GST_ELEMENT (child_proxy)->numsinkpads;
  GST_OBJECT_UNLOCK (child_proxy);

  GST_INFO_OBJECT (child_proxy, "Children Count: %d", count);

  return count;
}

static void
gst_rtsp_client_sink_child_proxy_init (gpointer g_iface, gpointer iface_data)
{
  GstChildProxyInterface *iface = g_iface;

  GST_INFO ("intializing child proxy interface");
  iface->get_child_by_index =
      gst_rtsp_client_sink_child_proxy_get_child_by_index;
  iface->get_children_count =
      gst_rtsp_client_sink_child_proxy_get_children_count;
}

#define gst_rtsp_client_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstRTSPClientSink, gst_rtsp_client_sink, GST_TYPE_BIN,
    G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
        gst_rtsp_client_sink_uri_handler_init);
    G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
        gst_rtsp_client_sink_child_proxy_init);
    );

#ifndef GST_DISABLE_GST_DEBUG
static inline const gchar *
cmd_to_string (guint cmd)
{
  switch (cmd) {
    case CMD_OPEN:
      return "OPEN";
    case CMD_RECORD:
      return "RECORD";
    case CMD_PAUSE:
      return "PAUSE";
    case CMD_CLOSE:
      return "CLOSE";
    case CMD_WAIT:
      return "WAIT";
    case CMD_RECONNECT:
      return "RECONNECT";
    case CMD_LOOP:
      return "LOOP";
  }

  return "unknown";
}
#endif

static void
gst_rtsp_client_sink_class_init (GstRTSPClientSinkClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBinClass *gstbin_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
  gstbin_class = (GstBinClass *) klass;

  GST_DEBUG_CATEGORY_INIT (rtsp_client_sink_debug, "rtspclientsink", 0,
      "RTSP sink element");

  gobject_class->set_property = gst_rtsp_client_sink_set_property;
  gobject_class->get_property = gst_rtsp_client_sink_get_property;

  gobject_class->finalize = gst_rtsp_client_sink_finalize;

  g_object_class_install_property (gobject_class, PROP_LOCATION,
      g_param_spec_string ("location", "RTSP Location",
          "Location of the RTSP url to read",
          DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
      g_param_spec_flags ("protocols", "Protocols",
          "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
          DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROFILES,
      g_param_spec_flags ("profiles", "Profiles",
          "Allowed RTSP profiles", GST_TYPE_RTSP_PROFILE,
          DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_DEBUG,
      g_param_spec_boolean ("debug", "Debug",
          "Dump request and response messages to stdout",
          DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_RETRY,
      g_param_spec_uint ("retry", "Retry",
          "Max number of retries when allocating RTP ports.",
          0, G_MAXUINT16, DEFAULT_RETRY,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
      g_param_spec_uint64 ("timeout", "Timeout",
          "Retry TCP transport after UDP timeout microseconds (0 = disabled)",
          0, G_MAXUINT64, DEFAULT_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT,
      g_param_spec_uint64 ("tcp-timeout", "TCP Timeout",
          "Fail after timeout microseconds on TCP connections (0 = disabled)",
          0, G_MAXUINT64, DEFAULT_TCP_TIMEOUT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_LATENCY,
      g_param_spec_uint ("latency", "Buffer latency in ms",
          "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_RTX_TIME,
      g_param_spec_uint ("rtx-time", "Retransmission buffer in ms",
          "Amount of ms to buffer for retransmission. 0 disables retransmission",
          0, G_MAXUINT, DEFAULT_RTX_TIME_MS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:do-rtsp-keep-alive:
   *
   * Enable RTSP keep alive support. Some old server don't like RTSP
   * keep alive and then this property needs to be set to FALSE.
   */
  g_object_class_install_property (gobject_class, PROP_DO_RTSP_KEEP_ALIVE,
      g_param_spec_boolean ("do-rtsp-keep-alive", "Do RTSP Keep Alive",
          "Send RTSP keep alive packets, disable for old incompatible server.",
          DEFAULT_DO_RTSP_KEEP_ALIVE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:proxy:
   *
   * Set the proxy parameters. This has to be a string of the format
   * [http://][user:passwd@]host[:port].
   */
  g_object_class_install_property (gobject_class, PROP_PROXY,
      g_param_spec_string ("proxy", "Proxy",
          "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]",
          DEFAULT_PROXY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPClientSink:proxy-id:
   *
   * Sets the proxy URI user id for authentication. If the URI set via the
   * "proxy" property contains a user-id already, that will take precedence.
   *
   */
  g_object_class_install_property (gobject_class, PROP_PROXY_ID,
      g_param_spec_string ("proxy-id", "proxy-id",
          "HTTP proxy URI user id for authentication", "",
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstRTSPClientSink:proxy-pw:
   *
   * Sets the proxy URI password for authentication. If the URI set via the
   * "proxy" property contains a password already, that will take precedence.
   *
   */
  g_object_class_install_property (gobject_class, PROP_PROXY_PW,
      g_param_spec_string ("proxy-pw", "proxy-pw",
          "HTTP proxy URI user password for authentication", "",
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:rtp-blocksize:
   *
   * RTP package size to suggest to server.
   */
  g_object_class_install_property (gobject_class, PROP_RTP_BLOCKSIZE,
      g_param_spec_uint ("rtp-blocksize", "RTP Blocksize",
          "RTP package size to suggest to server (0 = disabled)",
          0, 65536, DEFAULT_RTP_BLOCKSIZE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_USER_ID,
      g_param_spec_string ("user-id", "user-id",
          "RTSP location URI user id for authentication", DEFAULT_USER_ID,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (gobject_class, PROP_USER_PW,
      g_param_spec_string ("user-pw", "user-pw",
          "RTSP location URI user password for authentication", DEFAULT_USER_PW,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:port-range:
   *
   * Configure the client port numbers that can be used to send RTP and receive
   * RTCP.
   */
  g_object_class_install_property (gobject_class, PROP_PORT_RANGE,
      g_param_spec_string ("port-range", "Port range",
          "Client port range that can be used to send RTP data and receive RTCP "
          "data, eg. 3000-3005 (NULL = no restrictions)", DEFAULT_PORT_RANGE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:udp-buffer-size:
   *
   * Size of the kernel UDP receive buffer in bytes.
   */
  g_object_class_install_property (gobject_class, PROP_UDP_BUFFER_SIZE,
      g_param_spec_int ("udp-buffer-size", "UDP Buffer Size",
          "Size of the kernel UDP receive buffer in bytes, 0=default",
          0, G_MAXINT, DEFAULT_UDP_BUFFER_SIZE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_UDP_RECONNECT,
      g_param_spec_boolean ("udp-reconnect", "Reconnect to the server",
          "Reconnect to the server if RTSP connection is closed when doing UDP",
          DEFAULT_UDP_RECONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE,
      g_param_spec_string ("multicast-iface", "Multicast Interface",
          "The network interface on which to join the multicast group",
          DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_SDES,
      g_param_spec_boxed ("sdes", "SDES",
          "The SDES items of this session",
          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:tls-validation-flags:
   *
   * TLS certificate validation flags used to validate server
   * certificate.
   *
   * GLib guarantees that if certificate verification fails, at least one
   * error will be set, but it does not guarantee that all possible errors
   * will be set. Accordingly, you may not safely decide to ignore any
   * particular type of error.
   *
   * For example, it would be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if
   * you want to allow expired certificates, because this could potentially be
   * the only error flag set even if other problems exist with the
   * certificate.
   *
   */
  g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS,
      g_param_spec_flags ("tls-validation-flags", "TLS validation flags",
          "TLS certificate validation flags used to validate the server certificate",
          G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:tls-database:
   *
   * TLS database with anchor certificate authorities used to validate
   * the server certificate.
   *
   */
  g_object_class_install_property (gobject_class, PROP_TLS_DATABASE,
      g_param_spec_object ("tls-database", "TLS database",
          "TLS database with anchor certificate authorities used to validate the server certificate",
          G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:tls-interaction:
   *
   * A #GTlsInteraction object to be used when the connection or certificate
   * database need to interact with the user. This will be used to prompt the
   * user for passwords where necessary.
   *
   */
  g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION,
      g_param_spec_object ("tls-interaction", "TLS interaction",
          "A GTlsInteraction object to prompt the user for password or certificate",
          G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:ntp-time-source:
   *
   * allows to select the time source that should be used
   * for the NTP time in outgoing packets
   *
   */
  g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE,
      g_param_spec_enum ("ntp-time-source", "NTP Time Source",
          "NTP time source for RTCP packets",
          GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:user-agent:
   *
   * The string to set in the User-Agent header.
   *
   */
  g_object_class_install_property (gobject_class, PROP_USER_AGENT,
      g_param_spec_string ("user-agent", "User Agent",
          "The User-Agent string to send to the server",
          DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink:publish-clock-mode:
   *
   * Sets if and how the media clock should be published according to RFC7273.
   *
   * Since: 1.22
   *
   */
  g_object_class_install_property (gobject_class, PROP_PUBLISH_CLOCK_MODE,
      g_param_spec_enum ("publish-clock-mode", "Publish Clock Mode",
          "Clock publishing mode according to RFC7273",
          GST_TYPE_RTSP_PUBLISH_CLOCK_MODE, DEFAULT_PUBLISH_CLOCK_MODE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstRTSPClientSink::handle-request:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @request: a #GstRTSPMessage
   * @response: a #GstRTSPMessage
   *
   * Handle a server request in @request and prepare @response.
   *
   * This signal is called from the streaming thread, you should therefore not
   * do any state changes on @rtsp_client_sink because this might deadlock. If you want
   * to modify the state as a result of this signal, post a
   * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread
   * in some other way.
   *
   */
  gst_rtsp_client_sink_signals[SIGNAL_HANDLE_REQUEST] =
      g_signal_new ("handle-request", G_TYPE_FROM_CLASS (klass), 0,
      0, NULL, NULL, NULL, G_TYPE_NONE, 2,
      GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE,
      GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);

  /**
   * GstRTSPClientSink::new-manager:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @manager: a #GstElement
   *
   * Emitted after a new manager (like rtpbin) was created and the default
   * properties were configured.
   *
   */
  gst_rtsp_client_sink_signals[SIGNAL_NEW_MANAGER] =
      g_signal_new_class_handler ("new-manager", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_ELEMENT);

  /**
   * GstRTSPClientSink::new-payloader:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @payloader: a #GstElement
   *
   * Emitted after a new RTP payloader was created and the default
   * properties were configured.
   *
   */
  gst_rtsp_client_sink_signals[SIGNAL_NEW_PAYLOADER] =
      g_signal_new_class_handler ("new-payloader", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_ELEMENT);

  /**
   * GstRTSPClientSink::request-rtcp-key:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @num: the stream number
   *
   * Signal emitted to get the crypto parameters relevant to the RTCP
   * stream. User should provide the key and the RTCP encryption ciphers
   * and authentication, and return them wrapped in a GstCaps.
   *
   */
  gst_rtsp_client_sink_signals[SIGNAL_REQUEST_RTCP_KEY] =
      g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);

  /**
   * GstRTSPClientSink::accept-certificate:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @peer_cert: the peer's #GTlsCertificate
   * @errors: the problems with @peer_cert
   * @user_data: user data set when the signal handler was connected.
   *
   * This will directly map to #GTlsConnection 's "accept-certificate"
   * signal and be performed after the default checks of #GstRTSPConnection
   * (checking against the #GTlsDatabase with the given #GTlsCertificateFlags)
   * have failed. If no #GTlsDatabase is set on this connection, only this
   * signal will be emitted.
   *
   * Since: 1.14
   */
  gst_rtsp_client_sink_signals[SIGNAL_ACCEPT_CERTIFICATE] =
      g_signal_new ("accept-certificate", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL,
      G_TYPE_BOOLEAN, 3, G_TYPE_TLS_CONNECTION, G_TYPE_TLS_CERTIFICATE,
      G_TYPE_TLS_CERTIFICATE_FLAGS);

  /**
   * GstRTSPClientSink::update-sdp:
   * @rtsp_client_sink: a #GstRTSPClientSink
   * @sdp: a #GstSDPMessage
   *
   * Emitted right before the ANNOUNCE request is sent to the server with the
   * generated SDP. The SDP can be updated from signal handlers but the order
   * and number of medias must not be changed.
   *
   * Since: 1.20
   */
  gst_rtsp_client_sink_signals[SIGNAL_UPDATE_SDP] =
      g_signal_new_class_handler ("update-sdp", G_TYPE_FROM_CLASS (klass),
      0, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 1, GST_TYPE_SDP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);

  gstelement_class->provide_clock = gst_rtsp_client_sink_provide_clock;
  gstelement_class->change_state = gst_rtsp_client_sink_change_state;
  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_request_new_pad);
  gstelement_class->release_pad =
      GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_release_pad);

  gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
      &rtptemplate, GST_TYPE_RTSP_CLIENT_SINK_PAD);

  gst_element_class_set_static_metadata (gstelement_class,
      "RTSP RECORD client", "Sink/Network",
      "Send data over the network via RTSP RECORD(RFC 2326)",
      "Jan Schmidt <jan@centricular.com>");

  gstbin_class->handle_message = gst_rtsp_client_sink_handle_message;

  gst_type_mark_as_plugin_api (GST_TYPE_RTSP_CLIENT_SINK_PAD, 0);
  gst_type_mark_as_plugin_api (GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE, 0);
}

static void
gst_rtsp_client_sink_init (GstRTSPClientSink * sink)
{
  sink->conninfo.location = g_strdup (DEFAULT_LOCATION);
  sink->protocols = DEFAULT_PROTOCOLS;
  sink->debug = DEFAULT_DEBUG;
  sink->retry = DEFAULT_RETRY;
  sink->udp_timeout = DEFAULT_TIMEOUT;
  gst_rtsp_client_sink_set_tcp_timeout (sink, DEFAULT_TCP_TIMEOUT);
  sink->latency = DEFAULT_LATENCY_MS;
  sink->rtx_time = DEFAULT_RTX_TIME_MS;
  sink->do_rtsp_keep_alive = DEFAULT_DO_RTSP_KEEP_ALIVE;
  gst_rtsp_client_sink_set_proxy (sink, DEFAULT_PROXY);
  sink->rtp_blocksize = DEFAULT_RTP_BLOCKSIZE;
  sink->user_id = g_strdup (DEFAULT_USER_ID);
  sink->user_pw = g_strdup (DEFAULT_USER_PW);
  sink->client_port_range.min = 0;
  sink->client_port_range.max = 0;
  sink->udp_buffer_size = DEFAULT_UDP_BUFFER_SIZE;
  sink->udp_reconnect = DEFAULT_UDP_RECONNECT;
  sink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE);
  sink->sdes = NULL;
  sink->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS;
  sink->tls_database = DEFAULT_TLS_DATABASE;
  sink->tls_interaction = DEFAULT_TLS_INTERACTION;
  sink->ntp_time_source = DEFAULT_NTP_TIME_SOURCE;
  sink->user_agent = g_strdup (DEFAULT_USER_AGENT);
  sink->publish_clock_mode = DEFAULT_PUBLISH_CLOCK_MODE;

  sink->pool = NULL;

  sink->profiles = DEFAULT_PROFILES;

  /* protects the streaming thread in interleaved mode or the polling
   * thread in UDP mode. */
  g_rec_mutex_init (&sink->stream_rec_lock);

  /* protects our state changes from multiple invocations */
  g_rec_mutex_init (&sink->state_rec_lock);

  g_mutex_init (&sink->send_lock);

  g_mutex_init (&sink->preroll_lock);
  g_cond_init (&sink->preroll_cond);

  sink->state = GST_RTSP_STATE_INVALID;

  g_mutex_init (&sink->conninfo.send_lock);
  g_mutex_init (&sink->conninfo.recv_lock);

  g_mutex_init (&sink->block_streams_lock);
  g_cond_init (&sink->block_streams_cond);

  g_mutex_init (&sink->open_conn_lock);
  g_cond_init (&sink->open_conn_cond);

  sink->internal_bin = (GstBin *) gst_bin_new ("rtspbin");
  g_object_set (sink->internal_bin, "async-handling", TRUE, NULL);
  gst_element_set_locked_state (GST_ELEMENT_CAST (sink->internal_bin), TRUE);
  gst_bin_add (GST_BIN (sink), GST_ELEMENT_CAST (sink->internal_bin));

  sink->next_dyn_pt = 96;

  gst_sdp_message_init (&sink->cursdp);

  GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
}

static void
gst_rtsp_client_sink_finalize (GObject * object)
{
  GstRTSPClientSink *rtsp_client_sink;

  rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);

  gst_sdp_message_uninit (&rtsp_client_sink->cursdp);

  g_free (rtsp_client_sink->conninfo.location);
  gst_rtsp_url_free (rtsp_client_sink->conninfo.url);
  g_free (rtsp_client_sink->conninfo.url_str);
  g_free (rtsp_client_sink->user_id);
  g_free (rtsp_client_sink->user_pw);
  g_free (rtsp_client_sink->multi_iface);
  g_free (rtsp_client_sink->user_agent);
  g_free (rtsp_client_sink->prop_proxy_id);
  g_free (rtsp_client_sink->prop_proxy_pw);
  g_free (rtsp_client_sink->proxy_user);
  g_free (rtsp_client_sink->proxy_passwd);

  if (rtsp_client_sink->pool) {
    gst_object_unref (rtsp_client_sink->pool);
    rtsp_client_sink->pool = NULL;
  }
  if (rtsp_client_sink->uri_sdp) {
    gst_sdp_message_free (rtsp_client_sink->uri_sdp);
    rtsp_client_sink->uri_sdp = NULL;
  }
  if (rtsp_client_sink->provided_clock)
    gst_object_unref (rtsp_client_sink->provided_clock);

  if (rtsp_client_sink->sdes)
    gst_structure_free (rtsp_client_sink->sdes);

  if (rtsp_client_sink->tls_database)
    g_object_unref (rtsp_client_sink->tls_database);

  if (rtsp_client_sink->tls_interaction)
    g_object_unref (rtsp_client_sink->tls_interaction);

  /* free locks */
  g_rec_mutex_clear (&rtsp_client_sink->stream_rec_lock);
  g_rec_mutex_clear (&rtsp_client_sink->state_rec_lock);

  g_mutex_clear (&rtsp_client_sink->conninfo.send_lock);
  g_mutex_clear (&rtsp_client_sink->conninfo.recv_lock);

  g_mutex_clear (&rtsp_client_sink->send_lock);

  g_mutex_clear (&rtsp_client_sink->preroll_lock);
  g_cond_clear (&rtsp_client_sink->preroll_cond);

  g_mutex_clear (&rtsp_client_sink->block_streams_lock);
  g_cond_clear (&rtsp_client_sink->block_streams_cond);

  g_mutex_clear (&rtsp_client_sink->open_conn_lock);
  g_cond_clear (&rtsp_client_sink->open_conn_cond);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
gst_rtp_payloader_filter_func (GstPluginFeature * feature, gpointer user_data)
{
  GstElementFactory *factory = NULL;
  const gchar *klass;

  if (!GST_IS_ELEMENT_FACTORY (feature))
    return FALSE;

  factory = GST_ELEMENT_FACTORY (feature);

  if (gst_plugin_feature_get_rank (feature) == GST_RANK_NONE)
    return FALSE;

  if (!gst_element_factory_list_is_type (factory,
          GST_ELEMENT_FACTORY_TYPE_PAYLOADER))
    return FALSE;

  klass =
      gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
  if (strstr (klass, "Codec") == NULL)
    return FALSE;
  if (strstr (klass, "RTP") == NULL)
    return FALSE;

  return TRUE;
}

static gint
compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
{
  gint diff;
  const gchar *rname1, *rname2;
  GstRank rank1, rank2;

  rname1 = gst_plugin_feature_get_name (f1);
  rname2 = gst_plugin_feature_get_name (f2);

  rank1 = gst_plugin_feature_get_rank (f1);
  rank2 = gst_plugin_feature_get_rank (f2);

  /* HACK: Prefer rtpmp4apay over rtpmp4gpay */
  if (g_str_equal (rname1, "rtpmp4apay"))
    rank1 = GST_RANK_SECONDARY + 1;
  if (g_str_equal (rname2, "rtpmp4apay"))
    rank2 = GST_RANK_SECONDARY + 1;

  diff = rank2 - rank1;
  if (diff != 0)
    return diff;

  diff = strcmp (rname2, rname1);

  return diff;
}

static GList *
gst_rtsp_client_sink_get_factories (void)
{
  static GList *payloader_factories = NULL;

  if (g_once_init_enter (&payloader_factories)) {
    GList *all_factories;

    all_factories =
        gst_registry_feature_filter (gst_registry_get (),
        gst_rtp_payloader_filter_func, FALSE, NULL);

    all_factories = g_list_sort (all_factories, (GCompareFunc) compare_ranks);

    g_once_init_leave (&payloader_factories, all_factories);
  }

  return payloader_factories;
}

static GstCaps *
gst_rtsp_client_sink_get_payloader_caps (GstElementFactory * factory)
{
  const GList *tmp;
  GstCaps *caps = gst_caps_new_empty ();

  for (tmp = gst_element_factory_get_static_pad_templates (factory);
      tmp; tmp = g_list_next (tmp)) {
    GstStaticPadTemplate *template = tmp->data;

    if (template->direction == GST_PAD_SINK) {
      GstCaps *static_caps = gst_static_pad_template_get_caps (template);

      GST_LOG ("Found pad template %s on factory %s",
          template->name_template, gst_plugin_feature_get_name (factory));

      if (static_caps)
        caps = gst_caps_merge (caps, static_caps);

      /* Early out, any is absorbing */
      if (gst_caps_is_any (caps))
        goto out;
    }
  }

out:
  return caps;
}

static GstCaps *
gst_rtsp_client_sink_get_all_payloaders_caps (void)
{
  /* Cached caps result */
  static GstCaps *ret;

  if (g_once_init_enter (&ret)) {
    GList *factories, *cur;
    GstCaps *caps = gst_caps_new_empty ();

    factories = gst_rtsp_client_sink_get_factories ();
    for (cur = factories; cur != NULL; cur = g_list_next (cur)) {
      GstElementFactory *factory = GST_ELEMENT_FACTORY (cur->data);
      GstCaps *payloader_caps =
          gst_rtsp_client_sink_get_payloader_caps (factory);

      caps = gst_caps_merge (caps, payloader_caps);

      /* Early out, any is absorbing */
      if (gst_caps_is_any (caps))
        goto out;
    }

    GST_MINI_OBJECT_FLAG_SET (caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);

  out:
    g_once_init_leave (&ret, caps);
  }

  /* Return cached result */
  return gst_caps_ref (ret);
}

static GstElement *
gst_rtsp_client_sink_make_payloader (GstCaps * caps)
{
  GList *factories, *cur;

  factories = gst_rtsp_client_sink_get_factories ();
  for (cur = factories; cur != NULL; cur = g_list_next (cur)) {
    GstElementFactory *factory = GST_ELEMENT_FACTORY (cur->data);
    const GList *tmp;

    for (tmp = gst_element_factory_get_static_pad_templates (factory);
        tmp; tmp = g_list_next (tmp)) {
      GstStaticPadTemplate *template = tmp->data;

      if (template->direction == GST_PAD_SINK) {
        GstCaps *static_caps = gst_static_pad_template_get_caps (template);
        GstElement *payloader = NULL;

        if (gst_caps_can_intersect (static_caps, caps)) {
          GST_DEBUG ("caps %" GST_PTR_FORMAT " intersects with template %"
              GST_PTR_FORMAT " for payloader %s", caps, static_caps,
              gst_plugin_feature_get_name (factory));
          payloader = gst_element_factory_create (factory, NULL);
        }

        gst_caps_unref (static_caps);

        if (payloader)
          return payloader;
      }
    }
  }

  return NULL;
}

static GstRTSPStream *
gst_rtsp_client_sink_create_stream (GstRTSPClientSink * sink,
    GstRTSPStreamContext * context, GstElement * payloader, GstPad * pad)
{
  GstRTSPStream *stream = NULL;
  guint pt, aux_pt, ulpfec_pt;

  GST_OBJECT_LOCK (sink);

  g_object_get (G_OBJECT (payloader), "pt", &pt, NULL);
  if (pt >= 96 && pt <= sink->next_dyn_pt) {
    /* Payloader has a dynamic PT, but one that's already used */
    /* FIXME: Create a caps->ptmap instead? */
    pt = sink->next_dyn_pt;

    if (pt > 127)
      goto no_free_pt;

    GST_DEBUG_OBJECT (sink, "Assigning pt %u to stream %d", pt, context->index);

    sink->next_dyn_pt++;
  } else {
    GST_DEBUG_OBJECT (sink, "Keeping existing pt %u for stream %d",
        pt, context->index);
  }

  aux_pt = sink->next_dyn_pt;
  if (aux_pt > 127)
    goto no_free_pt;
  sink->next_dyn_pt++;

  ulpfec_pt = sink->next_dyn_pt;
  if (ulpfec_pt > 127)
    goto no_free_pt;
  sink->next_dyn_pt++;

  GST_OBJECT_UNLOCK (sink);


  g_object_set (G_OBJECT (payloader), "pt", pt, NULL);

  stream = gst_rtsp_stream_new (context->index, payloader, pad);

  gst_rtsp_stream_set_client_side (stream, TRUE);
  gst_rtsp_stream_set_retransmission_time (stream,
      (GstClockTime) (sink->rtx_time) * GST_MSECOND);
  gst_rtsp_stream_set_protocols (stream, sink->protocols);
  gst_rtsp_stream_set_profiles (stream, sink->profiles);
  gst_rtsp_stream_set_retransmission_pt (stream, aux_pt);
  gst_rtsp_stream_set_buffer_size (stream, sink->udp_buffer_size);
  if (sink->rtp_blocksize > 0)
    gst_rtsp_stream_set_mtu (stream, sink->rtp_blocksize);
  gst_rtsp_stream_set_multicast_iface (stream, sink->multi_iface);

  gst_rtsp_stream_set_ulpfec_pt (stream, ulpfec_pt);
  gst_rtsp_stream_set_ulpfec_percentage (stream, context->ulpfec_percentage);
  gst_rtsp_stream_set_publish_clock_mode (stream, sink->publish_clock_mode);

  if (sink->pool)
    gst_rtsp_stream_set_address_pool (stream, sink->pool);

  return stream;
no_free_pt:
  GST_OBJECT_UNLOCK (sink);

  GST_ELEMENT_ERROR (sink, RESOURCE, NO_SPACE_LEFT, (NULL),
      ("Ran out of dynamic payload types."));

  return NULL;
}

static GstPadProbeReturn
handle_payloader_block (GstPad * pad, GstPadProbeInfo * info,
    GstRTSPStreamContext * context)
{
  GstRTSPClientSink *sink = context->parent;

  GST_INFO_OBJECT (sink, "Block on pad %" GST_PTR_FORMAT, pad);

  g_mutex_lock (&sink->preroll_lock);
  context->prerolled = TRUE;
  g_cond_broadcast (&sink->preroll_cond);
  g_mutex_unlock (&sink->preroll_lock);

  GST_INFO_OBJECT (sink, "Announced preroll on pad %" GST_PTR_FORMAT, pad);

  return GST_PAD_PROBE_OK;
}

static gboolean
gst_rtsp_client_sink_setup_payloader (GstRTSPClientSink * sink, GstPad * pad,
    GstCaps * caps)
{
  GstRTSPStreamContext *context;
  GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);

  GstElement *payloader;
  GstPad *sinkpad, *srcpad, *ghostsink;

  context = gst_pad_get_element_private (pad);

  if (cspad->custom_payloader) {
    payloader = cspad->custom_payloader;
  } else {
    /* Find the payloader. */
    payloader = gst_rtsp_client_sink_make_payloader (caps);
  }

  if (payloader == NULL)
    return FALSE;

  GST_DEBUG_OBJECT (sink, "Configuring payloader %" GST_PTR_FORMAT
      " for pad %" GST_PTR_FORMAT, payloader, pad);

  sinkpad = gst_element_get_static_pad (payloader, "sink");
  if (sinkpad == NULL)
    goto no_sinkpad;

  srcpad = gst_element_get_static_pad (payloader, "src");
  if (srcpad == NULL)
    goto no_srcpad;

  gst_bin_add (GST_BIN (sink->internal_bin), payloader);
  ghostsink = gst_ghost_pad_new (NULL, sinkpad);
  gst_pad_set_active (ghostsink, TRUE);
  gst_element_add_pad (GST_ELEMENT (sink->internal_bin), ghostsink);

  g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_NEW_PAYLOADER], 0,
      payloader);

  GST_RTSP_STATE_LOCK (sink);
  context->payloader_block_id =
      gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
      (GstPadProbeCallback) handle_payloader_block, context, NULL);
  context->payloader = payloader;

  payloader = gst_object_ref (payloader);

  gst_ghost_pad_set_target (GST_GHOST_PAD (pad), ghostsink);
  gst_object_unref (GST_OBJECT (sinkpad));
  GST_RTSP_STATE_UNLOCK (sink);

  context->ulpfec_percentage = cspad->ulpfec_percentage;

  gst_element_sync_state_with_parent (payloader);

  gst_object_unref (payloader);
  gst_object_unref (GST_OBJECT (srcpad));

  return TRUE;

no_sinkpad:
  GST_ERROR_OBJECT (sink,
      "Could not find sink pad on payloader %" GST_PTR_FORMAT, payloader);
  if (!cspad->custom_payloader)
    gst_object_unref (payloader);
  return FALSE;

no_srcpad:
  GST_ERROR_OBJECT (sink,
      "Could not find src pad on payloader %" GST_PTR_FORMAT, payloader);
  gst_object_unref (GST_OBJECT (sinkpad));
  gst_object_unref (payloader);
  return TRUE;
}

static gboolean
gst_rtsp_client_sink_sinkpad_event (GstPad * pad, GstObject * parent,
    GstEvent * event)
{
  if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
    GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
    if (target == NULL) {
      GstCaps *caps;

      /* No target yet - choose a payloader and configure it */
      gst_event_parse_caps (event, &caps);

      GST_DEBUG_OBJECT (parent,
          "Have set caps event on pad %" GST_PTR_FORMAT
          " caps %" GST_PTR_FORMAT, pad, caps);

      if (!gst_rtsp_client_sink_setup_payloader (GST_RTSP_CLIENT_SINK (parent),
              pad, caps)) {
        GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);
        GST_ELEMENT_ERROR (parent, CORE, NEGOTIATION,
            ("Could not create payloader"),
            ("Custom payloader: %p, caps: %" GST_PTR_FORMAT,
                cspad->custom_payloader, caps));
        gst_event_unref (event);
        return FALSE;
      }
    } else {
      gst_object_unref (target);
    }
  }

  return gst_pad_event_default (pad, parent, event);
}

static gboolean
gst_rtsp_client_sink_sinkpad_query (GstPad * pad, GstObject * parent,
    GstQuery * query)
{
  if (GST_QUERY_TYPE (query) == GST_QUERY_CAPS) {
    GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
    if (target == NULL) {
      GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);
      GstCaps *caps;

      if (cspad->custom_payloader) {
        GstPad *sinkpad =
            gst_element_get_static_pad (cspad->custom_payloader, "sink");

        if (sinkpad) {
          caps = gst_pad_query_caps (sinkpad, NULL);
          gst_object_unref (sinkpad);
        } else {
          GST_ELEMENT_ERROR (parent, CORE, NEGOTIATION, (NULL),
              ("Custom payloaders are expected to expose a sink pad named 'sink'"));
          return FALSE;
        }
      } else {
        /* No target yet - return the union of all payloader caps */
        caps = gst_rtsp_client_sink_get_all_payloaders_caps ();
      }

      GST_TRACE_OBJECT (parent, "Returning payloader caps %" GST_PTR_FORMAT,
          caps);

      gst_query_set_caps_result (query, caps);
      gst_caps_unref (caps);

      return TRUE;
    }
    gst_object_unref (target);
  }

  return gst_pad_query_default (pad, parent, query);
}

static GstPad *
gst_rtsp_client_sink_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
{
  GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
  GstPad *pad;
  GstRTSPStreamContext *context;
  guint idx = (guint) - 1;
  gchar *tmpname;

  g_mutex_lock (&sink->preroll_lock);
  if (sink->streams_collected) {
    GST_WARNING_OBJECT (element, "Can't add streams to a running session");
    g_mutex_unlock (&sink->preroll_lock);
    return NULL;
  }
  g_mutex_unlock (&sink->preroll_lock);

  GST_OBJECT_LOCK (sink);
  if (name) {
    if (!sscanf (name, "sink_%u", &idx)) {
      GST_OBJECT_UNLOCK (sink);
      GST_ERROR_OBJECT (element, "Invalid sink pad name %s", name);
      return NULL;
    }

    if (idx >= sink->next_pad_id)
      sink->next_pad_id = idx + 1;
  }
  if (idx == (guint) - 1) {
    idx = sink->next_pad_id;
    sink->next_pad_id++;
  }
  GST_OBJECT_UNLOCK (sink);

  tmpname = g_strdup_printf ("sink_%u", idx);
  pad = gst_rtsp_client_sink_pad_new (templ, tmpname);
  g_free (tmpname);

  GST_DEBUG_OBJECT (element, "Creating request pad %" GST_PTR_FORMAT, pad);

  gst_pad_set_event_function (pad,
      GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_sinkpad_event));
  gst_pad_set_query_function (pad,
      GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_sinkpad_query));

  context = g_new0 (GstRTSPStreamContext, 1);
  context->parent = sink;
  context->index = idx;

  gst_pad_set_element_private (pad, context);

  /* The rest of the context is configured on a caps set */
  gst_pad_set_active (pad, TRUE);
  gst_element_add_pad (element, pad);
  gst_child_proxy_child_added (GST_CHILD_PROXY (element), G_OBJECT (pad),
      GST_PAD_NAME (pad));

  (void) gst_rtsp_client_sink_get_factories ();

  g_mutex_init (&context->conninfo.send_lock);
  g_mutex_init (&context->conninfo.recv_lock);

  GST_RTSP_STATE_LOCK (sink);
  sink->contexts = g_list_prepend (sink->contexts, context);
  GST_RTSP_STATE_UNLOCK (sink);

  return pad;
}

static void
gst_rtsp_client_sink_release_pad (GstElement * element, GstPad * pad)
{
  GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
  GstRTSPStreamContext *context;

  context = gst_pad_get_element_private (pad);

  /* FIXME: we may need to change our blocking state waiting for
   * GstRTSPStreamBlocking messages */

  GST_RTSP_STATE_LOCK (sink);
  sink->contexts = g_list_remove (sink->contexts, context);
  GST_RTSP_STATE_UNLOCK (sink);

  /* FIXME: Shut down and clean up streaming on this pad,
   * do teardown if needed */
  GST_LOG_OBJECT (sink,
      "Cleaning up payloader and stream for released pad %" GST_PTR_FORMAT,
      pad);

  if (context->stream_transport) {
    gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
    gst_object_unref (context->stream_transport);
    context->stream_transport = NULL;
  }
  if (context->stream) {
    if (context->joined) {
      gst_rtsp_stream_leave_bin (context->stream,
          GST_BIN (sink->internal_bin), sink->rtpbin);
      context->joined = FALSE;
    }
    gst_object_unref (context->stream);
    context->stream = NULL;
  }
  if (context->srtcpparams)
    gst_caps_unref (context->srtcpparams);

  g_free (context->conninfo.location);
  context->conninfo.location = NULL;

  g_mutex_clear (&context->conninfo.send_lock);
  g_mutex_clear (&context->conninfo.recv_lock);

  g_free (context);

  gst_element_remove_pad (element, pad);
}

static GstClock *
gst_rtsp_client_sink_provide_clock (GstElement * element)
{
  GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
  GstClock *clock;

  if ((clock = sink->provided_clock) != NULL)
    gst_object_ref (clock);

  return clock;
}

/* a proxy string of the format [user:passwd@]host[:port] */
static gboolean
gst_rtsp_client_sink_set_proxy (GstRTSPClientSink * rtsp, const gchar * proxy)
{
  const gchar *p, *at, *col;

  g_free (rtsp->proxy_user);
  rtsp->proxy_user = NULL;
  g_free (rtsp->proxy_passwd);
  rtsp->proxy_passwd = NULL;
  g_free (rtsp->proxy_host);
  rtsp->proxy_host = NULL;
  rtsp->proxy_port = 0;

  p = proxy;

  if (p == NULL)
    return TRUE;

  /* we allow http:// in front but ignore it */
  if (g_str_has_prefix (p, "http://"))
    p += 7;

  at = strchr (p, '@');
  if (at) {
    /* look for user:passwd */
    col = strchr (proxy, ':');
    if (col == NULL || col > at)
      return FALSE;

    rtsp->proxy_user = g_strndup (p, col - p);
    col++;
    rtsp->proxy_passwd = g_strndup (col, at - col);

    /* move to host */
    p = at + 1;
  } else {
    if (rtsp->prop_proxy_id != NULL && *rtsp->prop_proxy_id != '\0')
      rtsp->proxy_user = g_strdup (rtsp->prop_proxy_id);
    if (rtsp->prop_proxy_pw != NULL && *rtsp->prop_proxy_pw != '\0')
      rtsp->proxy_passwd = g_strdup (rtsp->prop_proxy_pw);
    if (rtsp->proxy_user != NULL || rtsp->proxy_passwd != NULL) {
      GST_LOG_OBJECT (rtsp, "set proxy user/pw from properties: %s:%s",
          GST_STR_NULL (rtsp->proxy_user), GST_STR_NULL (rtsp->proxy_passwd));
    }
  }
  col = strchr (p, ':');

  if (col) {
    /* everything before the colon is the hostname */
    rtsp->proxy_host = g_strndup (p, col - p);
    p = col + 1;
    rtsp->proxy_port = strtoul (p, (char **) &p, 10);
  } else {
    rtsp->proxy_host = g_strdup (p);
    rtsp->proxy_port = 8080;
  }
  return TRUE;
}

static void
gst_rtsp_client_sink_set_tcp_timeout (GstRTSPClientSink * rtsp_client_sink,
    guint64 timeout)
{
  rtsp_client_sink->tcp_timeout = timeout;
}

static void
gst_rtsp_client_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstRTSPClientSink *rtsp_client_sink;

  rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);

  switch (prop_id) {
    case PROP_LOCATION:
      gst_rtsp_client_sink_uri_set_uri (GST_URI_HANDLER (rtsp_client_sink),
          g_value_get_string (value), NULL);
      break;
    case PROP_PROTOCOLS:
      rtsp_client_sink->protocols = g_value_get_flags (value);
      break;
    case PROP_PROFILES:
      rtsp_client_sink->profiles = g_value_get_flags (value);
      break;
    case PROP_DEBUG:
      rtsp_client_sink->debug = g_value_get_boolean (value);
      break;
    case PROP_RETRY:
      rtsp_client_sink->retry = g_value_get_uint (value);
      break;
    case PROP_TIMEOUT:
      rtsp_client_sink->udp_timeout = g_value_get_uint64 (value);
      break;
    case PROP_TCP_TIMEOUT:
      gst_rtsp_client_sink_set_tcp_timeout (rtsp_client_sink,
          g_value_get_uint64 (value));
      break;
    case PROP_LATENCY:
      rtsp_client_sink->latency = g_value_get_uint (value);
      break;
    case PROP_RTX_TIME:
      rtsp_client_sink->rtx_time = g_value_get_uint (value);
      break;
    case PROP_DO_RTSP_KEEP_ALIVE:
      rtsp_client_sink->do_rtsp_keep_alive = g_value_get_boolean (value);
      break;
    case PROP_PROXY:
      gst_rtsp_client_sink_set_proxy (rtsp_client_sink,
          g_value_get_string (value));
      break;
    case PROP_PROXY_ID:
      if (rtsp_client_sink->prop_proxy_id)
        g_free (rtsp_client_sink->prop_proxy_id);
      rtsp_client_sink->prop_proxy_id = g_value_dup_string (value);
      break;
    case PROP_PROXY_PW:
      if (rtsp_client_sink->prop_proxy_pw)
        g_free (rtsp_client_sink->prop_proxy_pw);
      rtsp_client_sink->prop_proxy_pw = g_value_dup_string (value);
      break;
    case PROP_RTP_BLOCKSIZE:
      rtsp_client_sink->rtp_blocksize = g_value_get_uint (value);
      break;
    case PROP_USER_ID:
      if (rtsp_client_sink->user_id)
        g_free (rtsp_client_sink->user_id);
      rtsp_client_sink->user_id = g_value_dup_string (value);
      break;
    case PROP_USER_PW:
      if (rtsp_client_sink->user_pw)
        g_free (rtsp_client_sink->user_pw);
      rtsp_client_sink->user_pw = g_value_dup_string (value);
      break;
    case PROP_PORT_RANGE:
    {
      const gchar *str;

      str = g_value_get_string (value);
      if (!str || !sscanf (str, "%u-%u",
              &rtsp_client_sink->client_port_range.min,
              &rtsp_client_sink->client_port_range.max)) {
        rtsp_client_sink->client_port_range.min = 0;
        rtsp_client_sink->client_port_range.max = 0;
      }
      break;
    }
    case PROP_UDP_BUFFER_SIZE:
      rtsp_client_sink->udp_buffer_size = g_value_get_int (value);
      break;
    case PROP_UDP_RECONNECT:
      rtsp_client_sink->udp_reconnect = g_value_get_boolean (value);
      break;
    case PROP_MULTICAST_IFACE:
      g_free (rtsp_client_sink->multi_iface);

      if (g_value_get_string (value) == NULL)
        rtsp_client_sink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE);
      else
        rtsp_client_sink->multi_iface = g_value_dup_string (value);
      break;
    case PROP_SDES:
      if (rtsp_client_sink->sdes)
        gst_structure_free (rtsp_client_sink->sdes);
      rtsp_client_sink->sdes = g_value_dup_boxed (value);
      break;
    case PROP_TLS_VALIDATION_FLAGS:
      rtsp_client_sink->tls_validation_flags = g_value_get_flags (value);
      break;
    case PROP_TLS_DATABASE:
      g_clear_object (&rtsp_client_sink->tls_database);
      rtsp_client_sink->tls_database = g_value_dup_object (value);
      break;
    case PROP_TLS_INTERACTION:
      g_clear_object (&rtsp_client_sink->tls_interaction);
      rtsp_client_sink->tls_interaction = g_value_dup_object (value);
      break;
    case PROP_NTP_TIME_SOURCE:
      rtsp_client_sink->ntp_time_source = g_value_get_enum (value);
      break;
    case PROP_USER_AGENT:
      g_free (rtsp_client_sink->user_agent);
      rtsp_client_sink->user_agent = g_value_dup_string (value);
      break;
    case PROP_PUBLISH_CLOCK_MODE:
      rtsp_client_sink->publish_clock_mode = g_value_get_enum (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_rtsp_client_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstRTSPClientSink *rtsp_client_sink;

  rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);

  switch (prop_id) {
    case PROP_LOCATION:
      g_value_set_string (value, rtsp_client_sink->conninfo.location);
      break;
    case PROP_PROTOCOLS:
      g_value_set_flags (value, rtsp_client_sink->protocols);
      break;
    case PROP_PROFILES:
      g_value_set_flags (value, rtsp_client_sink->profiles);
      break;
    case PROP_DEBUG:
      g_value_set_boolean (value, rtsp_client_sink->debug);
      break;
    case PROP_RETRY:
      g_value_set_uint (value, rtsp_client_sink->retry);
      break;
    case PROP_TIMEOUT:
      g_value_set_uint64 (value, rtsp_client_sink->udp_timeout);
      break;
    case PROP_TCP_TIMEOUT:
      g_value_set_uint64 (value, rtsp_client_sink->tcp_timeout);
      break;
    case PROP_LATENCY:
      g_value_set_uint (value, rtsp_client_sink->latency);
      break;
    case PROP_RTX_TIME:
      g_value_set_uint (value, rtsp_client_sink->rtx_time);
      break;
    case PROP_DO_RTSP_KEEP_ALIVE:
      g_value_set_boolean (value, rtsp_client_sink->do_rtsp_keep_alive);
      break;
    case PROP_PROXY:
    {
      gchar *str;

      if (rtsp_client_sink->proxy_host) {
        str =
            g_strdup_printf ("%s:%d", rtsp_client_sink->proxy_host,
            rtsp_client_sink->proxy_port);
      } else {
        str = NULL;
      }
      g_value_take_string (value, str);
      break;
    }
    case PROP_PROXY_ID:
      g_value_set_string (value, rtsp_client_sink->prop_proxy_id);
      break;
    case PROP_PROXY_PW:
      g_value_set_string (value, rtsp_client_sink->prop_proxy_pw);
      break;
    case PROP_RTP_BLOCKSIZE:
      g_value_set_uint (value, rtsp_client_sink->rtp_blocksize);
      break;
    case PROP_USER_ID:
      g_value_set_string (value, rtsp_client_sink->user_id);
      break;
    case PROP_USER_PW:
      g_value_set_string (value, rtsp_client_sink->user_pw);
      break;
    case PROP_PORT_RANGE:
    {
      gchar *str;

      if (rtsp_client_sink->client_port_range.min != 0) {
        str = g_strdup_printf ("%u-%u", rtsp_client_sink->client_port_range.min,
            rtsp_client_sink->client_port_range.max);
      } else {
        str = NULL;
      }
      g_value_take_string (value, str);
      break;
    }
    case PROP_UDP_BUFFER_SIZE:
      g_value_set_int (value, rtsp_client_sink->udp_buffer_size);
      break;
    case PROP_UDP_RECONNECT:
      g_value_set_boolean (value, rtsp_client_sink->udp_reconnect);
      break;
    case PROP_MULTICAST_IFACE:
      g_value_set_string (value, rtsp_client_sink->multi_iface);
      break;
    case PROP_SDES:
      g_value_set_boxed (value, rtsp_client_sink->sdes);
      break;
    case PROP_TLS_VALIDATION_FLAGS:
      g_value_set_flags (value, rtsp_client_sink->tls_validation_flags);
      break;
    case PROP_TLS_DATABASE:
      g_value_set_object (value, rtsp_client_sink->tls_database);
      break;
    case PROP_TLS_INTERACTION:
      g_value_set_object (value, rtsp_client_sink->tls_interaction);
      break;
    case PROP_NTP_TIME_SOURCE:
      g_value_set_enum (value, rtsp_client_sink->ntp_time_source);
      break;
    case PROP_USER_AGENT:
      g_value_set_string (value, rtsp_client_sink->user_agent);
      break;
    case PROP_PUBLISH_CLOCK_MODE:
      g_value_set_enum (value, rtsp_client_sink->publish_clock_mode);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static const gchar *
get_aggregate_control (GstRTSPClientSink * sink)
{
  const gchar *base;

  if (sink->control)
    base = sink->control;
  else if (sink->content_base)
    base = sink->content_base;
  else if (sink->conninfo.url_str)
    base = sink->conninfo.url_str;
  else
    base = "/";

  return base;
}

static void
gst_rtsp_client_sink_cleanup (GstRTSPClientSink * sink)
{
  GList *walk;

  GST_DEBUG_OBJECT (sink, "cleanup");

  gst_element_set_state (GST_ELEMENT (sink->internal_bin), GST_STATE_NULL);

  /* Clean up any left over stream objects */
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) (walk->data);
    if (context->stream_transport) {
      gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
      gst_object_unref (context->stream_transport);
      context->stream_transport = NULL;
    }

    if (context->stream) {
      if (context->joined) {
        gst_rtsp_stream_leave_bin (context->stream,
            GST_BIN (sink->internal_bin), sink->rtpbin);
        context->joined = FALSE;
      }
      gst_object_unref (context->stream);
      context->stream = NULL;
    }

    if (context->srtcpparams) {
      gst_caps_unref (context->srtcpparams);
      context->srtcpparams = NULL;
    }
    g_free (context->conninfo.location);
    context->conninfo.location = NULL;
  }

  if (sink->rtpbin) {
    gst_element_set_state (sink->rtpbin, GST_STATE_NULL);
    gst_bin_remove (GST_BIN_CAST (sink->internal_bin), sink->rtpbin);
    sink->rtpbin = NULL;
  }

  g_free (sink->content_base);
  sink->content_base = NULL;

  g_free (sink->control);
  sink->control = NULL;

  if (sink->range)
    gst_rtsp_range_free (sink->range);
  sink->range = NULL;

  /* don't clear the SDP when it was used in the url */
  if (sink->uri_sdp && !sink->from_sdp) {
    gst_sdp_message_free (sink->uri_sdp);
    sink->uri_sdp = NULL;
  }

  if (sink->provided_clock) {
    gst_object_unref (sink->provided_clock);
    sink->provided_clock = NULL;
  }

  g_free (sink->server_ip);
  sink->server_ip = NULL;

  sink->next_pad_id = 0;
  sink->next_dyn_pt = 96;
}

static GstRTSPResult
gst_rtsp_client_sink_connection_send (GstRTSPClientSink * sink,
    GstRTSPConnInfo * conninfo, GstRTSPMessage * message, gint64 timeout)
{
  GstRTSPResult ret;

  if (conninfo->connection) {
    g_mutex_lock (&conninfo->send_lock);
    ret =
        gst_rtsp_connection_send_usec (conninfo->connection, message, timeout);
    g_mutex_unlock (&conninfo->send_lock);
  } else {
    ret = GST_RTSP_ERROR;
  }

  return ret;
}

static GstRTSPResult
gst_rtsp_client_sink_connection_send_messages (GstRTSPClientSink * sink,
    GstRTSPConnInfo * conninfo, GstRTSPMessage * messages, guint n_messages,
    gint64 timeout)
{
  GstRTSPResult ret;

  if (conninfo->connection) {
    g_mutex_lock (&conninfo->send_lock);
    ret =
        gst_rtsp_connection_send_messages_usec (conninfo->connection, messages,
        n_messages, timeout);
    g_mutex_unlock (&conninfo->send_lock);
  } else {
    ret = GST_RTSP_ERROR;
  }

  return ret;
}

static GstRTSPResult
gst_rtsp_client_sink_connection_receive (GstRTSPClientSink * sink,
    GstRTSPConnInfo * conninfo, GstRTSPMessage * message, gint64 timeout)
{
  GstRTSPResult ret;

  if (conninfo->connection) {
    g_mutex_lock (&conninfo->recv_lock);
    ret = gst_rtsp_connection_receive_usec (conninfo->connection, message,
        timeout);
    g_mutex_unlock (&conninfo->recv_lock);
  } else {
    ret = GST_RTSP_ERROR;
  }

  return ret;
}

static gboolean
accept_certificate_cb (GTlsConnection * conn, GTlsCertificate * peer_cert,
    GTlsCertificateFlags errors, gpointer user_data)
{
  GstRTSPClientSink *sink = user_data;
  gboolean accept = FALSE;

  g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_ACCEPT_CERTIFICATE],
      0, conn, peer_cert, errors, &accept);

  return accept;
}

static GstRTSPResult
gst_rtsp_conninfo_connect (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
    gboolean async)
{
  GstRTSPResult res;

  if (info->connection == NULL) {
    if (info->url == NULL) {
      GST_DEBUG_OBJECT (sink, "parsing uri (%s)...", info->location);
      if ((res = gst_rtsp_url_parse (info->location, &info->url)) < 0)
        goto parse_error;
    }

    /* create connection */
    GST_DEBUG_OBJECT (sink, "creating connection (%s)...", info->location);
    if ((res = gst_rtsp_connection_create (info->url, &info->connection)) < 0)
      goto could_not_create;

    if (info->url_str)
      g_free (info->url_str);
    info->url_str = gst_rtsp_url_get_request_uri (info->url);

    GST_DEBUG_OBJECT (sink, "sanitized uri %s", info->url_str);

    if (info->url->transports & GST_RTSP_LOWER_TRANS_TLS) {
      if (!gst_rtsp_connection_set_tls_validation_flags (info->connection,
              sink->tls_validation_flags))
        GST_WARNING_OBJECT (sink, "Unable to set TLS validation flags");

      if (sink->tls_database)
        gst_rtsp_connection_set_tls_database (info->connection,
            sink->tls_database);

      if (sink->tls_interaction)
        gst_rtsp_connection_set_tls_interaction (info->connection,
            sink->tls_interaction);

      gst_rtsp_connection_set_accept_certificate_func (info->connection,
          accept_certificate_cb, sink, NULL);
    }

    if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP)
      gst_rtsp_connection_set_tunneled (info->connection, TRUE);

    if (sink->proxy_host) {
      GST_DEBUG_OBJECT (sink, "setting proxy %s:%d", sink->proxy_host,
          sink->proxy_port);
      gst_rtsp_connection_set_proxy (info->connection, sink->proxy_host,
          sink->proxy_port);
    }
  }

  if (!info->connected) {
    /* connect */
    if (async)
      GST_ELEMENT_PROGRESS (sink, CONTINUE, "connect",
          ("Connecting to %s", info->location));
    GST_DEBUG_OBJECT (sink, "connecting (%s)...", info->location);
    if ((res =
            gst_rtsp_connection_connect_usec (info->connection,
                sink->tcp_timeout)) < 0)
      goto could_not_connect;

    info->connected = TRUE;
  }
  return GST_RTSP_OK;

  /* ERRORS */
parse_error:
  {
    GST_ERROR_OBJECT (sink, "No valid RTSP URL was provided");
    return res;
  }
could_not_create:
  {
    gchar *str = gst_rtsp_strresult (res);
    GST_ERROR_OBJECT (sink, "Could not create connection. (%s)", str);
    g_free (str);
    return res;
  }
could_not_connect:
  {
    gchar *str = gst_rtsp_strresult (res);
    GST_ERROR_OBJECT (sink, "Could not connect to server. (%s)", str);
    g_free (str);
    return res;
  }
}

static GstRTSPResult
gst_rtsp_conninfo_close (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
    gboolean free)
{
  GST_RTSP_STATE_LOCK (sink);
  if (info->connected) {
    GST_DEBUG_OBJECT (sink, "closing connection...");
    gst_rtsp_connection_close (info->connection);
    info->connected = FALSE;
  }
  if (free && info->connection) {
    /* free connection */
    GST_DEBUG_OBJECT (sink, "freeing connection...");
    gst_rtsp_connection_free (info->connection);
    g_mutex_lock (&sink->preroll_lock);
    info->connection = NULL;
    g_cond_broadcast (&sink->preroll_cond);
    g_mutex_unlock (&sink->preroll_lock);
  }
  GST_RTSP_STATE_UNLOCK (sink);
  return GST_RTSP_OK;
}

static GstRTSPResult
gst_rtsp_conninfo_reconnect (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
    gboolean async)
{
  GstRTSPResult res;

  GST_DEBUG_OBJECT (sink, "reconnecting connection...");
  gst_rtsp_conninfo_close (sink, info, FALSE);
  res = gst_rtsp_conninfo_connect (sink, info, async);

  return res;
}

static void
gst_rtsp_client_sink_connection_flush (GstRTSPClientSink * sink, gboolean flush)
{
  GList *walk;

  GST_DEBUG_OBJECT (sink, "set flushing %d", flush);
  g_mutex_lock (&sink->preroll_lock);
  if (sink->conninfo.connection && sink->conninfo.flushing != flush) {
    GST_DEBUG_OBJECT (sink, "connection flush");
    gst_rtsp_connection_flush (sink->conninfo.connection, flush);
    sink->conninfo.flushing = flush;
  }
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
    if (stream->conninfo.connection && stream->conninfo.flushing != flush) {
      GST_DEBUG_OBJECT (sink, "stream %p flush", stream);
      gst_rtsp_connection_flush (stream->conninfo.connection, flush);
      stream->conninfo.flushing = flush;
    }
  }
  g_cond_broadcast (&sink->preroll_cond);
  g_mutex_unlock (&sink->preroll_lock);
}

static GstRTSPResult
gst_rtsp_client_sink_init_request (GstRTSPClientSink * sink,
    GstRTSPMessage * msg, GstRTSPMethod method, const gchar * uri)
{
  GstRTSPResult res;

  res = gst_rtsp_message_init_request (msg, method, uri);
  if (res < 0)
    return res;

  /* set user-agent */
  if (sink->user_agent) {
    GString *user_agent = g_string_new (sink->user_agent);

    g_string_replace (user_agent, "{VERSION}", PACKAGE_VERSION, 0);
    gst_rtsp_message_add_header (msg, GST_RTSP_HDR_USER_AGENT, user_agent->str);
    g_string_free (user_agent, TRUE);
  }

  return res;
}

/* FIXME, handle server request, reply with OK, for now */
static GstRTSPResult
gst_rtsp_client_sink_handle_request (GstRTSPClientSink * sink,
    GstRTSPConnInfo * conninfo, GstRTSPMessage * request)
{
  GstRTSPMessage response = { 0 };
  GstRTSPResult res;

  GST_DEBUG_OBJECT (sink, "got server request message");

  if (sink->debug)
    gst_rtsp_message_dump (request);

  /* default implementation, send OK */
  GST_DEBUG_OBJECT (sink, "prepare OK reply");
  res =
      gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, "OK",
      request);
  if (res < 0)
    goto send_error;

  /* let app parse and reply */
  g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_HANDLE_REQUEST],
      0, request, &response);

  if (sink->debug)
    gst_rtsp_message_dump (&response);

  res = gst_rtsp_client_sink_connection_send (sink, conninfo, &response, 0);
  if (res < 0)
    goto send_error;

  gst_rtsp_message_unset (&response);

  return GST_RTSP_OK;

  /* ERRORS */
send_error:
  {
    gst_rtsp_message_unset (&response);
    return res;
  }
}

/* send server keep-alive */
static GstRTSPResult
gst_rtsp_client_sink_send_keep_alive (GstRTSPClientSink * sink)
{
  GstRTSPMessage request = { 0 };
  GstRTSPResult res;
  GstRTSPMethod method;
  const gchar *control;

  if (sink->do_rtsp_keep_alive == FALSE) {
    GST_DEBUG_OBJECT (sink, "do-rtsp-keep-alive is FALSE, not sending.");
    gst_rtsp_connection_reset_timeout (sink->conninfo.connection);
    return GST_RTSP_OK;
  }

  GST_DEBUG_OBJECT (sink, "creating server keep-alive");

  /* find a method to use for keep-alive */
  if (sink->methods & GST_RTSP_GET_PARAMETER)
    method = GST_RTSP_GET_PARAMETER;
  else
    method = GST_RTSP_OPTIONS;

  control = get_aggregate_control (sink);
  if (control == NULL)
    goto no_control;

  res = gst_rtsp_client_sink_init_request (sink, &request, method, control);
  if (res < 0)
    goto send_error;

  if (sink->debug)
    gst_rtsp_message_dump (&request);

  res =
      gst_rtsp_client_sink_connection_send (sink, &sink->conninfo, &request, 0);
  if (res < 0)
    goto send_error;

  gst_rtsp_connection_reset_timeout (sink->conninfo.connection);
  gst_rtsp_message_unset (&request);

  return GST_RTSP_OK;

  /* ERRORS */
no_control:
  {
    GST_WARNING_OBJECT (sink, "no control url to send keepalive");
    return GST_RTSP_OK;
  }
send_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    gst_rtsp_message_unset (&request);
    GST_ELEMENT_WARNING (sink, RESOURCE, WRITE, (NULL),
        ("Could not send keep-alive. (%s)", str));
    g_free (str);
    return res;
  }
}

static GstFlowReturn
gst_rtsp_client_sink_loop_rx (GstRTSPClientSink * sink)
{
  GstRTSPResult res;
  GstRTSPMessage message = { 0 };
  gint retry = 0;

  while (TRUE) {
    gint64 timeout;

    /* get the next timeout interval */
    timeout = gst_rtsp_connection_next_timeout_usec (sink->conninfo.connection);

    GST_DEBUG_OBJECT (sink, "doing receive with timeout %d seconds",
        (gint) timeout / G_USEC_PER_SEC);

    gst_rtsp_message_unset (&message);

    /* we should continue reading the TCP socket because the server might
     * send us requests. When the session timeout expires, we need to send a
     * keep-alive request to keep the session open. */
    res =
        gst_rtsp_client_sink_connection_receive (sink,
        &sink->conninfo, &message, timeout);

    switch (res) {
      case GST_RTSP_OK:
        GST_DEBUG_OBJECT (sink, "we received a server message");
        break;
      case GST_RTSP_EINTR:
        /* we got interrupted, see what we have to do */
        goto interrupt;
      case GST_RTSP_ETIMEOUT:
        /* send keep-alive, ignore the result, a warning will be posted. */
        GST_DEBUG_OBJECT (sink, "timeout, sending keep-alive");
        if ((res =
                gst_rtsp_client_sink_send_keep_alive (sink)) == GST_RTSP_EINTR)
          goto interrupt;
        continue;
      case GST_RTSP_EEOF:
        /* server closed the connection. not very fatal for UDP, reconnect and
         * see what happens. */
        GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
            ("The server closed the connection."));
        if (sink->udp_reconnect) {
          if ((res =
                  gst_rtsp_conninfo_reconnect (sink, &sink->conninfo,
                      FALSE)) < 0)
            goto connect_error;
        } else {
          goto server_eof;
        }
        continue;
        break;
      case GST_RTSP_ENET:
        GST_DEBUG_OBJECT (sink, "An ethernet problem occured.");
        /* FALLTHROUGH */
      default:
        GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
            ("Unhandled return value %d.", res));
        goto receive_error;
    }

    switch (message.type) {
      case GST_RTSP_MESSAGE_REQUEST:
        /* server sends us a request message, handle it */
        res =
            gst_rtsp_client_sink_handle_request (sink,
            &sink->conninfo, &message);
        if (res == GST_RTSP_EEOF)
          goto server_eof;
        else if (res < 0)
          goto handle_request_failed;
        break;
      case GST_RTSP_MESSAGE_RESPONSE:
        /* we ignore response and data messages */
        GST_DEBUG_OBJECT (sink, "ignoring response message");
        if (sink->debug)
          gst_rtsp_message_dump (&message);
        if (message.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) {
          GST_DEBUG_OBJECT (sink, "but is Unauthorized response ...");
          if (gst_rtsp_client_sink_setup_auth (sink, &message) && !(retry++)) {
            GST_DEBUG_OBJECT (sink, "so retrying keep-alive");
            if ((res =
                    gst_rtsp_client_sink_send_keep_alive (sink)) ==
                GST_RTSP_EINTR)
              goto interrupt;
          }
        } else {
          retry = 0;
        }
        break;
      case GST_RTSP_MESSAGE_DATA:
        /* we ignore response and data messages */
        GST_DEBUG_OBJECT (sink, "ignoring data message");
        break;
      default:
        GST_WARNING_OBJECT (sink, "ignoring unknown message type %d",
            message.type);
        break;
    }
  }
  g_assert_not_reached ();

  /* we get here when the connection got interrupted */
interrupt:
  {
    gst_rtsp_message_unset (&message);
    GST_DEBUG_OBJECT (sink, "got interrupted");
    return GST_FLOW_FLUSHING;
  }
connect_error:
  {
    gchar *str = gst_rtsp_strresult (res);
    GstFlowReturn ret;

    sink->conninfo.connected = FALSE;
    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ_WRITE, (NULL),
          ("Could not connect to server. (%s)", str));
      g_free (str);
      ret = GST_FLOW_ERROR;
    } else {
      ret = GST_FLOW_FLUSHING;
    }
    return ret;
  }
receive_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
        ("Could not receive message. (%s)", str));
    g_free (str);
    return GST_FLOW_ERROR;
  }
handle_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);
    GstFlowReturn ret;

    gst_rtsp_message_unset (&message);
    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
          ("Could not handle server message. (%s)", str));
      g_free (str);
      ret = GST_FLOW_ERROR;
    } else {
      ret = GST_FLOW_FLUSHING;
    }
    return ret;
  }
server_eof:
  {
    GST_DEBUG_OBJECT (sink, "we got an eof from the server");
    GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
        ("The server closed the connection."));
    sink->conninfo.connected = FALSE;
    gst_rtsp_message_unset (&message);
    return GST_FLOW_EOS;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_reconnect (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPResult res = GST_RTSP_OK;
  gboolean restart = FALSE;

  GST_DEBUG_OBJECT (sink, "doing reconnect");

  GST_FIXME_OBJECT (sink, "Reconnection is not yet implemented");

  /* no need to restart, we're done */
  if (!restart)
    goto done;

  /* we can try only TCP now */
  sink->cur_protocols = GST_RTSP_LOWER_TRANS_TCP;

  /* close and cleanup our state */
  if ((res = gst_rtsp_client_sink_close (sink, async, FALSE)) < 0)
    goto done;

  /* see if we have TCP left to try. Also don't try TCP when we were configured
   * with an SDP. */
  if (!(sink->protocols & GST_RTSP_LOWER_TRANS_TCP) || sink->from_sdp)
    goto no_protocols;

  /* We post a warning message now to inform the user
   * that nothing happened. It's most likely a firewall thing. */
  GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
      ("Could not receive any UDP packets for %.4f seconds, maybe your "
          "firewall is blocking it. Retrying using a TCP connection.",
          gst_guint64_to_gdouble (sink->udp_timeout / 1000000.0)));

  /* open new connection using tcp */
  if (gst_rtsp_client_sink_open (sink, async) < 0)
    goto open_failed;

  /* start recording */
  if (gst_rtsp_client_sink_record (sink, async) < 0)
    goto play_failed;

done:
  return res;

  /* ERRORS */
no_protocols:
  {
    sink->cur_protocols = 0;
    /* no transport possible, post an error and stop */
    GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
        ("Could not receive any UDP packets for %.4f seconds, maybe your "
            "firewall is blocking it. No other protocols to try.",
            gst_guint64_to_gdouble (sink->udp_timeout / 1000000.0)));
    return GST_RTSP_ERROR;
  }
open_failed:
  {
    GST_DEBUG_OBJECT (sink, "open failed");
    return GST_RTSP_OK;
  }
play_failed:
  {
    GST_DEBUG_OBJECT (sink, "play failed");
    return GST_RTSP_OK;
  }
}

static void
gst_rtsp_client_sink_loop_start_cmd (GstRTSPClientSink * sink, gint cmd)
{
  switch (cmd) {
    case CMD_OPEN:
      GST_ELEMENT_PROGRESS (sink, START, "open", ("Opening Stream"));
      break;
    case CMD_RECORD:
      GST_ELEMENT_PROGRESS (sink, START, "request", ("Sending RECORD request"));
      break;
    case CMD_PAUSE:
      GST_ELEMENT_PROGRESS (sink, START, "request", ("Sending PAUSE request"));
      break;
    case CMD_CLOSE:
      GST_ELEMENT_PROGRESS (sink, START, "close", ("Closing Stream"));
      break;
    default:
      break;
  }
}

static void
gst_rtsp_client_sink_loop_complete_cmd (GstRTSPClientSink * sink, gint cmd)
{
  switch (cmd) {
    case CMD_OPEN:
      GST_ELEMENT_PROGRESS (sink, COMPLETE, "open", ("Opened Stream"));
      break;
    case CMD_RECORD:
      GST_ELEMENT_PROGRESS (sink, COMPLETE, "request", ("Sent RECORD request"));
      break;
    case CMD_PAUSE:
      GST_ELEMENT_PROGRESS (sink, COMPLETE, "request", ("Sent PAUSE request"));
      break;
    case CMD_CLOSE:
      GST_ELEMENT_PROGRESS (sink, COMPLETE, "close", ("Closed Stream"));
      break;
    default:
      break;
  }
}

static void
gst_rtsp_client_sink_loop_cancel_cmd (GstRTSPClientSink * sink, gint cmd)
{
  switch (cmd) {
    case CMD_OPEN:
      GST_ELEMENT_PROGRESS (sink, CANCELED, "open", ("Open canceled"));
      break;
    case CMD_RECORD:
      GST_ELEMENT_PROGRESS (sink, CANCELED, "request", ("RECORD canceled"));
      break;
    case CMD_PAUSE:
      GST_ELEMENT_PROGRESS (sink, CANCELED, "request", ("PAUSE canceled"));
      break;
    case CMD_CLOSE:
      GST_ELEMENT_PROGRESS (sink, CANCELED, "close", ("Close canceled"));
      break;
    default:
      break;
  }
}

static void
gst_rtsp_client_sink_loop_error_cmd (GstRTSPClientSink * sink, gint cmd)
{
  switch (cmd) {
    case CMD_OPEN:
      GST_ELEMENT_PROGRESS (sink, ERROR, "open", ("Open failed"));
      break;
    case CMD_RECORD:
      GST_ELEMENT_PROGRESS (sink, ERROR, "request", ("RECORD failed"));
      break;
    case CMD_PAUSE:
      GST_ELEMENT_PROGRESS (sink, ERROR, "request", ("PAUSE failed"));
      break;
    case CMD_CLOSE:
      GST_ELEMENT_PROGRESS (sink, ERROR, "close", ("Close failed"));
      break;
    default:
      break;
  }
}

static void
gst_rtsp_client_sink_loop_end_cmd (GstRTSPClientSink * sink, gint cmd,
    GstRTSPResult ret)
{
  if (ret == GST_RTSP_OK)
    gst_rtsp_client_sink_loop_complete_cmd (sink, cmd);
  else if (ret == GST_RTSP_EINTR)
    gst_rtsp_client_sink_loop_cancel_cmd (sink, cmd);
  else
    gst_rtsp_client_sink_loop_error_cmd (sink, cmd);
}

static gboolean
gst_rtsp_client_sink_loop_send_cmd (GstRTSPClientSink * sink, gint cmd,
    gint mask)
{
  gint old;
  gboolean flushed = FALSE;

  /* start new request */
  gst_rtsp_client_sink_loop_start_cmd (sink, cmd);

  GST_DEBUG_OBJECT (sink, "sending cmd %s", cmd_to_string (cmd));

  GST_OBJECT_LOCK (sink);
  old = sink->pending_cmd;
  if (old == CMD_RECONNECT) {
    GST_DEBUG_OBJECT (sink, "ignore, we were reconnecting");
    cmd = CMD_RECONNECT;
  }
  if (old != CMD_WAIT) {
    sink->pending_cmd = CMD_WAIT;
    GST_OBJECT_UNLOCK (sink);
    /* cancel previous request */
    GST_DEBUG_OBJECT (sink, "cancel previous request %s", cmd_to_string (old));
    gst_rtsp_client_sink_loop_cancel_cmd (sink, old);
    GST_OBJECT_LOCK (sink);
  }
  sink->pending_cmd = cmd;
  /* interrupt if allowed */
  if (sink->busy_cmd & mask) {
    GST_DEBUG_OBJECT (sink, "connection flush busy %s",
        cmd_to_string (sink->busy_cmd));
    gst_rtsp_client_sink_connection_flush (sink, TRUE);
    flushed = TRUE;
  } else {
    GST_DEBUG_OBJECT (sink, "not interrupting busy cmd %s",
        cmd_to_string (sink->busy_cmd));
  }
  if (sink->task)
    gst_task_start (sink->task);
  GST_OBJECT_UNLOCK (sink);

  return flushed;
}

static gboolean
gst_rtsp_client_sink_loop (GstRTSPClientSink * sink)
{
  GstFlowReturn ret;

  if (!sink->conninfo.connection || !sink->conninfo.connected)
    goto no_connection;

  ret = gst_rtsp_client_sink_loop_rx (sink);
  if (ret != GST_FLOW_OK)
    goto pause;

  return TRUE;

  /* ERRORS */
no_connection:
  {
    GST_WARNING_OBJECT (sink, "we are not connected");
    ret = GST_FLOW_FLUSHING;
    goto pause;
  }
pause:
  {
    const gchar *reason = gst_flow_get_name (ret);

    GST_DEBUG_OBJECT (sink, "pausing task, reason %s", reason);
    gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT, CMD_LOOP);
    return FALSE;
  }
}

#ifndef GST_DISABLE_GST_DEBUG
static const gchar *
gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method)
{
  gint index = 0;

  while (method != 0) {
    index++;
    method >>= 1;
  }
  switch (index) {
    case 0:
      return "None";
    case 1:
      return "Basic";
    case 2:
      return "Digest";
  }

  return "Unknown";
}
#endif

/* Parse a WWW-Authenticate Response header and determine the
 * available authentication methods
 *
 * This code should also cope with the fact that each WWW-Authenticate
 * header can contain multiple challenge methods + tokens
 *
 * At the moment, for Basic auth, we just do a minimal check and don't
 * even parse out the realm */
static void
gst_rtsp_client_sink_parse_auth_hdr (GstRTSPMessage * response,
    GstRTSPAuthMethod * methods, GstRTSPConnection * conn, gboolean * stale)
{
  GstRTSPAuthCredential **credentials, **credential;

  g_return_if_fail (response != NULL);
  g_return_if_fail (methods != NULL);
  g_return_if_fail (stale != NULL);

  credentials =
      gst_rtsp_message_parse_auth_credentials (response,
      GST_RTSP_HDR_WWW_AUTHENTICATE);
  if (!credentials)
    return;

  credential = credentials;
  while (*credential) {
    if ((*credential)->scheme == GST_RTSP_AUTH_BASIC) {
      *methods |= GST_RTSP_AUTH_BASIC;
    } else if ((*credential)->scheme == GST_RTSP_AUTH_DIGEST) {
      GstRTSPAuthParam **param = (*credential)->params;

      *methods |= GST_RTSP_AUTH_DIGEST;

      gst_rtsp_connection_clear_auth_params (conn);
      *stale = FALSE;

      while (*param) {
        if (strcmp ((*param)->name, "stale") == 0
            && g_ascii_strcasecmp ((*param)->value, "TRUE") == 0)
          *stale = TRUE;
        gst_rtsp_connection_set_auth_param (conn, (*param)->name,
            (*param)->value);
        param++;
      }
    }

    credential++;
  }

  gst_rtsp_auth_credentials_free (credentials);
}

/**
 * gst_rtsp_client_sink_setup_auth:
 * @src: the rtsp source
 *
 * Configure a username and password and auth method on the
 * connection object based on a response we received from the
 * peer.
 *
 * Currently, this requires that a username and password were supplied
 * in the uri. In the future, they may be requested on demand by sending
 * a message up the bus.
 *
 * Returns: TRUE if authentication information could be set up correctly.
 */
static gboolean
gst_rtsp_client_sink_setup_auth (GstRTSPClientSink * sink,
    GstRTSPMessage * response)
{
  gchar *user = NULL;
  gchar *pass = NULL;
  GstRTSPAuthMethod avail_methods = GST_RTSP_AUTH_NONE;
  GstRTSPAuthMethod method;
  GstRTSPResult auth_result;
  GstRTSPUrl *url;
  GstRTSPConnection *conn;
  gboolean stale = FALSE;

  conn = sink->conninfo.connection;

  /* Identify the available auth methods and see if any are supported */
  gst_rtsp_client_sink_parse_auth_hdr (response, &avail_methods, conn, &stale);

  if (avail_methods == GST_RTSP_AUTH_NONE)
    goto no_auth_available;

  /* For digest auth, if the response indicates that the session
   * data are stale, we just update them in the connection object and
   * return TRUE to retry the request */
  if (stale)
    sink->tried_url_auth = FALSE;

  url = gst_rtsp_connection_get_url (conn);

  /* Do we have username and password available? */
  if (url != NULL && !sink->tried_url_auth && url->user != NULL
      && url->passwd != NULL) {
    user = url->user;
    pass = url->passwd;
    sink->tried_url_auth = TRUE;
    GST_DEBUG_OBJECT (sink,
        "Attempting authentication using credentials from the URL");
  } else {
    user = sink->user_id;
    pass = sink->user_pw;
    GST_DEBUG_OBJECT (sink,
        "Attempting authentication using credentials from the properties");
  }

  /* FIXME: If the url didn't contain username and password or we tried them
   * already, request a username and passwd from the application via some kind
   * of credentials request message */

  /* If we don't have a username and passwd at this point, bail out. */
  if (user == NULL || pass == NULL)
    goto no_user_pass;

  /* Try to configure for each available authentication method, strongest to
   * weakest */
  for (method = GST_RTSP_AUTH_MAX; method != GST_RTSP_AUTH_NONE; method >>= 1) {
    /* Check if this method is available on the server */
    if ((method & avail_methods) == 0)
      continue;

    /* Pass the credentials to the connection to try on the next request */
    auth_result = gst_rtsp_connection_set_auth (conn, method, user, pass);
    /* INVAL indicates an invalid username/passwd were supplied, so we'll just
     * ignore it and end up retrying later */
    if (auth_result == GST_RTSP_OK || auth_result == GST_RTSP_EINVAL) {
      GST_DEBUG_OBJECT (sink, "Attempting %s authentication",
          gst_rtsp_auth_method_to_string (method));
      break;
    }
  }

  if (method == GST_RTSP_AUTH_NONE)
    goto no_auth_available;

  return TRUE;

no_auth_available:
  {
    /* Output an error indicating that we couldn't connect because there were
     * no supported authentication protocols */
    GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ, (NULL),
        ("No supported authentication protocol was found"));
    return FALSE;
  }
no_user_pass:
  {
    /* We don't fire an error message, we just return FALSE and let the
     * normal NOT_AUTHORIZED error be propagated */
    return FALSE;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_try_send (GstRTSPClientSink * sink,
    GstRTSPConnInfo * conninfo, GstRTSPMessage * requests,
    guint n_requests, GstRTSPMessage * response, GstRTSPStatusCode * code)
{
  GstRTSPResult res;
  GstRTSPStatusCode thecode;
  gchar *content_base = NULL;
  gint try = 0;

  g_assert (n_requests == 1 || response == NULL);

again:
  GST_DEBUG_OBJECT (sink, "sending message");

  if (sink->debug && n_requests == 1)
    gst_rtsp_message_dump (&requests[0]);

  g_mutex_lock (&sink->send_lock);

  res =
      gst_rtsp_client_sink_connection_send_messages (sink, conninfo, requests,
      n_requests, sink->tcp_timeout);
  if (res < 0) {
    g_mutex_unlock (&sink->send_lock);
    goto send_error;
  }

  gst_rtsp_connection_reset_timeout (conninfo->connection);

  /* See if we should handle the response */
  if (response == NULL) {
    g_mutex_unlock (&sink->send_lock);
    return GST_RTSP_OK;
  }
next:
  res =
      gst_rtsp_client_sink_connection_receive (sink, conninfo, response,
      sink->tcp_timeout);

  g_mutex_unlock (&sink->send_lock);

  if (res < 0)
    goto receive_error;

  if (sink->debug)
    gst_rtsp_message_dump (response);


  switch (response->type) {
    case GST_RTSP_MESSAGE_REQUEST:
      res = gst_rtsp_client_sink_handle_request (sink, conninfo, response);
      if (res == GST_RTSP_EEOF)
        goto server_eof;
      else if (res < 0)
        goto handle_request_failed;
      g_mutex_lock (&sink->send_lock);
      goto next;
    case GST_RTSP_MESSAGE_RESPONSE:
      /* ok, a response is good */
      GST_DEBUG_OBJECT (sink, "received response message");
      break;
    case GST_RTSP_MESSAGE_DATA:
      /* we ignore data messages */
      GST_DEBUG_OBJECT (sink, "ignoring data message");
      g_mutex_lock (&sink->send_lock);
      goto next;
    default:
      GST_WARNING_OBJECT (sink, "ignoring unknown message type %d",
          response->type);
      g_mutex_lock (&sink->send_lock);
      goto next;
  }

  thecode = response->type_data.response.code;

  GST_DEBUG_OBJECT (sink, "got response message %d", thecode);

  /* if the caller wanted the result code, we store it. */
  if (code)
    *code = thecode;

  /* If the request didn't succeed, bail out before doing any more */
  if (thecode != GST_RTSP_STS_OK)
    return GST_RTSP_OK;

  /* store new content base if any */
  gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE,
      &content_base, 0);
  if (content_base) {
    g_free (sink->content_base);
    sink->content_base = g_strdup (content_base);
  }

  return GST_RTSP_OK;

  /* ERRORS */
send_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
          ("Could not send message. (%s)", str));
    } else {
      GST_WARNING_OBJECT (sink, "send interrupted");
    }
    g_free (str);
    return res;
  }
receive_error:
  {
    switch (res) {
      case GST_RTSP_EEOF:
        GST_WARNING_OBJECT (sink, "server closed connection");
        if ((try == 0) && !sink->interleaved && sink->udp_reconnect) {
          try++;
          /* if reconnect succeeds, try again */
          if ((res =
                  gst_rtsp_conninfo_reconnect (sink, &sink->conninfo,
                      FALSE)) == 0)
            goto again;
        }
        /* only try once after reconnect, else carry on and error out */
        /* FALLTHROUGH */
      default:
      {
        gchar *str = gst_rtsp_strresult (res);

        if (res != GST_RTSP_EINTR) {
          GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
              ("Could not receive message. (%s)", str));
        } else {
          GST_WARNING_OBJECT (sink, "receive interrupted");
        }
        g_free (str);
        break;
      }
    }
    return res;
  }
handle_request_failed:
  {
    /* ERROR was posted */
    gst_rtsp_message_unset (response);
    return res;
  }
server_eof:
  {
    GST_DEBUG_OBJECT (sink, "we got an eof from the server");
    GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
        ("The server closed the connection."));
    gst_rtsp_message_unset (response);
    return res;
  }
}

static void
gst_rtsp_client_sink_set_state (GstRTSPClientSink * sink, GstState state)
{
  GST_DEBUG_OBJECT (sink, "Setting internal state to %s",
      gst_state_get_name (state));
  gst_element_set_state (GST_ELEMENT (sink->internal_bin), state);
}

/**
 * gst_rtsp_client_sink_send:
 * @src: the rtsp source
 * @conn: the connection to send on
 * @request: must point to a valid request
 * @response: must point to an empty #GstRTSPMessage
 * @code: an optional code result
 *
 * send @request and retrieve the response in @response. optionally @code can be
 * non-NULL in which case it will contain the status code of the response.
 *
 * If This function returns #GST_RTSP_OK, @response will contain a valid response
 * message that should be cleaned with gst_rtsp_message_unset() after usage.
 *
 * If @code is NULL, this function will return #GST_RTSP_ERROR (with an invalid
 * @response message) if the response code was not 200 (OK).
 *
 * If the attempt results in an authentication failure, then this will attempt
 * to retrieve authentication credentials via gst_rtsp_client_sink_setup_auth and retry
 * the request.
 *
 * Returns: #GST_RTSP_OK if the processing was successful.
 */
static GstRTSPResult
gst_rtsp_client_sink_send (GstRTSPClientSink * sink, GstRTSPConnInfo * conninfo,
    GstRTSPMessage * request, GstRTSPMessage * response,
    GstRTSPStatusCode * code)
{
  GstRTSPStatusCode int_code = GST_RTSP_STS_OK;
  GstRTSPResult res = GST_RTSP_ERROR;
  gint count;
  gboolean retry;
  GstRTSPMethod method = GST_RTSP_INVALID;

  count = 0;
  do {
    retry = FALSE;

    /* make sure we don't loop forever */
    if (count++ > 8)
      break;

    /* save method so we can disable it when the server complains */
    method = request->type_data.request.method;

    if ((res =
            gst_rtsp_client_sink_try_send (sink, conninfo, request, 1, response,
                &int_code)) < 0)
      goto error;

    switch (int_code) {
      case GST_RTSP_STS_UNAUTHORIZED:
        if (gst_rtsp_client_sink_setup_auth (sink, response)) {
          /* Try the request/response again after configuring the auth info
           * and loop again */
          retry = TRUE;
        }
        break;
      default:
        break;
    }
  } while (retry == TRUE);

  /* If the user requested the code, let them handle errors, otherwise
   * post an error below */
  if (code != NULL)
    *code = int_code;
  else if (int_code != GST_RTSP_STS_OK)
    goto error_response;

  return res;

  /* ERRORS */
error:
  {
    GST_DEBUG_OBJECT (sink, "got error %d", res);
    return res;
  }
error_response:
  {
    res = GST_RTSP_ERROR;

    switch (response->type_data.response.code) {
      case GST_RTSP_STS_NOT_FOUND:
        GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL), ("%s",
                response->type_data.response.reason));
        break;
      case GST_RTSP_STS_UNAUTHORIZED:
        GST_ELEMENT_ERROR (sink, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s",
                response->type_data.response.reason));
        break;
      case GST_RTSP_STS_MOVED_PERMANENTLY:
      case GST_RTSP_STS_MOVE_TEMPORARILY:
      {
        gchar *new_location;
        GstRTSPLowerTrans transports;

        GST_DEBUG_OBJECT (sink, "got redirection");
        /* if we don't have a Location Header, we must error */
        if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_LOCATION,
                &new_location, 0) < 0)
          break;

        /* When we receive a redirect result, we go back to the INIT state after
         * parsing the new URI. The caller should do the needed steps to issue
         * a new setup when it detects this state change. */
        GST_DEBUG_OBJECT (sink, "redirection to %s", new_location);

        /* save current transports */
        if (sink->conninfo.url)
          transports = sink->conninfo.url->transports;
        else
          transports = GST_RTSP_LOWER_TRANS_UNKNOWN;

        gst_rtsp_client_sink_uri_set_uri (GST_URI_HANDLER (sink), new_location,
            NULL);

        /* set old transports */
        if (sink->conninfo.url && transports != GST_RTSP_LOWER_TRANS_UNKNOWN)
          sink->conninfo.url->transports = transports;

        sink->need_redirect = TRUE;
        sink->state = GST_RTSP_STATE_INIT;
        res = GST_RTSP_OK;
        break;
      }
      case GST_RTSP_STS_NOT_ACCEPTABLE:
      case GST_RTSP_STS_NOT_IMPLEMENTED:
      case GST_RTSP_STS_METHOD_NOT_ALLOWED:
        GST_WARNING_OBJECT (sink, "got NOT IMPLEMENTED, disable method %s",
            gst_rtsp_method_as_text (method));
        sink->methods &= ~method;
        res = GST_RTSP_OK;
        break;
      default:
        GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
            ("Got error response: %d (%s).", response->type_data.response.code,
                response->type_data.response.reason));
        break;
    }
    /* if we return ERROR we should unset the response ourselves */
    if (res == GST_RTSP_ERROR)
      gst_rtsp_message_unset (response);

    return res;
  }
}

/* parse the response and collect all the supported methods. We need this
 * information so that we don't try to send an unsupported request to the
 * server.
 */
static gboolean
gst_rtsp_client_sink_parse_methods (GstRTSPClientSink * sink,
    GstRTSPMessage * response)
{
  GstRTSPHeaderField field;
  gchar *respoptions;
  gint indx = 0;

  /* reset supported methods */
  sink->methods = 0;

  /* Try Allow Header first */
  field = GST_RTSP_HDR_ALLOW;
  while (TRUE) {
    respoptions = NULL;
    gst_rtsp_message_get_header (response, field, &respoptions, indx);
    if (indx == 0 && !respoptions) {
      /* if no Allow header was found then try the Public header... */
      field = GST_RTSP_HDR_PUBLIC;
      gst_rtsp_message_get_header (response, field, &respoptions, indx);
    }
    if (!respoptions)
      break;

    sink->methods |= gst_rtsp_options_from_text (respoptions);

    indx++;
  }

  if (sink->methods == 0) {
    /* neither Allow nor Public are required, assume the server supports
     * at least SETUP. */
    GST_DEBUG_OBJECT (sink, "could not get OPTIONS");
    sink->methods = GST_RTSP_SETUP;
  }

  /* Even if the server replied, and didn't say it supports
   * RECORD|ANNOUNCE, try anyway by assuming it does */
  sink->methods |= GST_RTSP_ANNOUNCE | GST_RTSP_RECORD;

  if (!(sink->methods & GST_RTSP_SETUP))
    goto no_setup;

  return TRUE;

  /* ERRORS */
no_setup:
  {
    GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ, (NULL),
        ("Server does not support SETUP."));
    return FALSE;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_connect_to_server (GstRTSPClientSink * sink,
    gboolean async)
{
  GstRTSPResult res;
  GstRTSPMessage request = { 0 };
  GstRTSPMessage response = { 0 };
  GSocket *conn_socket;
  GSocketAddress *sa;
  GInetAddress *ia;

  sink->need_redirect = FALSE;

  /* can't continue without a valid url */
  if (G_UNLIKELY (sink->conninfo.url == NULL)) {
    res = GST_RTSP_EINVAL;
    goto no_url;
  }
  sink->tried_url_auth = FALSE;

  if ((res = gst_rtsp_conninfo_connect (sink, &sink->conninfo, async)) < 0)
    goto connect_failed;

  conn_socket = gst_rtsp_connection_get_read_socket (sink->conninfo.connection);
  sa = g_socket_get_remote_address (conn_socket, NULL);
  ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));

  g_free (sink->server_ip);
  sink->server_ip = g_inet_address_to_string (ia);

  g_object_unref (sa);

  /* create OPTIONS */
  GST_DEBUG_OBJECT (sink, "create options...");
  res =
      gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_OPTIONS,
      sink->conninfo.url_str);
  if (res < 0)
    goto create_request_failed;

  /* send OPTIONS */
  GST_DEBUG_OBJECT (sink, "send options...");

  if (async)
    GST_ELEMENT_PROGRESS (sink, CONTINUE, "open",
        ("Retrieving server options"));

  if ((res =
          gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
              &response, NULL)) < 0)
    goto send_error;

  /* parse OPTIONS */
  if (!gst_rtsp_client_sink_parse_methods (sink, &response))
    goto methods_error;

  /* FIXME: Do we need to handle REDIRECT responses for OPTIONS? */

  /* clean up any messages */
  gst_rtsp_message_unset (&request);
  gst_rtsp_message_unset (&response);

  return res;

  /* ERRORS */
no_url:
  {
    GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL),
        ("No valid RTSP URL was provided"));
    goto cleanup_error;
  }
connect_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ_WRITE, (NULL),
          ("Failed to connect. (%s)", str));
    } else {
      GST_WARNING_OBJECT (sink, "connect interrupted");
    }
    g_free (str);
    goto cleanup_error;
  }
create_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
        ("Could not create request. (%s)", str));
    g_free (str);
    goto cleanup_error;
  }
send_error:
  {
    /* Don't post a message - the rtsp_send method will have
     * taken care of it because we passed NULL for the response code */
    goto cleanup_error;
  }
methods_error:
  {
    /* error was posted */
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
cleanup_error:
  {
    if (sink->conninfo.connection) {
      GST_DEBUG_OBJECT (sink, "free connection");
      gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
    }
    gst_rtsp_message_unset (&request);
    gst_rtsp_message_unset (&response);
    return res;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_open (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPResult ret;

  sink->methods =
      GST_RTSP_SETUP | GST_RTSP_RECORD | GST_RTSP_PAUSE | GST_RTSP_TEARDOWN;

  g_mutex_lock (&sink->open_conn_lock);
  sink->open_conn_start = TRUE;
  g_cond_broadcast (&sink->open_conn_cond);
  GST_DEBUG_OBJECT (sink, "connection to server started");
  g_mutex_unlock (&sink->open_conn_lock);

  if ((ret = gst_rtsp_client_sink_connect_to_server (sink, async)) < 0)
    goto open_failed;

  if (async)
    gst_rtsp_client_sink_loop_end_cmd (sink, CMD_OPEN, ret);

  return ret;

  /* ERRORS */
open_failed:
  {
    GST_WARNING_OBJECT (sink, "Failed to connect to server");
    sink->open_error = TRUE;
    if (async)
      gst_rtsp_client_sink_loop_end_cmd (sink, CMD_OPEN, ret);
    return ret;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_close (GstRTSPClientSink * sink, gboolean async,
    gboolean only_close)
{
  GstRTSPMessage request = { 0 };
  GstRTSPMessage response = { 0 };
  GstRTSPResult res = GST_RTSP_OK;
  GList *walk;
  const gchar *control;

  GST_DEBUG_OBJECT (sink, "TEARDOWN...");

  gst_rtsp_client_sink_set_state (sink, GST_STATE_NULL);

  if (sink->state < GST_RTSP_STATE_READY) {
    GST_DEBUG_OBJECT (sink, "not ready, doing cleanup");
    goto close;
  }

  if (only_close)
    goto close;

  /* construct a control url */
  control = get_aggregate_control (sink);

  if (!(sink->methods & (GST_RTSP_RECORD | GST_RTSP_TEARDOWN)))
    goto not_supported;

  /* stop streaming */
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;

    if (context->stream_transport) {
      gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
      gst_object_unref (context->stream_transport);
      context->stream_transport = NULL;
    }

    if (context->joined) {
      gst_rtsp_stream_leave_bin (context->stream, GST_BIN (sink->internal_bin),
          sink->rtpbin);
      context->joined = FALSE;
    }
  }

  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
    const gchar *setup_url;
    GstRTSPConnInfo *info;

    GST_DEBUG_OBJECT (sink, "Looking at stream %p for teardown",
        context->stream);

    /* try aggregate control first but do non-aggregate control otherwise */
    if (control)
      setup_url = control;
    else if ((setup_url = context->conninfo.location) == NULL) {
      GST_DEBUG_OBJECT (sink, "Skipping TEARDOWN stream %p - no setup URL",
          context->stream);
      continue;
    }

    if (sink->conninfo.connection) {
      info = &sink->conninfo;
    } else if (context->conninfo.connection) {
      info = &context->conninfo;
    } else {
      continue;
    }
    if (!info->connected)
      goto next;

    /* do TEARDOWN */
    GST_DEBUG_OBJECT (sink, "Sending teardown for stream %p at URL %s",
        context->stream, setup_url);
    res =
        gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_TEARDOWN,
        setup_url);
    if (res < 0)
      goto create_request_failed;

    if (async)
      GST_ELEMENT_PROGRESS (sink, CONTINUE, "close", ("Closing stream"));

    if ((res =
            gst_rtsp_client_sink_send (sink, info, &request,
                &response, NULL)) < 0)
      goto send_error;

    /* FIXME, parse result? */
    gst_rtsp_message_unset (&request);
    gst_rtsp_message_unset (&response);

  next:
    /* early exit when we did aggregate control */
    if (control)
      break;
  }

close:
  /* close connections */
  GST_DEBUG_OBJECT (sink, "closing connection...");
  gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
    gst_rtsp_conninfo_close (sink, &stream->conninfo, TRUE);
  }

  /* cleanup */
  gst_rtsp_client_sink_cleanup (sink);

  sink->state = GST_RTSP_STATE_INVALID;

  if (async)
    gst_rtsp_client_sink_loop_end_cmd (sink, CMD_CLOSE, res);

  return res;

  /* ERRORS */
create_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
        ("Could not create request. (%s)", str));
    g_free (str);
    goto close;
  }
send_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    gst_rtsp_message_unset (&request);
    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
          ("Could not send message. (%s)", str));
    } else {
      GST_WARNING_OBJECT (sink, "TEARDOWN interrupted");
    }
    g_free (str);
    goto close;
  }
not_supported:
  {
    GST_DEBUG_OBJECT (sink,
        "TEARDOWN and PLAY not supported, can't do TEARDOWN");
    goto close;
  }
}

static gboolean
gst_rtsp_client_sink_configure_manager (GstRTSPClientSink * sink)
{
  GstElement *rtpbin;
  GstStateChangeReturn ret;

  rtpbin = sink->rtpbin;

  if (rtpbin == NULL) {
    GObjectClass *klass;

    rtpbin = gst_element_factory_make ("rtpbin", NULL);
    if (rtpbin == NULL)
      goto no_rtpbin;

    gst_bin_add (GST_BIN_CAST (sink->internal_bin), rtpbin);

    sink->rtpbin = rtpbin;

    /* Any more settings we should configure on rtpbin here? */
    g_object_set (sink->rtpbin, "latency", sink->latency, NULL);

    klass = G_OBJECT_GET_CLASS (G_OBJECT (rtpbin));

    if (g_object_class_find_property (klass, "ntp-time-source")) {
      g_object_set (sink->rtpbin, "ntp-time-source", sink->ntp_time_source,
          NULL);
    }

    if (sink->sdes && g_object_class_find_property (klass, "sdes")) {
      g_object_set (sink->rtpbin, "sdes", sink->sdes, NULL);
    }

    g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_NEW_MANAGER], 0,
        sink->rtpbin);
  }

  ret = gst_element_set_state (rtpbin, GST_STATE_PAUSED);
  if (ret == GST_STATE_CHANGE_FAILURE)
    goto start_manager_failure;

  return TRUE;

no_rtpbin:
  {
    GST_WARNING ("no rtpbin element");
    g_warning ("failed to create element 'rtpbin', check your installation");
    return FALSE;
  }
start_manager_failure:
  {
    GST_DEBUG_OBJECT (sink, "could not start session manager");
    gst_bin_remove (GST_BIN_CAST (sink->internal_bin), rtpbin);
    return FALSE;
  }
}

static GstElement *
request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPClientSink * sink)
{
  GstRTSPStream *stream = NULL;
  GstElement *ret = NULL;
  GList *walk;

  GST_RTSP_STATE_LOCK (sink);
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;

    if (sessid == gst_rtsp_stream_get_index (context->stream)) {
      stream = context->stream;
      break;
    }
  }

  if (stream != NULL) {
    GST_DEBUG_OBJECT (sink, "Creating aux sender for stream %u", sessid);
    ret = gst_rtsp_stream_request_aux_sender (stream, sessid);
  }

  GST_RTSP_STATE_UNLOCK (sink);

  return ret;
}

static GstElement *
request_fec_encoder (GstElement * rtpbin, guint sessid,
    GstRTSPClientSink * sink)
{
  GstRTSPStream *stream = NULL;
  GstElement *ret = NULL;
  GList *walk;

  GST_RTSP_STATE_LOCK (sink);
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;

    if (sessid == gst_rtsp_stream_get_index (context->stream)) {
      stream = context->stream;
      break;
    }
  }

  if (stream != NULL) {
    ret = gst_rtsp_stream_request_ulpfec_encoder (stream, sessid);
  }

  GST_RTSP_STATE_UNLOCK (sink);

  return ret;
}

static gboolean
gst_rtsp_client_sink_is_stopping (GstRTSPClientSink * sink)
{
  gboolean is_stopping;

  GST_OBJECT_LOCK (sink);
  is_stopping = sink->task == NULL;
  GST_OBJECT_UNLOCK (sink);

  return is_stopping;
}

static gboolean
gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink)
{
  GstRTSPStreamContext *context;
  GList *walk;
  const gchar *base;
  gchar *stream_path;
  GstUri *base_uri, *uri;

  GST_DEBUG_OBJECT (sink, "Collecting stream information");

  if (!gst_rtsp_client_sink_configure_manager (sink))
    return FALSE;

  base = get_aggregate_control (sink);

  base_uri = gst_uri_from_string (base);
  if (!base_uri) {
    GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL),
        ("Could not parse uri %s", base));
    return FALSE;
  }

  g_mutex_lock (&sink->preroll_lock);
  while (sink->contexts == NULL && !sink->conninfo.flushing) {
    g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
  }
  g_mutex_unlock (&sink->preroll_lock);

  /* FIXME: Need different locking - need to protect against pad releases
   * and potential state changes ruining things here */
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstPad *srcpad;

    context = (GstRTSPStreamContext *) walk->data;
    if (context->stream)
      continue;

    g_mutex_lock (&sink->preroll_lock);
    while (!context->prerolled && !sink->conninfo.flushing
        && !gst_rtsp_client_sink_is_stopping (sink)) {
      GST_DEBUG_OBJECT (sink, "Waiting for caps on stream %d", context->index);
      g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
    }
    if (sink->conninfo.flushing) {
      g_mutex_unlock (&sink->preroll_lock);
      break;
    }
    g_mutex_unlock (&sink->preroll_lock);

    if (context->payloader == NULL)
      continue;

    srcpad = gst_element_get_static_pad (context->payloader, "src");

    GST_DEBUG_OBJECT (sink, "Creating stream object for stream %d",
        context->index);
    context->stream =
        gst_rtsp_client_sink_create_stream (sink, context, context->payloader,
        srcpad);

    /* append stream index to uri path */
    g_free (context->conninfo.location);

    stream_path = g_strdup_printf ("stream=%d", context->index);
    uri = gst_uri_copy (base_uri);
    gst_uri_append_path (uri, stream_path);

    context->conninfo.location = gst_uri_to_string (uri);
    gst_uri_unref (uri);
    g_free (stream_path);

    if (sink->rtx_time > 0) {
      /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
      g_signal_connect (sink->rtpbin, "request-aux-sender",
          (GCallback) request_aux_sender, sink);
    }

    g_signal_connect (sink->rtpbin, "request-fec-encoder",
        (GCallback) request_fec_encoder, sink);

    if (!gst_rtsp_stream_join_bin (context->stream,
            GST_BIN (sink->internal_bin), sink->rtpbin, GST_STATE_PAUSED)) {
      goto join_bin_failed;
    }
    context->joined = TRUE;

    /* Block the stream, as it does not have any transport parts yet */
    gst_rtsp_stream_set_blocked (context->stream, TRUE);

    /* Let the stream object receive data */
    gst_pad_remove_probe (srcpad, context->payloader_block_id);

    gst_object_unref (srcpad);
  }

  /* Now wait for the preroll of the rtp bin */
  g_mutex_lock (&sink->preroll_lock);
  while (!sink->prerolled && sink->conninfo.connection
      && !sink->conninfo.flushing) {
    GST_LOG_OBJECT (sink, "Waiting for preroll before continuing");
    g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
  }
  GST_LOG_OBJECT (sink, "Marking streams as collected");
  sink->streams_collected = TRUE;
  g_mutex_unlock (&sink->preroll_lock);

  gst_uri_unref (base_uri);
  return TRUE;

join_bin_failed:

  gst_uri_unref (base_uri);
  GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
      ("Could not start stream %d", context->index));
  return FALSE;
}

static GstRTSPResult
gst_rtsp_client_sink_create_transports_string (GstRTSPClientSink * sink,
    GstRTSPStreamContext * context, GSocketFamily family,
    GstRTSPLowerTrans protocols, GstRTSPProfile profiles, gchar ** transports)
{
  GString *result;
  GstRTSPStream *stream = context->stream;
  gboolean first = TRUE;

  /* the default RTSP transports */
  result = g_string_new ("RTP");

  while (profiles != 0) {
    if (!first)
      g_string_append (result, ",RTP");

    if (profiles & GST_RTSP_PROFILE_SAVPF) {
      g_string_append (result, "/SAVPF");
      profiles &= ~GST_RTSP_PROFILE_SAVPF;
    } else if (profiles & GST_RTSP_PROFILE_SAVP) {
      g_string_append (result, "/SAVP");
      profiles &= ~GST_RTSP_PROFILE_SAVP;
    } else if (profiles & GST_RTSP_PROFILE_AVPF) {
      g_string_append (result, "/AVPF");
      profiles &= ~GST_RTSP_PROFILE_AVPF;
    } else if (profiles & GST_RTSP_PROFILE_AVP) {
      g_string_append (result, "/AVP");
      profiles &= ~GST_RTSP_PROFILE_AVP;
    } else {
      GST_WARNING_OBJECT (sink, "Unimplemented profile(s) 0x%x", profiles);
      break;
    }

    if (protocols & GST_RTSP_LOWER_TRANS_UDP) {
      GstRTSPRange ports;

      GST_DEBUG_OBJECT (sink, "adding UDP unicast");
      gst_rtsp_stream_get_server_port (stream, &ports, family);

      g_string_append_printf (result, "/UDP;unicast;client_port=%d-%d",
          ports.min, ports.max);
    } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) {
      GstRTSPAddress *addr =
          gst_rtsp_stream_get_multicast_address (stream, family);
      if (addr) {
        GST_DEBUG_OBJECT (sink, "adding UDP multicast");
        g_string_append_printf (result, "/UDP;multicast;client_port=%d-%d",
            addr->port, addr->port + addr->n_ports - 1);
        gst_rtsp_address_free (addr);
      }
    } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) {
      GST_DEBUG_OBJECT (sink, "adding TCP");
      g_string_append_printf (result, "/TCP;unicast;interleaved=%d-%d",
          sink->free_channel, sink->free_channel + 1);
    }

    g_string_append (result, ";mode=RECORD");
    /* FIXME: Support appending too:
       if (sink->append)
       g_string_append (result, ";append");
     */

    first = FALSE;
  }

  if (first) {
    /* No valid transport could be constructed */
    GST_ERROR_OBJECT (sink, "No supported profiles configured");
    goto fail;
  }

  *transports = g_string_free (result, FALSE);

  GST_DEBUG_OBJECT (sink, "prepared transports %s", GST_STR_NULL (*transports));

  return GST_RTSP_OK;
fail:
  g_string_free (result, TRUE);
  return GST_RTSP_ERROR;
}

static GstCaps *
signal_get_srtcp_params (GstRTSPClientSink * sink,
    GstRTSPStreamContext * context)
{
  GstCaps *caps = NULL;

  g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_REQUEST_RTCP_KEY], 0,
      context->index, &caps);

  if (caps != NULL)
    GST_DEBUG_OBJECT (sink, "SRTP parameters received");

  return caps;
}

static gchar *
gst_rtsp_client_sink_stream_make_keymgmt (GstRTSPClientSink * sink,
    GstRTSPStreamContext * context)
{
  gchar *base64, *result = NULL;
  GstMIKEYMessage *mikey_msg;

  context->srtcpparams = signal_get_srtcp_params (sink, context);
  if (context->srtcpparams == NULL)
    context->srtcpparams = gst_rtsp_stream_get_caps (context->stream);

  mikey_msg = gst_mikey_message_new_from_caps (context->srtcpparams);
  if (mikey_msg) {
    guint send_ssrc, send_rtx_ssrc;
    const GstStructure *s = gst_caps_get_structure (context->srtcpparams, 0);

    /* add policy '0' for our SSRC */
    gst_rtsp_stream_get_ssrc (context->stream, &send_ssrc);
    GST_LOG_OBJECT (sink, "Stream %p ssrc %x", context->stream, send_ssrc);
    gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_ssrc, 0);

    if (gst_structure_get_uint (s, "rtx-ssrc", &send_rtx_ssrc))
      gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_rtx_ssrc, 0);

    base64 = gst_mikey_message_base64_encode (mikey_msg);
    gst_mikey_message_unref (mikey_msg);

    if (base64) {
      result = gst_sdp_make_keymgmt (context->conninfo.location, base64);
      g_free (base64);
    }
  }

  return result;
}

/* masks to be kept in sync with the hardcoded protocol order of preference
 * in code below */
static const guint protocol_masks[] = {
  GST_RTSP_LOWER_TRANS_UDP,
  GST_RTSP_LOWER_TRANS_UDP_MCAST,
  GST_RTSP_LOWER_TRANS_TCP,
  0
};

/* Same for profile_masks */
static const guint profile_masks[] = {
  GST_RTSP_PROFILE_SAVPF,
  GST_RTSP_PROFILE_SAVP,
  GST_RTSP_PROFILE_AVPF,
  GST_RTSP_PROFILE_AVP,
  0
};

static gboolean
do_send_data (GstBuffer * buffer, guint8 channel,
    GstRTSPStreamContext * context)
{
  GstRTSPClientSink *sink = context->parent;
  GstRTSPMessage message = { 0 };
  GstRTSPResult res = GST_RTSP_OK;

  gst_rtsp_message_init_data (&message, channel);

  gst_rtsp_message_set_body_buffer (&message, buffer);

  res =
      gst_rtsp_client_sink_try_send (sink, &sink->conninfo, &message, 1,
      NULL, NULL);

  gst_rtsp_message_unset (&message);

  gst_rtsp_stream_transport_message_sent (context->stream_transport);

  return res == GST_RTSP_OK;
}

static gboolean
do_send_data_list (GstBufferList * buffer_list, guint8 channel,
    GstRTSPStreamContext * context)
{
  GstRTSPClientSink *sink = context->parent;
  GstRTSPResult res = GST_RTSP_OK;
  guint i, n = gst_buffer_list_length (buffer_list);
  GstRTSPMessage *messages = g_newa (GstRTSPMessage, n);

  memset (messages, 0, n * sizeof (GstRTSPMessage));

  for (i = 0; i < n; i++) {
    GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);

    gst_rtsp_message_init_data (&messages[i], channel);

    gst_rtsp_message_set_body_buffer (&messages[i], buffer);
  }

  res =
      gst_rtsp_client_sink_try_send (sink, &sink->conninfo, messages, n,
      NULL, NULL);

  for (i = 0; i < n; i++) {
    gst_rtsp_message_unset (&messages[i]);
    gst_rtsp_stream_transport_message_sent (context->stream_transport);
  }

  return res == GST_RTSP_OK;
}

static void
transport_timed_out_notify_cb (GstRTSPStreamTransport * transport,
    GParamSpec * unused, GstRTSPClientSink * sink)
{
  gboolean timed_out;

  g_object_get (G_OBJECT (transport), "timed-out", &timed_out, NULL);

  GST_DEBUG_OBJECT (sink, "Transport %p timed out notify: %d", transport,
      timed_out);
}

static GstRTSPResult
gst_rtsp_client_sink_setup_streams (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPResult res = GST_RTSP_ERROR;
  GstRTSPMessage request = { 0 };
  GstRTSPMessage response = { 0 };
  GstRTSPLowerTrans protocols;
  GstRTSPStatusCode code;
  GSocketFamily family;
  GSocketAddress *sa;
  GSocket *conn_socket;
  GstRTSPUrl *url;
  GList *walk;
  gchar *hval;

  if (sink->conninfo.connection) {
    url = gst_rtsp_connection_get_url (sink->conninfo.connection);
    /* we initially allow all configured lower transports. based on the URL
     * transports and the replies from the server we narrow them down. */
    protocols = url->transports & sink->cur_protocols;
  } else {
    url = NULL;
    protocols = sink->cur_protocols;
  }

  if (protocols == 0)
    goto no_protocols;

  GST_RTSP_STATE_LOCK (sink);

  if (G_UNLIKELY (sink->contexts == NULL))
    goto no_streams;

  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
    GstRTSPStream *stream;

    GstRTSPConnInfo *info;
    GstRTSPProfile profiles;
    GstRTSPProfile cur_profile;
    gchar *transports;
    gint retry = 0;
    guint profile_mask = 0;
    guint mask = 0;
    GstCaps *caps;
    const GstSDPMedia *media;

    stream = context->stream;
    profiles = gst_rtsp_stream_get_profiles (stream);

    caps = gst_rtsp_stream_get_caps (stream);
    if (caps == NULL) {
      GST_DEBUG_OBJECT (sink, "skipping stream %p, no caps", stream);
      continue;
    }
    gst_caps_unref (caps);
    media = gst_sdp_message_get_media (&sink->cursdp, context->sdp_index);
    if (media == NULL) {
      GST_DEBUG_OBJECT (sink, "skipping stream %p, no SDP info", stream);
      continue;
    }

    /* skip setup if we have no URL for it */
    if (context->conninfo.location == NULL) {
      GST_DEBUG_OBJECT (sink, "skipping stream %p, no setup", stream);
      continue;
    }

    if (sink->conninfo.connection == NULL) {
      if (!gst_rtsp_conninfo_connect (sink, &context->conninfo, async)) {
        GST_DEBUG_OBJECT (sink, "skipping stream %p, failed to connect",
            stream);
        continue;
      }
      info = &context->conninfo;
    } else {
      info = &sink->conninfo;
    }
    GST_DEBUG_OBJECT (sink, "doing setup of stream %p with %s", stream,
        context->conninfo.location);

    conn_socket = gst_rtsp_connection_get_read_socket (info->connection);
    sa = g_socket_get_local_address (conn_socket, NULL);
    family = g_socket_address_get_family (sa);
    g_object_unref (sa);

  next_protocol:
    /* first selectable profile */
    while (profile_masks[profile_mask]
        && !(profiles & profile_masks[profile_mask]))
      profile_mask++;
    if (!profile_masks[profile_mask])
      goto no_profiles;

    /* first selectable protocol */
    while (protocol_masks[mask] && !(protocols & protocol_masks[mask]))
      mask++;
    if (!protocol_masks[mask])
      goto no_protocols;

  retry:
    GST_DEBUG_OBJECT (sink, "protocols = 0x%x, protocol mask = 0x%x", protocols,
        protocol_masks[mask]);
    /* create a string with first transport in line */
    transports = NULL;
    cur_profile = profiles & profile_masks[profile_mask];
    res = gst_rtsp_client_sink_create_transports_string (sink, context, family,
        protocols & protocol_masks[mask], cur_profile, &transports);
    if (res < 0 || transports == NULL)
      goto setup_transport_failed;

    if (strlen (transports) == 0) {
      g_free (transports);
      GST_DEBUG_OBJECT (sink, "no transports found");
      mask++;
      profile_mask = 0;
      goto next_protocol;
    }

    GST_DEBUG_OBJECT (sink, "transport is %s", GST_STR_NULL (transports));

    /* create SETUP request */
    res =
        gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_SETUP,
        context->conninfo.location);
    if (res < 0) {
      g_free (transports);
      goto create_request_failed;
    }

    /* set up keys */
    if (cur_profile == GST_RTSP_PROFILE_SAVP ||
        cur_profile == GST_RTSP_PROFILE_SAVPF) {
      hval = gst_rtsp_client_sink_stream_make_keymgmt (sink, context);
      gst_rtsp_message_take_header (&request, GST_RTSP_HDR_KEYMGMT, hval);
    }

    /* if the user wants a non default RTP packet size we add the blocksize
     * parameter */
    if (sink->rtp_blocksize > 0) {
      hval = g_strdup_printf ("%d", sink->rtp_blocksize);
      gst_rtsp_message_take_header (&request, GST_RTSP_HDR_BLOCKSIZE, hval);
    }

    if (async)
      GST_ELEMENT_PROGRESS (sink, CONTINUE, "request", ("SETUP stream %d",
              context->index));

    {
      GstRTSPTransport *transport;

      gst_rtsp_transport_new (&transport);
      if (gst_rtsp_transport_parse (transports, transport) != GST_RTSP_OK)
        goto parse_transport_failed;
      if (transport->lower_transport != GST_RTSP_LOWER_TRANS_TCP) {
        if (!gst_rtsp_stream_allocate_udp_sockets (stream, family, transport,
                FALSE)) {
          gst_rtsp_transport_free (transport);
          goto allocate_udp_ports_failed;
        }
      }
      if (!gst_rtsp_stream_complete_stream (stream, transport)) {
        gst_rtsp_transport_free (transport);
        goto complete_stream_failed;
      }

      gst_rtsp_transport_free (transport);
      gst_rtsp_stream_set_blocked (stream, FALSE);
    }

    /* FIXME:
     * the creation of the transports string depends on
     * calling stream_get_server_port, which only starts returning
     * something meaningful after a call to stream_allocate_udp_sockets
     * has been made, this function expects a transport that we parse
     * from the transport string ...
     *
     * Significant refactoring is in order, but does not look entirely
     * trivial, for now we put a band aid on and create a second transport
     * string after the stream has been completed, to pass it in
     * the request headers instead of the previous, incomplete one.
     */
    g_free (transports);
    transports = NULL;
    res = gst_rtsp_client_sink_create_transports_string (sink, context, family,
        protocols & protocol_masks[mask], cur_profile, &transports);

    if (res < 0 || transports == NULL)
      goto setup_transport_failed;

    /* select transport */
    gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports);

    /* handle the code ourselves */
    res = gst_rtsp_client_sink_send (sink, info, &request, &response, &code);
    if (res < 0)
      goto send_error;

    switch (code) {
      case GST_RTSP_STS_OK:
        break;
      case GST_RTSP_STS_UNSUPPORTED_TRANSPORT:
        gst_rtsp_message_unset (&request);
        gst_rtsp_message_unset (&response);

        /* Try another profile. If no more, move to the next protocol */
        profile_mask++;
        while (profile_masks[profile_mask]
            && !(profiles & profile_masks[profile_mask]))
          profile_mask++;
        if (profile_masks[profile_mask])
          goto retry;

        /* select next available protocol, give up on this stream if none */
        /* Reset profiles to try: */
        profile_mask = 0;

        mask++;
        while (protocol_masks[mask] && !(protocols & protocol_masks[mask]))
          mask++;
        if (!protocol_masks[mask])
          continue;
        else
          goto retry;
      default:
        goto response_error;
    }

    /* parse response transport */
    {
      gchar *resptrans = NULL;
      GstRTSPTransport *transport;

      gst_rtsp_message_get_header (&response, GST_RTSP_HDR_TRANSPORT,
          &resptrans, 0);
      if (!resptrans) {
        goto no_transport;
      }

      gst_rtsp_transport_new (&transport);

      /* parse transport, go to next stream on parse error */
      if (gst_rtsp_transport_parse (resptrans, transport) != GST_RTSP_OK) {
        GST_WARNING_OBJECT (sink, "failed to parse transport %s", resptrans);
        goto next;
      }

      /* update allowed transports for other streams. once the transport of
       * one stream has been determined, we make sure that all other streams
       * are configured in the same way */
      switch (transport->lower_transport) {
        case GST_RTSP_LOWER_TRANS_TCP:
          GST_DEBUG_OBJECT (sink, "stream %p as TCP interleaved", stream);
          protocols = GST_RTSP_LOWER_TRANS_TCP;
          sink->interleaved = TRUE;
          /* update free channels */
          sink->free_channel =
              MAX (transport->interleaved.min, sink->free_channel);
          sink->free_channel =
              MAX (transport->interleaved.max, sink->free_channel);
          sink->free_channel++;
          break;
        case GST_RTSP_LOWER_TRANS_UDP_MCAST:
          /* only allow multicast for other streams */
          GST_DEBUG_OBJECT (sink, "stream %p as UDP multicast", stream);
          protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST;
          break;
        case GST_RTSP_LOWER_TRANS_UDP:
          /* only allow unicast for other streams */
          GST_DEBUG_OBJECT (sink, "stream %p as UDP unicast", stream);
          protocols = GST_RTSP_LOWER_TRANS_UDP;
          /* Update transport with server destination if not provided by the server */
          if (transport->destination == NULL) {
            transport->destination = g_strdup (sink->server_ip);
          }
          break;
        default:
          GST_DEBUG_OBJECT (sink, "stream %p unknown transport %d", stream,
              transport->lower_transport);
          break;
      }

      if (!retry) {
        GST_DEBUG ("Configuring the stream transport for stream %d",
            context->index);
        if (context->stream_transport == NULL) {
          context->stream_transport =
              gst_rtsp_stream_transport_new (stream, transport);

          g_signal_connect (context->stream_transport, "notify::timed-out",
              (GCallback) transport_timed_out_notify_cb, sink);
        } else
          gst_rtsp_stream_transport_set_transport (context->stream_transport,
              transport);

        if (transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
          /* our callbacks to send data on this TCP connection */
          gst_rtsp_stream_transport_set_callbacks (context->stream_transport,
              (GstRTSPSendFunc) do_send_data,
              (GstRTSPSendFunc) do_send_data, context, NULL);
          gst_rtsp_stream_transport_set_list_callbacks
              (context->stream_transport,
              (GstRTSPSendListFunc) do_send_data_list,
              (GstRTSPSendListFunc) do_send_data_list, context, NULL);
        }

        /* The stream_transport now owns the transport */
        transport = NULL;

        gst_rtsp_stream_transport_set_active (context->stream_transport, TRUE);
      }
    next:
      if (transport)
        gst_rtsp_transport_free (transport);
      /* clean up used RTSP messages */
      gst_rtsp_message_unset (&request);
      gst_rtsp_message_unset (&response);
    }
  }
  GST_RTSP_STATE_UNLOCK (sink);

  /* store the transport protocol that was configured */
  sink->cur_protocols = protocols;

  return res;

no_streams:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("SDP contains no streams"));
    return GST_RTSP_ERROR;
  }
setup_transport_failed:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("Could not setup transport."));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
no_profiles:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    /* no transport possible, post an error and stop */
    GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
        ("Could not connect to server, no profiles left"));
    return GST_RTSP_ERROR;
  }
no_protocols:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    /* no transport possible, post an error and stop */
    GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
        ("Could not connect to server, no protocols left"));
    return GST_RTSP_ERROR;
  }
no_transport:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("Server did not select transport."));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
create_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
        ("Could not create request. (%s)", str));
    g_free (str);
    goto cleanup_error;
  }
parse_transport_failed:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("Could not parse transport."));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
allocate_udp_ports_failed:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("Could not parse transport."));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
complete_stream_failed:
  {
    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
        ("Could not parse transport."));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
send_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_RTSP_STATE_UNLOCK (sink);
    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
          ("Could not send message. (%s)", str));
    } else {
      GST_WARNING_OBJECT (sink, "send interrupted");
    }
    g_free (str);
    goto cleanup_error;
  }
response_error:
  {
    const gchar *str = gst_rtsp_status_as_text (code);

    GST_RTSP_STATE_UNLOCK (sink);
    GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
        ("Error (%d): %s", code, GST_STR_NULL (str)));
    res = GST_RTSP_ERROR;
    goto cleanup_error;
  }
cleanup_error:
  {
    gst_rtsp_message_unset (&request);
    gst_rtsp_message_unset (&response);
    return res;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_ensure_open (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPResult res = GST_RTSP_OK;

  if (sink->state < GST_RTSP_STATE_READY) {
    res = GST_RTSP_ERROR;
    if (sink->open_error) {
      GST_DEBUG_OBJECT (sink, "the stream was in error");
      goto done;
    }
    if (async)
      gst_rtsp_client_sink_loop_start_cmd (sink, CMD_OPEN);

    if ((res = gst_rtsp_client_sink_open (sink, async)) < 0) {
      GST_DEBUG_OBJECT (sink, "failed to open stream");
      goto done;
    }
  }

done:
  return res;
}

static GstRTSPResult
gst_rtsp_client_sink_record (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPMessage request = { 0 };
  GstRTSPMessage response = { 0 };
  GstRTSPResult res = GST_RTSP_OK;
  GstSDPMessage *sdp;
  guint sdp_index = 0;
  GstSDPInfo info = { 0, };
  gchar *keymgmt;
  guint i;

  const gchar *proto;
  gchar *sess_id, *client_ip, *str;
  GSocketAddress *sa;
  GInetAddress *ia;
  GSocket *conn_socket;
  GList *walk;

  g_mutex_lock (&sink->preroll_lock);
  if (sink->state == GST_RTSP_STATE_PLAYING) {
    /* Already recording, don't send another request */
    GST_LOG_OBJECT (sink, "Already in RECORD. Skipping duplicate request.");
    g_mutex_unlock (&sink->preroll_lock);
    goto done;
  }
  g_mutex_unlock (&sink->preroll_lock);

  /* Collect all our input streams and create
   * stream objects before actually returning.
   * The streams are blocked at this point as we do not have any transport
   * parts yet. */
  gst_rtsp_client_sink_collect_streams (sink);

  if (gst_rtsp_client_sink_is_stopping (sink)) {
    GST_INFO_OBJECT (sink, "task stopped, shutting down");
    return GST_RTSP_EINTR;
  }

  g_mutex_lock (&sink->block_streams_lock);
  /* Wait for streams to be blocked */
  while (sink->n_streams_blocked < g_list_length (sink->contexts)
      && !gst_rtsp_client_sink_is_stopping (sink)) {
    GST_DEBUG_OBJECT (sink, "waiting for streams to be blocked");
    g_cond_wait (&sink->block_streams_cond, &sink->block_streams_lock);
  }
  g_mutex_unlock (&sink->block_streams_lock);

  if (gst_rtsp_client_sink_is_stopping (sink)) {
    GST_INFO_OBJECT (sink, "task stopped, shutting down");
    return GST_RTSP_EINTR;
  }

  /* Send announce, then setup for all streams */
  gst_sdp_message_init (&sink->cursdp);
  sdp = &sink->cursdp;

  /* some standard things first */
  gst_sdp_message_set_version (sdp, "0");

  /* session ID doesn't have to be super-unique in this case */
  sess_id = g_strdup_printf ("%u", g_random_int ());

  if (sink->conninfo.connection == NULL)
    return GST_RTSP_ERROR;

  conn_socket = gst_rtsp_connection_get_read_socket (sink->conninfo.connection);

  sa = g_socket_get_local_address (conn_socket, NULL);
  ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));
  client_ip = g_inet_address_to_string (ia);
  if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV6) {
    info.is_ipv6 = TRUE;
    proto = "IP6";
  } else if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV4)
    proto = "IP4";
  else
    g_assert_not_reached ();
  g_object_unref (sa);

  /* FIXME: Should this actually be the server's IP or ours? */
  info.server_ip = sink->server_ip;

  gst_sdp_message_set_origin (sdp, "-", sess_id, "1", "IN", proto, client_ip);

  gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
  gst_sdp_message_set_information (sdp, "rtspclientsink");
  gst_sdp_message_add_time (sdp, "0", "0", NULL);
  gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");

  /* add stream */
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;

    gst_rtsp_sdp_from_stream (sdp, &info, context->stream);
    context->sdp_index = sdp_index++;
  }

  g_free (sess_id);
  g_free (client_ip);

  /* send ANNOUNCE request */
  GST_DEBUG_OBJECT (sink, "create ANNOUNCE request...");
  res =
      gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_ANNOUNCE,
      sink->conninfo.url_str);
  if (res < 0)
    goto create_request_failed;

  g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_UPDATE_SDP], 0, sdp);

  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  /* add SDP to the request body */
  str = gst_sdp_message_as_text (sdp);
  gst_rtsp_message_take_body (&request, (guint8 *) str, strlen (str));

  /* send ANNOUNCE */
  GST_DEBUG_OBJECT (sink, "sending announce...");

  if (async)
    GST_ELEMENT_PROGRESS (sink, CONTINUE, "record",
        ("Sending server stream info"));

  if ((res =
          gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
              &response, NULL)) < 0)
    goto send_error;

  /* parse the keymgmt */
  i = 0;
  walk = sink->contexts;
  while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_KEYMGMT,
          &keymgmt, i++) == GST_RTSP_OK) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
    walk = g_list_next (walk);
    if (!gst_rtsp_stream_handle_keymgmt (context->stream, keymgmt))
      goto keymgmt_error;
  }

  /* send setup for all streams */
  if ((res = gst_rtsp_client_sink_setup_streams (sink, async)) < 0)
    goto setup_failed;

  res = gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_RECORD,
      sink->conninfo.url_str);

  if (res < 0)
    goto create_request_failed;

#if 0                           /* FIXME: Configure a range based on input segments? */
  if (src->need_range) {
    hval = gen_range_header (src, segment);

    gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval);
  }

  if (segment->rate != 1.0) {
    gchar hval[G_ASCII_DTOSTR_BUF_SIZE];

    g_ascii_dtostr (hval, sizeof (hval), segment->rate);
    if (src->skip)
      gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, hval);
    else
      gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval);
  }
#endif

  if (async)
    GST_ELEMENT_PROGRESS (sink, CONTINUE, "record", ("Starting recording"));
  if ((res =
          gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
              &response, NULL)) < 0)
    goto send_error;

#if 0                           /* FIXME: Check if servers return these for record: */
  /* parse the RTP-Info header field (if ANY) to get the base seqnum and timestamp
   * for the RTP packets. If this is not present, we assume all starts from 0...
   * This is info for the RTP session manager that we pass to it in caps. */
  hval_idx = 0;
  while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTP_INFO,
          &hval, hval_idx++) == GST_RTSP_OK)
    gst_rtspsrc_parse_rtpinfo (src, hval);

  /* some servers indicate RTCP parameters in PLAY response,
   * rather than properly in SDP */
  if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTCP_INTERVAL,
          &hval, 0) == GST_RTSP_OK)
    gst_rtspsrc_handle_rtcp_interval (src, hval);
#endif

  gst_rtsp_client_sink_set_state (sink, GST_STATE_PLAYING);
  sink->state = GST_RTSP_STATE_PLAYING;
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;

    gst_rtsp_stream_unblock_rtcp (context->stream);
  }

  /* clean up any messages */
  gst_rtsp_message_unset (&request);
  gst_rtsp_message_unset (&response);

done:
  return res;

create_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
        ("Could not create request. (%s)", str));
    g_free (str);
    goto cleanup_error;
  }
send_error:
  {
    /* Don't post a message - the rtsp_send method will have
     * taken care of it because we passed NULL for the response code */
    goto cleanup_error;
  }
keymgmt_error:
  {
    GST_ELEMENT_ERROR (sink, STREAM, DECRYPT_NOKEY, (NULL),
        ("Could not handle KeyMgmt"));
  }
setup_failed:
  {
    GST_ERROR_OBJECT (sink, "setup failed");
    goto cleanup_error;
  }
cleanup_error:
  {
    if (sink->conninfo.connection) {
      GST_DEBUG_OBJECT (sink, "free connection");
      gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
    }
    gst_rtsp_message_unset (&request);
    gst_rtsp_message_unset (&response);
    return res;
  }
}

static GstRTSPResult
gst_rtsp_client_sink_pause (GstRTSPClientSink * sink, gboolean async)
{
  GstRTSPResult res = GST_RTSP_OK;
  GstRTSPMessage request = { 0 };
  GstRTSPMessage response = { 0 };
  GList *walk;
  const gchar *control;

  GST_DEBUG_OBJECT (sink, "PAUSE...");

  if ((res = gst_rtsp_client_sink_ensure_open (sink, async)) < 0)
    goto open_failed;

  if (!(sink->methods & GST_RTSP_PAUSE))
    goto not_supported;

  if (sink->state == GST_RTSP_STATE_READY)
    goto was_paused;

  if (!sink->conninfo.connection || !sink->conninfo.connected)
    goto no_connection;

  /* construct a control url */
  control = get_aggregate_control (sink);

  /* loop over the streams. We might exit the loop early when we could do an
   * aggregate control */
  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
    GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
    GstRTSPConnInfo *info;
    const gchar *setup_url;

    /* try aggregate control first but do non-aggregate control otherwise */
    if (control)
      setup_url = control;
    else if ((setup_url = stream->conninfo.location) == NULL)
      continue;

    if (sink->conninfo.connection) {
      info = &sink->conninfo;
    } else if (stream->conninfo.connection) {
      info = &stream->conninfo;
    } else {
      continue;
    }

    if (async)
      GST_ELEMENT_PROGRESS (sink, CONTINUE, "request",
          ("Sending PAUSE request"));

    if ((res =
            gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_PAUSE,
                setup_url)) < 0)
      goto create_request_failed;

    if ((res =
            gst_rtsp_client_sink_send (sink, info, &request, &response,
                NULL)) < 0)
      goto send_error;

    gst_rtsp_message_unset (&request);
    gst_rtsp_message_unset (&response);

    /* exit early when we did agregate control */
    if (control)
      break;
  }

  /* change element states now */
  gst_rtsp_client_sink_set_state (sink, GST_STATE_PAUSED);

no_connection:
  sink->state = GST_RTSP_STATE_READY;

done:
  if (async)
    gst_rtsp_client_sink_loop_end_cmd (sink, CMD_PAUSE, res);

  return res;

  /* ERRORS */
open_failed:
  {
    GST_DEBUG_OBJECT (sink, "failed to open stream");
    goto done;
  }
not_supported:
  {
    GST_DEBUG_OBJECT (sink, "PAUSE is not supported");
    goto done;
  }
was_paused:
  {
    GST_DEBUG_OBJECT (sink, "we were already PAUSED");
    goto done;
  }
create_request_failed:
  {
    gchar *str = gst_rtsp_strresult (res);

    GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
        ("Could not create request. (%s)", str));
    g_free (str);
    goto done;
  }
send_error:
  {
    gchar *str = gst_rtsp_strresult (res);

    gst_rtsp_message_unset (&request);
    if (res != GST_RTSP_EINTR) {
      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
          ("Could not send message. (%s)", str));
    } else {
      GST_WARNING_OBJECT (sink, "PAUSE interrupted");
    }
    g_free (str);
    goto done;
  }
}

static void
gst_rtsp_client_sink_handle_message (GstBin * bin, GstMessage * message)
{
  GstRTSPClientSink *rtsp_client_sink;

  rtsp_client_sink = GST_RTSP_CLIENT_SINK (bin);

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ELEMENT:
    {
      const GstStructure *s = gst_message_get_structure (message);

      if (gst_structure_has_name (s, "GstUDPSrcTimeout")) {
        gboolean ignore_timeout;

        GST_DEBUG_OBJECT (bin, "timeout on UDP port");

        GST_OBJECT_LOCK (rtsp_client_sink);
        ignore_timeout = rtsp_client_sink->ignore_timeout;
        rtsp_client_sink->ignore_timeout = TRUE;
        GST_OBJECT_UNLOCK (rtsp_client_sink);

        /* we only act on the first udp timeout message, others are irrelevant
         * and can be ignored. */
        if (!ignore_timeout)
          gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_RECONNECT,
              CMD_LOOP);
        /* eat and free */
        gst_message_unref (message);
        return;
      } else if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
        /* An RTSPStream has prerolled */
        GST_DEBUG_OBJECT (rtsp_client_sink, "received GstRTSPStreamBlocking");
        g_mutex_lock (&rtsp_client_sink->block_streams_lock);
        rtsp_client_sink->n_streams_blocked++;
        g_cond_broadcast (&rtsp_client_sink->block_streams_cond);
        g_mutex_unlock (&rtsp_client_sink->block_streams_lock);
      }
      GST_BIN_CLASS (parent_class)->handle_message (bin, message);
      break;
    }
    case GST_MESSAGE_ASYNC_START:{
      GstObject *sender;

      sender = GST_MESSAGE_SRC (message);

      GST_LOG_OBJECT (rtsp_client_sink,
          "Have async-start from %" GST_PTR_FORMAT, sender);
      if (sender == GST_OBJECT (rtsp_client_sink->internal_bin)) {
        GST_LOG_OBJECT (rtsp_client_sink, "child bin is now ASYNC");
      }
      GST_BIN_CLASS (parent_class)->handle_message (bin, message);
      break;
    }
    case GST_MESSAGE_ASYNC_DONE:
    {
      GstObject *sender;
      gboolean need_async_done;

      sender = GST_MESSAGE_SRC (message);
      GST_LOG_OBJECT (rtsp_client_sink, "Have async-done from %" GST_PTR_FORMAT,
          sender);

      g_mutex_lock (&rtsp_client_sink->preroll_lock);
      if (sender == GST_OBJECT_CAST (rtsp_client_sink->internal_bin)) {
        GST_LOG_OBJECT (rtsp_client_sink, "child bin is no longer ASYNC");
      }
      need_async_done = rtsp_client_sink->in_async;
      if (rtsp_client_sink->in_async) {
        rtsp_client_sink->in_async = FALSE;
        g_cond_broadcast (&rtsp_client_sink->preroll_cond);
      }
      g_mutex_unlock (&rtsp_client_sink->preroll_lock);

      GST_BIN_CLASS (parent_class)->handle_message (bin, message);

      if (need_async_done) {
        GST_DEBUG_OBJECT (rtsp_client_sink, "Posting ASYNC-DONE");
        gst_element_post_message (GST_ELEMENT_CAST (rtsp_client_sink),
            gst_message_new_async_done (GST_OBJECT_CAST (rtsp_client_sink),
                GST_CLOCK_TIME_NONE));
      }
      break;
    }
    case GST_MESSAGE_ERROR:
    {
      GstObject *sender;

      sender = GST_MESSAGE_SRC (message);

      GST_DEBUG_OBJECT (rtsp_client_sink, "got error from %s",
          GST_ELEMENT_NAME (sender));

      /* FIXME: Ignore errors on RTCP? */
      /* fatal but not our message, forward */
      GST_BIN_CLASS (parent_class)->handle_message (bin, message);
      break;
    }
    case GST_MESSAGE_STATE_CHANGED:
    {
      if (GST_MESSAGE_SRC (message) ==
          (GstObject *) rtsp_client_sink->internal_bin) {
        GstState newstate, pending;
        gst_message_parse_state_changed (message, NULL, &newstate, &pending);
        g_mutex_lock (&rtsp_client_sink->preroll_lock);
        rtsp_client_sink->prerolled = (newstate >= GST_STATE_PAUSED)
            && pending == GST_STATE_VOID_PENDING;
        g_cond_broadcast (&rtsp_client_sink->preroll_cond);
        g_mutex_unlock (&rtsp_client_sink->preroll_lock);
        GST_DEBUG_OBJECT (bin,
            "Internal bin changed state to %s (pending %s). Prerolled now %d",
            gst_state_get_name (newstate),
            gst_state_get_name (pending), rtsp_client_sink->prerolled);
      }
    }
      /* FALLTHROUGH */
    default:
    {
      GST_BIN_CLASS (parent_class)->handle_message (bin, message);
      break;
    }
  }
}

/* the thread where everything happens */
static void
gst_rtsp_client_sink_thread (GstRTSPClientSink * sink)
{
  gint cmd;

  GST_OBJECT_LOCK (sink);
  cmd = sink->pending_cmd;
  if (cmd == CMD_RECONNECT || cmd == CMD_RECORD || cmd == CMD_PAUSE
      || cmd == CMD_LOOP || cmd == CMD_OPEN)
    sink->pending_cmd = CMD_LOOP;
  else
    sink->pending_cmd = CMD_WAIT;
  GST_DEBUG_OBJECT (sink, "got command %s", cmd_to_string (cmd));

  /* we got the message command, so ensure communication is possible again */
  gst_rtsp_client_sink_connection_flush (sink, FALSE);

  sink->busy_cmd = cmd;
  GST_OBJECT_UNLOCK (sink);

  switch (cmd) {
    case CMD_OPEN:
      if (gst_rtsp_client_sink_open (sink, TRUE) == GST_RTSP_ERROR)
        gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT,
            CMD_ALL & ~CMD_CLOSE);
      break;
    case CMD_RECORD:
      gst_rtsp_client_sink_record (sink, TRUE);
      break;
    case CMD_PAUSE:
      gst_rtsp_client_sink_pause (sink, TRUE);
      break;
    case CMD_CLOSE:
      gst_rtsp_client_sink_close (sink, TRUE, FALSE);
      break;
    case CMD_LOOP:
      gst_rtsp_client_sink_loop (sink);
      break;
    case CMD_RECONNECT:
      gst_rtsp_client_sink_reconnect (sink, FALSE);
      break;
    default:
      break;
  }

  GST_OBJECT_LOCK (sink);
  /* and go back to sleep */
  if (sink->pending_cmd == CMD_WAIT) {
    if (sink->task)
      gst_task_pause (sink->task);
  }
  /* reset waiting */
  sink->busy_cmd = CMD_WAIT;
  GST_OBJECT_UNLOCK (sink);
}

static gboolean
gst_rtsp_client_sink_start (GstRTSPClientSink * sink)
{
  GST_DEBUG_OBJECT (sink, "starting");

  sink->streams_collected = FALSE;
  gst_element_set_locked_state (GST_ELEMENT (sink->internal_bin), TRUE);

  gst_rtsp_client_sink_set_state (sink, GST_STATE_READY);

  GST_OBJECT_LOCK (sink);
  sink->pending_cmd = CMD_WAIT;

  if (sink->task == NULL) {
    sink->task =
        gst_task_new ((GstTaskFunction) gst_rtsp_client_sink_thread, sink,
        NULL);
    if (sink->task == NULL)
      goto task_error;

    gst_task_set_lock (sink->task, GST_RTSP_STREAM_GET_LOCK (sink));
  }
  GST_OBJECT_UNLOCK (sink);

  return TRUE;

  /* ERRORS */
task_error:
  {
    GST_OBJECT_UNLOCK (sink);
    GST_ERROR_OBJECT (sink, "failed to create task");
    return FALSE;
  }
}

static gboolean
gst_rtsp_client_sink_stop (GstRTSPClientSink * sink)
{
  GstTask *task;

  GST_DEBUG_OBJECT (sink, "stopping");

  /* also cancels pending task */
  gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT, CMD_ALL & ~CMD_CLOSE);

  GST_OBJECT_LOCK (sink);
  if ((task = sink->task)) {
    sink->task = NULL;
    GST_OBJECT_UNLOCK (sink);

    gst_task_stop (task);

    g_mutex_lock (&sink->block_streams_lock);
    g_cond_broadcast (&sink->block_streams_cond);
    g_mutex_unlock (&sink->block_streams_lock);

    g_mutex_lock (&sink->preroll_lock);
    g_cond_broadcast (&sink->preroll_cond);
    g_mutex_unlock (&sink->preroll_lock);

    /* make sure it is not running */
    GST_RTSP_STREAM_LOCK (sink);
    GST_RTSP_STREAM_UNLOCK (sink);

    /* now wait for the task to finish */
    gst_task_join (task);

    /* and free the task */
    gst_object_unref (GST_OBJECT (task));

    GST_OBJECT_LOCK (sink);
  }
  GST_OBJECT_UNLOCK (sink);

  /* ensure synchronously all is closed and clean */
  gst_rtsp_client_sink_close (sink, FALSE, TRUE);

  return TRUE;
}

static GstStateChangeReturn
gst_rtsp_client_sink_change_state (GstElement * element,
    GstStateChange transition)
{
  GstRTSPClientSink *rtsp_client_sink;
  GstStateChangeReturn ret;

  rtsp_client_sink = GST_RTSP_CLIENT_SINK (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      if (!gst_rtsp_client_sink_start (rtsp_client_sink))
        goto start_failed;
      break;
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      /* init some state */
      rtsp_client_sink->cur_protocols = rtsp_client_sink->protocols;

      /* setup IPv4/IPv6 unicast port range. */
      if (!rtsp_client_sink->pool)
        rtsp_client_sink->pool = gst_rtsp_address_pool_new ();
      if (rtsp_client_sink->client_port_range.max > 0) {
        gst_rtsp_address_pool_add_range (rtsp_client_sink->pool,
            GST_RTSP_ADDRESS_POOL_ANY_IPV4, GST_RTSP_ADDRESS_POOL_ANY_IPV4,
            rtsp_client_sink->client_port_range.min,
            rtsp_client_sink->client_port_range.max, 0);
        gst_rtsp_address_pool_add_range (rtsp_client_sink->pool,
            GST_RTSP_ADDRESS_POOL_ANY_IPV6, GST_RTSP_ADDRESS_POOL_ANY_IPV6,
            rtsp_client_sink->client_port_range.min,
            rtsp_client_sink->client_port_range.max, 0);
      }

      /* first attempt, don't ignore timeouts */
      rtsp_client_sink->ignore_timeout = FALSE;
      rtsp_client_sink->open_error = FALSE;

      gst_rtsp_client_sink_set_state (rtsp_client_sink, GST_STATE_PAUSED);

      g_mutex_lock (&rtsp_client_sink->preroll_lock);
      if (rtsp_client_sink->in_async) {
        GST_DEBUG_OBJECT (rtsp_client_sink, "Posting ASYNC-START");
        gst_element_post_message (GST_ELEMENT_CAST (rtsp_client_sink),
            gst_message_new_async_start (GST_OBJECT_CAST (rtsp_client_sink)));
      }
      g_mutex_unlock (&rtsp_client_sink->preroll_lock);

      break;
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      /* fall-through */
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      /* unblock the tcp tasks and make the loop waiting */
      if (gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_WAIT,
              CMD_LOOP)) {
        /* make sure it is waiting before we send PLAY below */
        GST_RTSP_STREAM_LOCK (rtsp_client_sink);
        GST_RTSP_STREAM_UNLOCK (rtsp_client_sink);
      }
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_rtsp_client_sink_set_state (rtsp_client_sink, GST_STATE_READY);

      if (rtsp_client_sink->pool) {
        gst_object_unref (rtsp_client_sink->pool);
        rtsp_client_sink->pool = NULL;
      }
      break;
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
    goto done;

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      ret = GST_STATE_CHANGE_SUCCESS;
      break;
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      /* Return ASYNC and preroll input streams */
      g_mutex_lock (&rtsp_client_sink->preroll_lock);
      if (rtsp_client_sink->in_async)
        ret = GST_STATE_CHANGE_ASYNC;
      g_mutex_unlock (&rtsp_client_sink->preroll_lock);
      gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_OPEN, 0);

      /* CMD_OPEN has been scheduled. Wait until the sink thread starts
       * opening connection to the server */
      g_mutex_lock (&rtsp_client_sink->open_conn_lock);
      while (!rtsp_client_sink->open_conn_start) {
        GST_DEBUG_OBJECT (rtsp_client_sink,
            "wait for connection to be started");
        g_cond_wait (&rtsp_client_sink->open_conn_cond,
            &rtsp_client_sink->open_conn_lock);
      }
      rtsp_client_sink->open_conn_start = FALSE;
      g_mutex_unlock (&rtsp_client_sink->open_conn_lock);
      break;
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{
      GST_DEBUG_OBJECT (rtsp_client_sink,
          "Switching to playing -sending RECORD");
      gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_RECORD, 0);
      ret = GST_STATE_CHANGE_SUCCESS;
      break;
    }
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      /* send pause request and keep the idle task around */
      gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_PAUSE,
          CMD_LOOP);
      ret = GST_STATE_CHANGE_NO_PREROLL;
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_CLOSE,
          CMD_PAUSE);
      ret = GST_STATE_CHANGE_SUCCESS;
      break;
    case GST_STATE_CHANGE_READY_TO_NULL:
      gst_rtsp_client_sink_stop (rtsp_client_sink);
      ret = GST_STATE_CHANGE_SUCCESS;
      break;
    default:
      break;
  }

done:
  return ret;

start_failed:
  {
    GST_DEBUG_OBJECT (rtsp_client_sink, "start failed");
    return GST_STATE_CHANGE_FAILURE;
  }
}

/*** GSTURIHANDLER INTERFACE *************************************************/

static GstURIType
gst_rtsp_client_sink_uri_get_type (GType type)
{
  return GST_URI_SINK;
}

static const gchar *const *
gst_rtsp_client_sink_uri_get_protocols (GType type)
{
  static const gchar *protocols[] =
      { "rtsp", "rtspu", "rtspt", "rtsph", "rtsp-sdp",
    "rtsps", "rtspsu", "rtspst", "rtspsh", NULL
  };

  return protocols;
}

static gchar *
gst_rtsp_client_sink_uri_get_uri (GstURIHandler * handler)
{
  GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (handler);

  /* FIXME: make thread-safe */
  return g_strdup (sink->conninfo.location);
}

static gboolean
gst_rtsp_client_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri,
    GError ** error)
{
  GstRTSPClientSink *sink;
  GstRTSPResult res;
  GstSDPResult sres;
  GstRTSPUrl *newurl = NULL;
  GstSDPMessage *sdp = NULL;

  sink = GST_RTSP_CLIENT_SINK (handler);

  /* same URI, we're fine */
  if (sink->conninfo.location && uri && !strcmp (uri, sink->conninfo.location))
    goto was_ok;

  if (g_str_has_prefix (uri, "rtsp-sdp://")) {
    sres = gst_sdp_message_new (&sdp);
    if (sres < 0)
      goto sdp_failed;

    GST_DEBUG_OBJECT (sink, "parsing SDP message");
    sres = gst_sdp_message_parse_uri (uri, sdp);
    if (sres < 0)
      goto invalid_sdp;
  } else {
    /* try to parse */
    GST_DEBUG_OBJECT (sink, "parsing URI");
    if ((res = gst_rtsp_url_parse (uri, &newurl)) < 0)
      goto parse_error;
  }

  /* if worked, free previous and store new url object along with the original
   * location. */
  GST_DEBUG_OBJECT (sink, "configuring URI");
  g_free (sink->conninfo.location);
  sink->conninfo.location = g_strdup (uri);
  gst_rtsp_url_free (sink->conninfo.url);
  sink->conninfo.url = newurl;
  g_free (sink->conninfo.url_str);
  if (newurl)
    sink->conninfo.url_str = gst_rtsp_url_get_request_uri (sink->conninfo.url);
  else
    sink->conninfo.url_str = NULL;

  if (sink->uri_sdp)
    gst_sdp_message_free (sink->uri_sdp);
  sink->uri_sdp = sdp;
  sink->from_sdp = sdp != NULL;

  GST_DEBUG_OBJECT (sink, "set uri: %s", GST_STR_NULL (uri));
  GST_DEBUG_OBJECT (sink, "request uri is: %s",
      GST_STR_NULL (sink->conninfo.url_str));

  return TRUE;

  /* Special cases */
was_ok:
  {
    GST_DEBUG_OBJECT (sink, "URI was ok: '%s'", GST_STR_NULL (uri));
    return TRUE;
  }
sdp_failed:
  {
    GST_ERROR_OBJECT (sink, "Could not create new SDP (%d)", sres);
    g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        "Could not create SDP");
    return FALSE;
  }
invalid_sdp:
  {
    GST_ERROR_OBJECT (sink, "Not a valid SDP (%d) '%s'", sres,
        GST_STR_NULL (uri));
    gst_sdp_message_free (sdp);
    g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        "Invalid SDP");
    return FALSE;
  }
parse_error:
  {
    GST_ERROR_OBJECT (sink, "Not a valid RTSP url '%s' (%d)",
        GST_STR_NULL (uri), res);
    g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        "Invalid RTSP URI");
    return FALSE;
  }
}

static void
gst_rtsp_client_sink_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
  GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;

  iface->get_type = gst_rtsp_client_sink_uri_get_type;
  iface->get_protocols = gst_rtsp_client_sink_uri_get_protocols;
  iface->get_uri = gst_rtsp_client_sink_uri_get_uri;
  iface->set_uri = gst_rtsp_client_sink_uri_set_uri;
}
 07070100000069000081A400000000000000000000000169D554BF00002027000000000000000000000000000000000000003900000000gst-rtsp-server-1.28.2/gst/rtsp-sink/gstrtspclientsink.h  /* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *               <2006> Wim Taymans <wim@fluendo.com>
 *               <2015> Jan Schmidt <jan at centricular dot com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/*
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 * See further explanation attached in License Statement (distributed in the file
 * LICENSE).
 *
 * 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.
 */

#ifndef __GST_RTSP_CLIENT_SINK_H__
#define __GST_RTSP_CLIENT_SINK_H__

#include <gst/gst.h>

G_BEGIN_DECLS

#include <gst/rtsp-server/rtsp-stream.h>
#include <gst/rtsp/rtsp.h>
#include <gio/gio.h>

#define GST_TYPE_RTSP_CLIENT_SINK \
  (gst_rtsp_client_sink_get_type())
#define GST_RTSP_CLIENT_SINK(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTSP_CLIENT_SINK,GstRTSPClientSink))
#define GST_RTSP_CLIENT_SINK_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTSP_CLIENT_SINK,GstRTSPClientSinkClass))
#define GST_IS_RTSP_CLIENT_SINK(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTSP_CLIENT_SINK))
#define GST_IS_RTSP_CLIENT_SINK_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTSP_CLIENT_SINK))
#define GST_RTSP_CLIENT_SINK_CAST(obj) \
  ((GstRTSPClientSink *)(obj))

typedef struct _GstRTSPClientSink GstRTSPClientSink;
typedef struct _GstRTSPClientSinkClass GstRTSPClientSinkClass;

#define GST_RTSP_STATE_GET_LOCK(rtsp)    (&GST_RTSP_CLIENT_SINK_CAST(rtsp)->state_rec_lock)
#define GST_RTSP_STATE_LOCK(rtsp)        (g_rec_mutex_lock (GST_RTSP_STATE_GET_LOCK(rtsp)))
#define GST_RTSP_STATE_UNLOCK(rtsp)      (g_rec_mutex_unlock (GST_RTSP_STATE_GET_LOCK(rtsp)))

#define GST_RTSP_STREAM_GET_LOCK(rtsp)   (&GST_RTSP_CLIENT_SINK_CAST(rtsp)->stream_rec_lock)
#define GST_RTSP_STREAM_LOCK(rtsp)       (g_rec_mutex_lock (GST_RTSP_STREAM_GET_LOCK(rtsp)))
#define GST_RTSP_STREAM_UNLOCK(rtsp)     (g_rec_mutex_unlock (GST_RTSP_STREAM_GET_LOCK(rtsp)))

typedef struct _GstRTSPConnInfo GstRTSPConnInfo;

struct _GstRTSPConnInfo {
  gchar              *location;
  GstRTSPUrl         *url;
  gchar              *url_str;
  GstRTSPConnection  *connection;
  gboolean            connected;
  gboolean            flushing;

  GMutex              send_lock;
  GMutex              recv_lock;
};

typedef struct _GstRTSPStreamInfo GstRTSPStreamInfo;
typedef struct _GstRTSPStreamContext GstRTSPStreamContext;

struct _GstRTSPStreamContext {
  GstRTSPClientSink *parent;

  guint index;
  /* Index of the SDPMedia in the stored SDP */
  guint sdp_index;

  GstElement *payloader;
  guint payloader_block_id;
  gboolean prerolled;

  /* Stream management object */
  GstRTSPStream *stream;
  gboolean joined;

  /* Secure profile key mgmt */
  GstCaps      *srtcpparams;

  /* per stream connection */
  GstRTSPConnInfo  conninfo;
  /* For interleaved mode */
  guint8        channel[2];

  GstRTSPStreamTransport *stream_transport;

  guint ulpfec_percentage;
};

/**
 * GstRTSPNatMethod:
 * @GST_RTSP_NAT_NONE: none
 * @GST_RTSP_NAT_DUMMY: send dummy packets
 *
 * Different methods for trying to traverse firewalls.
 */
typedef enum
{
  GST_RTSP_NAT_NONE,
  GST_RTSP_NAT_DUMMY
} GstRTSPNatMethod;

struct _GstRTSPClientSink {
  GstBin           parent;

  /* task and mutex for interleaved mode */
  gboolean         interleaved;
  GstTask         *task;
  GRecMutex        stream_rec_lock;
  GstSegment       segment;
  gint             free_channel;

  /* UDP mode loop */
  gint             pending_cmd;
  gint             busy_cmd;
  gboolean         ignore_timeout;
  gboolean         open_error;

  /* mutex for protecting state changes */
  GRecMutex        state_rec_lock;

  GstSDPMessage    *uri_sdp;
  gboolean         from_sdp;

  /* properties */
  GstRTSPLowerTrans protocols;
  gboolean          debug;
  guint             retry;
  guint64           udp_timeout;
  gint64            tcp_timeout;
  guint             latency;
  gboolean          do_rtsp_keep_alive;
  gchar            *proxy_host;
  guint             proxy_port;
  gchar            *proxy_user;        /* from url or property */
  gchar            *proxy_passwd;      /* from url or property */
  gchar            *prop_proxy_id;     /* set via property */
  gchar            *prop_proxy_pw;     /* set via property */
  guint             rtp_blocksize;
  gchar            *user_id;
  gchar            *user_pw;
  GstRTSPRange      client_port_range;
  gint              udp_buffer_size;
  gboolean          udp_reconnect;
  gchar            *multi_iface;
  gboolean          ntp_sync;
  gboolean          use_pipeline_clock;
  GstStructure     *sdes;
  GTlsCertificateFlags tls_validation_flags;
  GTlsDatabase     *tls_database;
  GTlsInteraction  *tls_interaction;
  gint              ntp_time_source;
  gchar            *user_agent;
  GstRTSPPublishClockMode publish_clock_mode;

  /* state */
  GstRTSPState        state;
  gchar              *content_base;
  GstRTSPLowerTrans   cur_protocols;
  gboolean            tried_url_auth;
  gchar              *addr;
  GstRTSPAddressPool *pool;
  gboolean            need_redirect;
  GstRTSPTimeRange   *range;
  gchar              *control;
  guint               next_port_num;
  GstClock           *provided_clock;

  /* supported methods */
  gint               methods;

  /* session management */
  GstRTSPConnInfo  conninfo;

  /* Everything goes in an internal
   * locked-state bin */
  GstBin          *internal_bin;
  /* Set to true when internal bin state
   * >= PAUSED */
  gboolean        prerolled;

  /* TRUE if we posted async-start */
  gboolean         in_async;

  /* TRUE when stream info has been collected */
  gboolean         streams_collected;

  /* TRUE when streams have been blocked */
  guint            n_streams_blocked;
  GMutex           block_streams_lock;
  GCond            block_streams_cond;

  guint            next_pad_id;
  gint             next_dyn_pt;

  GstElement      *rtpbin;

  GList           *contexts;
  GstSDPMessage   cursdp;

  GMutex          send_lock;

  GMutex          preroll_lock;
  GCond           preroll_cond;

  /* TRUE if connection to server has been scheduled */
  gboolean        open_conn_start;
  GMutex          open_conn_lock;
  GCond           open_conn_cond;

  GstClockTime    rtx_time;

  GstRTSPProfile profiles;
  gchar          *server_ip;
};

struct _GstRTSPClientSinkClass {
  GstBinClass parent_class;
};

GType gst_rtsp_client_sink_get_type(void);

G_END_DECLS

#endif /* __GST_RTSP_CLIENT_SINK_H__ */
 0707010000006A000081A400000000000000000000000169D554BF000002B2000000000000000000000000000000000000003100000000gst-rtsp-server-1.28.2/gst/rtsp-sink/meson.build  rtspsink_sources = [
  'gstrtspclientsink.c',
  'plugin.c',
]

rtspsink_headers = [
  'gstrtspclientsink.h',
]

doc_sources = []
foreach s: rtspsink_sources + rtspsink_headers
  doc_sources += meson.current_source_dir() / s
endforeach

plugin_sources += {
  'rtspclientsink': pathsep.join(doc_sources)
}

if get_option('rtspclientsink').disabled()
  subdir_done()
endif

rtspsink = library('gstrtspclientsink',
  rtspsink_sources,
  c_args : rtspserver_args + ['-DG_LOG_DOMAIN="GStreamer-rtspclientsink"'],
  include_directories : rtspserver_incs,
  dependencies : [gstrtsp_dep, gstsdp_dep, gst_rtsp_server_dep],
  install : true,
  install_dir : plugins_install_dir)
plugins += [rtspsink]
  0707010000006B000081A400000000000000000000000169D554BF00000263000000000000000000000000000000000000002E00000000gst-rtsp-server-1.28.2/gst/rtsp-sink/plugin.c #ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstrtspclientsink.h"

static gboolean
plugin_init (GstPlugin * plugin)
{
#ifdef ENABLE_NLS
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */

  if (!gst_element_register (plugin, "rtspclientsink", GST_RANK_NONE,
          GST_TYPE_RTSP_CLIENT_SINK))
    return FALSE;

  return TRUE;
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    rtspclientsink,
    "RTSP client sink element",
    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
 0707010000006C000081A400000000000000000000000169D554BF0000218C000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/meson.build    project('gst-rtsp-server', 'c',
  version : '1.28.2',
  meson_version : '>= 1.4',
  default_options : ['warning_level=1', 'buildtype=debugoptimized'])

gst_version = meson.project_version()
version_arr = gst_version.split('.')
gst_version_major = version_arr[0].to_int()
gst_version_minor = version_arr[1].to_int()
gst_version_micro = version_arr[2].to_int()
 if version_arr.length() == 4
  gst_version_nano = version_arr[3].to_int()
else
  gst_version_nano = 0
endif
gst_version_is_stable = gst_version_minor.is_even()
gst_version_is_dev = gst_version_minor.is_odd() and gst_version_micro < 90

glib_req = '>= 2.64.0'

if gst_version_is_stable
  gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor)
else
  gst_req = '>= ' + gst_version
endif

api_version = '1.0'
soversion = 0
# maintaining compatibility with the previous libtool versioning
# current = minor * 100 + micro
curversion = gst_version_minor * 100 + gst_version_micro
libversion = '@0@.@1@.0'.format(soversion, curversion)
osxversion = curversion + 1

plugins_install_dir = '@0@/gstreamer-1.0'.format(get_option('libdir'))

cc = meson.get_compiler('c')

cdata = configuration_data()

if cc.has_link_argument('-Wl,-Bsymbolic-functions')
  add_project_link_arguments('-Wl,-Bsymbolic-functions', language : 'c')
endif

# glib doesn't support unloading, which means that unloading and reloading
# any library that registers static types will fail
if cc.has_link_argument('-Wl,-z,nodelete')
  add_project_link_arguments('-Wl,-z,nodelete', language: 'c')
endif

# Symbol visibility
if cc.has_argument('-fvisibility=hidden')
  add_project_arguments('-fvisibility=hidden', language: 'c')
endif

# Disable strict aliasing
if cc.has_argument('-fno-strict-aliasing')
  add_project_arguments('-fno-strict-aliasing', language: 'c')
endif

# Define G_DISABLE_DEPRECATED for development versions
if gst_version_is_dev
  message('Disabling deprecated GLib API')
  add_project_arguments('-DG_DISABLE_DEPRECATED', language: 'c')
endif

# Same logic as in GLib.
glib_debug = get_option('glib_debug')
if glib_debug.disabled() or (
   glib_debug.auto() and (not get_option('debug') or get_option('optimization') not in [ '0', 'g' ]))
  message('Disabling GLib cast checks')
  add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: 'c')
endif

if not get_option('glib_assert')
  message('Disabling GLib asserts')
  add_project_arguments('-DG_DISABLE_ASSERT', language: 'c')
endif

if not get_option('glib_checks')
  message('Disabling GLib checks')
  add_project_arguments('-DG_DISABLE_CHECKS', language: 'c')
endif

cdata.set_quoted('GETTEXT_PACKAGE', 'gst-rtsp-server-1.0')
cdata.set_quoted('PACKAGE', 'gst-rtsp-server')
cdata.set_quoted('VERSION', gst_version)
cdata.set_quoted('PACKAGE_VERSION', gst_version)
cdata.set_quoted('GST_API_VERSION', api_version)
cdata.set_quoted('GST_LICENSE', 'LGPL')

# FIXME: ENABLE_NLS (currently also missing from autotools build)
# cdata.set('ENABLE_NLS', true)
# cdata.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir')))

# GStreamer package name and origin url
gst_package_name = get_option('package-name')
if gst_package_name == ''
  if gst_version_nano == 0
    gst_package_name = 'GStreamer RTSP Server Library source release'
  elif gst_version_nano == 1
    gst_package_name = 'GStreamer RTSP Server Library git'
  else
    gst_package_name = 'GStreamer RTSP Server Library prerelease'
  endif
endif
cdata.set_quoted('GST_PACKAGE_NAME', gst_package_name)
cdata.set_quoted('GST_PACKAGE_ORIGIN', get_option('package-origin'))

rtspserver_args = ['-DHAVE_CONFIG_H']

# NOTE: Keep entries alphabetically sorted
warning_flags = [
  '-Waddress',
  '-Waggregate-return',
  '-Wformat',
  '-Wformat-nonliteral',
  '-Wformat-security',
  '-Wimplicit-fallthrough=3',
  '-Winit-self',
  '-Wmissing-declarations',
  '-Wmissing-include-dirs',
  '-Wmissing-parameter-type',
  '-Wmissing-prototypes',
  '-Wno-multichar',
  '-Wold-style-definition',
  '-Wpointer-arith',
  '-Wredundant-decls',
  '-Wshift-negative-value',
  '-Wtype-limits',
  '-Wundef',
  '-Wvla',
  '-Wwrite-strings',
]

foreach extra_arg : warning_flags
  if cc.has_argument (extra_arg)
    add_project_arguments([extra_arg], language: 'c')
  endif
endforeach

rtspserver_incs = include_directories('gst/rtsp-server', '.')

gst_dep = dependency('gstreamer-1.0', version : gst_req,
  fallback : ['gstreamer', 'gst_dep'])
gstrtsp_dep = dependency('gstreamer-rtsp-1.0', version : gst_req,
  fallback : ['gst-plugins-base', 'rtsp_dep'])
gstrtp_dep = dependency('gstreamer-rtp-1.0', version : gst_req,
  fallback : ['gst-plugins-base', 'rtp_dep'])
gstsdp_dep = dependency('gstreamer-sdp-1.0', version : gst_req,
  fallback : ['gst-plugins-base', 'sdp_dep'])
gstapp_dep = dependency('gstreamer-app-1.0', version : gst_req,
  fallback : ['gst-plugins-base', 'app_dep'])
gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_req,
  fallback : ['gst-plugins-base', 'video_dep'])
gstnet_dep = dependency('gstreamer-net-1.0', version : gst_req,
  fallback : ['gstreamer', 'gst_net_dep'])
if host_machine.system() != 'windows'
  gstcheck_dep = dependency('gstreamer-check-1.0', version : gst_req,
    required : get_option('tests'),
    fallback : ['gstreamer', 'gst_check_dep'])
endif

# Disable compiler warnings for unused variables and args if gst debug system is disabled
if gst_dep.type_name() == 'internal'
  gst_debug_disabled = not subproject('gstreamer').get_variable('gst_debug')
else
  # We can't check that in the case of subprojects as we won't
  # be able to build against an internal dependency (which is not built yet)
  gst_debug_disabled = cc.has_header_symbol('gst/gstconfig.h', 'GST_DISABLE_GST_DEBUG', dependencies: gst_dep)
endif

if gst_debug_disabled
  message('GStreamer debug system is disabled')
  add_project_arguments(cc.get_supported_arguments(['-Wno-unused']), language: 'c')
else
  message('GStreamer debug system is enabled')
endif

gir = find_program('g-ir-scanner', required : get_option('introspection'))
gnome = import('gnome')
build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())
gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \
    'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
    'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
    'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
    'g_setenv("GST_TRACERS", "", TRUE);' + \
    'gst_init(NULL,NULL);', '--quiet']

pkgconfig = import('pkgconfig')
plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig')
if get_option('default_library') == 'shared'
  # If we don't build static plugins there is no need to generate pc files
  plugins_pkgconfig_install_dir = disabler()
endif

plugins = []
plugin_sources = {}
pkgconfig_subdirs = ['gstreamer-1.0']
static_build = get_option('default_library') == 'static'
gst_libraries = []

if host_machine.system() == 'windows'
  pathsep = ';'
else
  pathsep = ':'
endif

subdir('gst')

subdir('tests')
subdir('examples')

subdir('docs')

# Set release date
if gst_version_nano == 0
  extract_release_date = find_program('scripts/extract-release-date-from-doap-file.py')
  run_result = run_command(extract_release_date, gst_version, files('gst-rtsp-server.doap'), check: true)
  release_date = run_result.stdout().strip()
  cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', release_date)
  message('Package release date: ' + release_date)
endif

glib_gio_dep = dependency('gio-2.0', version: glib_req)
if glib_gio_dep.version().version_compare('< 2.67.4')
  cdata.set('g_memdup2(ptr,sz)', '(G_LIKELY(((guint64)(sz)) < G_MAXUINT)) ? g_memdup(ptr,sz) : (g_abort(),NULL)')
endif

configure_file(output: 'config.h', configuration: cdata)

meson.add_dist_script('scripts/gen-changelog.py', meson.project_name(), '1.26.0', meson.project_version())

# dist common files from monorepo root
if not meson.is_subproject()
  meson.add_dist_script('scripts/dist-common-files.py', meson.project_version())
endif

plugin_names = []
gst_plugins = []
foreach plugin: plugins
  pkgconfig.generate(plugin, install_dir: plugins_pkgconfig_install_dir)
  dep = declare_dependency(link_with: plugin, variables: {'full_path': plugin.full_path()})
  meson.override_dependency(plugin.name(), dep)
  gst_plugins += [dep]
  if plugin.name().startswith('gst')
    plugin_names += [plugin.name().substring(3)]
  else
    plugin_names += [plugin.name()]
  endif
endforeach

summary({
    'Plugins': plugin_names,
}, list_sep: ', ')
0707010000006D000081A400000000000000000000000169D554BF00000885000000000000000000000000000000000000002500000000gst-rtsp-server-1.28.2/meson.options  # Feature options for plugins with no external deps
option('rtspclientsink', type : 'feature', value : 'auto')

# Common feature options
option('examples', type : 'feature', value : 'auto', yield : true,
       description : 'Build the examples')
option('tests', type : 'feature', value : 'auto', yield : true,
       description : 'Build and enable unit tests')
option('introspection', type : 'feature', value : 'auto', yield : true,
       description : 'Generate gobject-introspection bindings')

# Common options
option('package-name', type : 'string', yield : true,
       description : 'package name to use in plugins')
option('package-origin', type : 'string',
       value : 'Unknown package origin', yield : true,
       description : 'package origin URL to use in plugins')
option('doc', type : 'feature', value : 'auto', yield: true,
       description: 'Enable documentation.')
option('glib_debug', type : 'feature', value : 'auto', yield : true, description : 'Enable GLib debug infrastructure (see docs/macros.txt)')
option('glib_assert', type : 'boolean', value : true, yield : true, description : 'Enable GLib assertion (see docs/macros.txt)',
  deprecated: {'enabled' : 'true', 'disabled' : 'false', 'auto' : 'false'},
)
option('glib_checks', type : 'boolean', value : true, yield : true, description : 'Enable GLib checks such as API guards (see docs/macros.txt)',
  deprecated: {'enabled' : 'true', 'disabled' : 'false', 'auto' : 'false'},
)

# Deprecated, kept for backward compat
option('gobject-cast-checks', type : 'feature', value : 'auto', yield : true,
       description: 'Enable run-time GObject cast checks (auto = enabled for development, disabled for stable releases)',
       deprecated: 'glib_debug')
option('glib-asserts', type : 'feature', value : 'enabled', yield : true,
       description: 'Enable GLib assertion (auto = enabled for development, disabled for stable releases)',
       deprecated: 'glib_assert')
option('glib-checks', type : 'feature', value : 'enabled', yield : true,
       description: 'Enable GLib checks such as API guards (auto = enabled for development, disabled for stable releases)',
       deprecated: 'glib_checks')
   0707010000006E000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000001F00000000gst-rtsp-server-1.28.2/scripts    0707010000006F000081ED00000000000000000000000169D554BF00000B67000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/scripts/dist-common-files.py   #!/usr/bin/env python3
#
# Copyright (C) 2023-2026 Tim-Philipp Müller <tim centricular net>
#
# 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., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301, USA.

import os
import subprocess
import shutil
import tempfile
import sys

if __name__ == "__main__":
    dist_root = os.environ['MESON_DIST_ROOT']
    build_root = os.environ['MESON_BUILD_ROOT']
    source_root = os.environ['MESON_SOURCE_ROOT']
    project_version = sys.argv[1]
    pwd = os.environ['PWD']
    tmpdir = tempfile.gettempdir()

    ver_array = project_version.split('.')
    major_minor = '{}.{}'.format(ver_array[0], ver_array[1])

    module = os.path.basename(os.path.normpath(source_root))

    print('Copying README.md into dist staging directory ..')
    readme_src = os.path.join(source_root, '..', '..', 'README.md')
    shutil.copy2(readme_src, dist_root)

    # Release notes (instead of NEWS) - could also write it out as NEWS.md
    print('Copying release notes into dist staging directory ..')
    relnotes_src = os.path.join(source_root, '..', '..', 'release-notes', major_minor, f'release-notes-{major_minor}.md')
    with open(relnotes_src, 'r') as f:
        lines = f.readlines()
    if not f'### {project_version}\n' in lines:
        sys.exit(f'Update {relnotes_src} first, must contain a section for {project_version}')
    if not project_version.endswith('.0'):
        found = False
        for line in lines:
            if line.startswith('The latest') and project_version in line:
                found = True
        if not found:
            sys.exit(f'Update {relnotes_src} first, header should say latest version is {project_version}.')
    shutil.copy2(relnotes_src, dist_root)

    # RELEASE
    print('Copying RELEASE into dist staging directory ..')
    rel_src = os.path.join(source_root, '..', '..', 'release-notes', major_minor, f'RELEASE-{major_minor}.template')
    with open(rel_src, 'r') as f:
        lines = f.readlines()

    assert (lines[0].startswith('This is GStreamer'))

    if module == 'gstreamer':
        lines[0] = f'This is GStreamer core {project_version}\n'
    else:
        lines[0] = f'This is GStreamer {module} {project_version}\n'

    with open(os.path.join(dist_root, 'RELEASE'), 'w') as f:
        f.writelines(lines)
 07070100000070000081ED00000000000000000000000169D554BF00000653000000000000000000000000000000000000004600000000gst-rtsp-server-1.28.2/scripts/extract-release-date-from-doap-file.py #!/usr/bin/env python3
#
# extract-release-date-from-doap-file.py VERSION DOAP-FILE
#
# Extract release date for the given release version from a DOAP file
#
# Copyright (C) 2020 Tim-Philipp Müller <tim centricular com>
#
# 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., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301, USA.

import sys
import xml.etree.ElementTree as ET

if len(sys.argv) != 3:
  sys.exit('Usage: {} VERSION DOAP-FILE'.format(sys.argv[0]))

release_version = sys.argv[1]
doap_fn = sys.argv[2]

tree = ET.parse(doap_fn)
root = tree.getroot()

namespaces = {'doap': 'http://usefulinc.com/ns/doap#'}

for v in root.findall('doap:release/doap:Version', namespaces=namespaces):
  if v.findtext('doap:revision', namespaces=namespaces) == release_version:
    release_date = v.findtext('doap:created', namespaces=namespaces)
    if release_date:
      print(release_date)
      sys.exit(0)

sys.exit('Could not find a release with version {} in {}'.format(release_version, doap_fn))
 07070100000071000081ED00000000000000000000000169D554BF000020D6000000000000000000000000000000000000003000000000gst-rtsp-server-1.28.2/scripts/gen-changelog.py   #!/usr/bin/env python3
#
# Makes a GNU-Style ChangeLog from a git repository
import os
import sys
import subprocess
import re

meson_source_root = os.environ.get('MESON_SOURCE_ROOT')

meson_dist_root = os.environ.get('MESON_DIST_ROOT')
if meson_dist_root:
    output_fn = os.path.join(meson_dist_root, 'ChangeLog')
else:
    output_fn = sys.stdout.fileno()

# commit hash => release version tag string
release_refs = {}

# These are the pre-monorepo module beginnings
changelog_starts = {
    'gstreamer': '70521179a75db0c7230cc47c6d7f9d63cf73d351',
    'gst-plugins-base': '68746a38d5e48e6f7c220663dcc2f175ff55cb3c',
    'gst-plugins-good': '81f63142d65b62b0971c19ceb79956c49ffc2f06',
    'gst-plugins-ugly': '7d7c3e478e32b7b66c44cc4442d571fbab534740',
    'gst-plugins-bad': 'ea6821e2934fe8d356ea89d5610f0630b3446877',
    'gst-libav': '3c440154c60d1ec0a54186f0fad4aebfd2ecc3ea',
    'gst-rtsp-server': '5029c85a46a8c366c4bf272d503e22bbcd624ece',
    'gst-editing-services': 'ee8bf88ebf131cf7c7161356540efc20bf411e14',
    'gst-python': 'b3e564eff577e2f577d795051bbcca85d47c89dc',
    'gst-devtools': 'da962d096af9460502843e41b7d25fdece7ff1c2',
    'gstreamer-sharp': 'b94528f8e7979df49fedf137dfa228d8fe475e1b',
}


def print_help():
    print('', file=sys.stderr)
    print('gen-changelog: generate GNU-style changelog from git history',
          file=sys.stderr)
    print('', file=sys.stderr)
    print('Usage: {} [OPTIONS] GSTREAMER-MODULE [START-TAG] [HEAD-TAG]'.format(
        sys.argv[0]), file=sys.stderr)
    print('', file=sys.stderr)
    sys.exit(1)


if len(sys.argv) < 2 or len(sys.argv) > 4 or '--help' in sys.argv:
    print_help()

module = sys.argv[1]

if len(sys.argv) > 2:
    start_tag = sys.argv[2]
else:
    start_tag = None

if len(sys.argv) > 3:
    head_tag = sys.argv[3]
else:
    head_tag = None

if module not in changelog_starts:
    print(f'Unknown module {module}', file=sys.stderr)
    print_help()


def process_commit(lines, files, subtree_path=None):
    # DATE NAME
    # BLANK LINE
    # Subject
    # BLANK LINE
    # ...
    # FILES
    fileincommit = False
    lines = [x.strip() for x in lines if x.strip()
             and not x.startswith('git-svn-id')]
    files = [x.strip() for x in files if x.strip()]
    for line in lines:
        if line.startswith('* ') and ':' in line:
            fileincommit = True
            break

    top_line = lines[0]
    print(top_line.strip())
    print()
    if not fileincommit:
        for f in files:
            if subtree_path and f.startswith(subtree_path):
                # requires Python 3.9
                print('\t* %s:' % f.removeprefix(subtree_path))
            else:
                print('\t* %s:' % f)
    for line in lines[1:]:
        print('\t ', line)
    print()


def output_commits(module, start_tag, end_tag, subtree_path=None):
    # retrieve commit date for start tag so we can filter the log for commits
    # after that date. That way we don't include commits from merged-in
    # plugin-move branches that go back to the beginning of time.
    start_date = get_commit_date_for_ref(start_tag)

    cmd = ['git', 'log',
           '--pretty=format:--START-COMMIT--%H%n%ai  %an <%ae>%n%n%s%n%b%n--END-COMMIT--',
           '--date=short',
           '--name-only',
           f'--since={start_date}',
           f'{start_tag}..{end_tag}',
           ]

    if subtree_path:
        cmd += ['--', '.']

    p = subprocess.Popen(args=cmd, shell=False,
                         stdout=subprocess.PIPE, cwd=meson_source_root)
    buf = []
    files = []
    filemode = False
    for lin in [x.decode('utf8', errors='replace') for x in p.stdout.readlines()]:
        if lin.startswith("--START-COMMIT--"):
            commit_hash = lin[16:].strip()
            if buf != []:
                process_commit(buf, files, subtree_path)

            if commit_hash in release_refs:
                version_str = release_refs[commit_hash]
                print(f'=== release {version_str} ===\n')

            buf = []
            files = []
            filemode = False
        elif lin.startswith("--END-COMMIT--"):
            filemode = True
        elif filemode is True:
            files.append(lin)
        else:
            buf.append(lin)
    if buf != []:
        process_commit(buf, files, subtree_path)


def get_commit_date_for_ref(ref):
    cmd = ['git', 'log', '--pretty=format:%cI', '-1', ref]
    r = subprocess.run(cmd, capture_output=True, text=True,
                       check=True, cwd=meson_source_root)
    commit_date = r.stdout.strip()
    return commit_date


def populate_release_tags_for_premonorepo_module(module_tag_prefix):
    if module_tag_prefix != '':
        cmd = ['git', 'tag', '--list', f'{module_tag_prefix}*']
    else:
        cmd = ['git', 'tag', '--list', '1.*', 'RELEASE-*']

    p = subprocess.Popen(args=cmd, shell=False,
                         stdout=subprocess.PIPE, cwd=meson_source_root)
    for line in [x.decode('utf8') for x in p.stdout.readlines()]:
        git_tag = line.strip()
        version_str = git_tag.removeprefix(module_tag_prefix).removeprefix('RELEASE-').split('-')[0].replace('_', '.')
        # might have been populated with post-monorepo tags already for gstreamer core
        if version_str not in release_refs:
            # find last commit before tag in module subdirectory
            cmd = ['git', 'log', '--pretty=format:%H', '-1', git_tag]
            r = subprocess.run(cmd, capture_output=True,
                               text=True, check=True, cwd=meson_source_root)
            commit_hash = r.stdout.strip()
            release_refs[commit_hash] = version_str

            # print(f'{git_tag} => {version_str} => {commit_hash}')


def populate_release_tags_for_monorepo_subproject():
    cmd = ['git', 'tag', '--list', '1.*']
    p = subprocess.Popen(args=cmd, shell=False,
                         stdout=subprocess.PIPE, cwd=meson_source_root)
    for line in [x.decode('utf8') for x in p.stdout.readlines()]:
        version_str = line.strip()
        version_arr = version_str.split('.')
        major = int(version_arr[0])
        minor = int(version_arr[1])
        micro = int(version_arr[2])
        # ignore pre-monorepo versions
        if major < 1:
            continue
        if major == 1 and minor < 19:
            continue
        if major == 1 and minor == 19 and micro < 2:
            continue
        # find last commit before tag in module subdirectory
        cmd = ['git', 'log', '--pretty=format:%H',
               '-1', version_str, '--', '.']
        r = subprocess.run(cmd, capture_output=True, text=True,
                           check=True, cwd=meson_source_root)
        commit_hash = r.stdout.strip()
        release_refs[commit_hash] = version_str


if __name__ == '__main__':
    module_tag_prefix = '' if module == 'gstreamer' else f'{module}-'

    populate_release_tags_for_monorepo_subproject()

    with open(output_fn, 'w') as f:
        sys.stdout = f

        # Force writing of head tag
        if head_tag and head_tag not in release_refs.values():
            print(f'=== release {head_tag} ===\n')

        # Output all commits from start_tag onwards, otherwise output full history.
        # (We assume the start_tag is after the monorepo merge if it's specified.)
        if start_tag and start_tag != 'start':
            output_commits(module, start_tag, 'HEAD', f'subprojects/{module}/')
        else:
            # First output all post-monorepo commits or commits from start_tag if specified
            output_commits(module, 'monorepo-start',
                           'HEAD', f'subprojects/{module}/')

            populate_release_tags_for_premonorepo_module(module_tag_prefix)

            # Next output all pre-monorepo commits (modules have their own root)
            if not start_tag:
                module_start = f'{module_tag_prefix}1.0.0'
            elif start_tag == 'start':
                module_start = changelog_starts[module]
            else:
                module_start = f'{module_tag_prefix}{start_tag}'

            output_commits(module, module_start,
                           f'{module_tag_prefix}1.19.2', None)

        # Write start tag at end for clarity
        if not start_tag:
            print(f'=== release 1.0.0 ===\n')
        elif start_tag != 'start':
            print(f'=== release {start_tag} ===\n')
  07070100000072000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000001D00000000gst-rtsp-server-1.28.2/tests  07070100000073000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/tests/check    07070100000074000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002700000000gst-rtsp-server-1.28.2/tests/check/gst    07070100000075000081A400000000000000000000000169D554BF0000284E000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/tests/check/gst/addresspool.c  /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-address-pool.h>

GST_START_TEST (test_pool)
{
  GstRTSPAddressPool *pool;
  GstRTSPAddress *addr, *addr2, *addr3;
  GstRTSPAddressPoolResult res;

  pool = gst_rtsp_address_pool_new ();

  fail_if (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "233.252.0.0", 5000, 5010, 1));
  fail_if (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "::1", 5000, 5010, 1));
  fail_if (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "ff02::1", 5000, 5010, 1));
  fail_if (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1.1", "233.252.0.1", 5000, 5010, 1));
  fail_if (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "233.252.0.1.1", 5000, 5010, 1));
  ASSERT_CRITICAL (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.0", "233.252.0.1", 5010, 5000, 1));

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.0", "233.252.0.255", 5000, 5010, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.255.0.0", "233.255.0.0", 5000, 5010, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.255.0.0", "233.255.0.0", 5020, 5020, 1));

  /* should fail, we can't allocate a block of 256 ports */
  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_MULTICAST, 256);
  fail_unless (addr == NULL);

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr != NULL);

  addr2 = gst_rtsp_address_copy (addr);

  gst_rtsp_address_free (addr2);
  gst_rtsp_address_free (addr);

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_MULTICAST, 4);
  fail_unless (addr != NULL);

  /* Will fail because pool is NULL */
  ASSERT_CRITICAL (gst_rtsp_address_pool_clear (NULL));

  /* will fail because an address is allocated */
  ASSERT_CRITICAL (gst_rtsp_address_pool_clear (pool));

  gst_rtsp_address_free (addr);

  gst_rtsp_address_pool_clear (pool);

  /* start with odd port to make sure we are allocated address
   * starting with even port
   */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "FF11:DB8::1", "FF11:DB8::1", 5001, 5003, 1));

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_IPV6 | GST_RTSP_ADDRESS_FLAG_EVEN_PORT |
      GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5002);
  fail_unless (!g_ascii_strcasecmp (addr->address, "FF11:DB8::1"));

  /* Will fail becuse there is only one IPv6 address left */
  addr2 = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_IPV6 | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr2 == NULL);

  /* Will fail because the only IPv6 address left has an odd port */
  addr2 = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_IPV6 | GST_RTSP_ADDRESS_FLAG_EVEN_PORT |
      GST_RTSP_ADDRESS_FLAG_MULTICAST, 1);
  fail_unless (addr2 == NULL);

  addr2 = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_IPV4 | GST_RTSP_ADDRESS_FLAG_MULTICAST, 1);
  fail_unless (addr2 == NULL);

  gst_rtsp_address_free (addr);

  gst_rtsp_address_pool_clear (pool);

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.0", "233.252.0.255", 5000, 5002, 1));

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5000);
  fail_unless (!strcmp (addr->address, "233.252.0.0"));

  addr2 = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr2 != NULL);
  fail_unless (addr2->port == 5000);
  fail_unless (!strcmp (addr2->address, "233.252.0.1"));

  gst_rtsp_address_free (addr);
  gst_rtsp_address_free (addr2);

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_IPV6 | GST_RTSP_ADDRESS_FLAG_MULTICAST, 1);
  fail_unless (addr == NULL);

  gst_rtsp_address_pool_clear (pool);

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.1.1", "233.252.1.1", 5000, 5001, 1));

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 5000, 3,
      1, &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.2", 5000, 2,
      1, &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 500, 2, 1,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 5000, 2,
      2, &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "2000::1", 5000, 2, 2,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_EINVAL);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "ff02::1", 5000, 2, 2,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "1.1", 5000, 2, 2, &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_EINVAL);
  fail_unless (addr == NULL);

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 5000, 2,
      1, &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_OK);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5000);
  fail_unless (!strcmp (addr->address, "233.252.1.1"));

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 5000, 2,
      1, &addr2);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERESERVED);
  fail_unless (addr2 == NULL);

  gst_rtsp_address_free (addr);
  gst_rtsp_address_pool_clear (pool);

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.1.1", "233.252.1.3", 5000, 5001, 1));

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.1", 5000, 2,
      1, &addr);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5000);
  fail_unless (!strcmp (addr->address, "233.252.1.1"));

  res = gst_rtsp_address_pool_reserve_address (pool, "233.252.1.3", 5000, 2,
      1, &addr2);
  fail_unless (addr2 != NULL);
  fail_unless (addr2->port == 5000);
  fail_unless (!strcmp (addr2->address, "233.252.1.3"));

  addr3 = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr3 != NULL);
  fail_unless (addr3->port == 5000);
  fail_unless (!strcmp (addr3->address, "233.252.1.2"));

  fail_unless (gst_rtsp_address_pool_acquire_address (pool,
          GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2)
      == NULL);

  gst_rtsp_address_free (addr);
  gst_rtsp_address_free (addr2);
  gst_rtsp_address_free (addr3);
  gst_rtsp_address_pool_clear (pool);

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.1.1", "233.252.1.1", 5000, 5001, 1));
  fail_if (gst_rtsp_address_pool_has_unicast_addresses (pool));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "192.168.1.1", "192.168.1.1", 6000, 6001, 0));
  fail_unless (gst_rtsp_address_pool_has_unicast_addresses (pool));

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST, 2);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5000);
  fail_unless (!strcmp (addr->address, "233.252.1.1"));
  gst_rtsp_address_free (addr);

  addr = gst_rtsp_address_pool_acquire_address (pool,
      GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_UNICAST, 2);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 6000);
  fail_unless (!strcmp (addr->address, "192.168.1.1"));
  gst_rtsp_address_free (addr);

  fail_unless (gst_rtsp_address_pool_add_range (pool,
          GST_RTSP_ADDRESS_POOL_ANY_IPV4, GST_RTSP_ADDRESS_POOL_ANY_IPV4, 5000,
          5001, 0));
  res =
      gst_rtsp_address_pool_reserve_address (pool, "192.168.0.1", 5000, 1, 0,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERANGE);
  res =
      gst_rtsp_address_pool_reserve_address (pool, "0.0.0.0", 5000, 1, 0,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_OK);
  gst_rtsp_address_free (addr);
  gst_rtsp_address_pool_clear (pool);

  /* Error case 2. Using ANY as min address makes it possible to allocate the
   * same address twice */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          GST_RTSP_ADDRESS_POOL_ANY_IPV4, "255.255.255.255", 5000, 5001, 0));
  res =
      gst_rtsp_address_pool_reserve_address (pool, "192.168.0.1", 5000, 1, 0,
      &addr);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_OK);
  res =
      gst_rtsp_address_pool_reserve_address (pool, "192.168.0.1", 5000, 1, 0,
      &addr2);
  fail_unless (res == GST_RTSP_ADDRESS_POOL_ERESERVED);
  gst_rtsp_address_free (addr);
  gst_rtsp_address_pool_clear (pool);

  g_object_unref (pool);
}

GST_END_TEST;

static Suite *
rtspaddresspool_suite (void)
{
  Suite *s = suite_create ("rtspaddresspool");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_pool);

  return s;
}

GST_CHECK_MAIN (rtspaddresspool);
  07070100000076000081A400000000000000000000000169D554BF00014027000000000000000000000000000000000000003000000000gst-rtsp-server-1.28.2/tests/check/gst/client.c   /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-client.h>

#define VIDEO_PIPELINE "videotestsrc ! " \
  "video/x-raw,width=352,height=288 ! " \
  "rtpgstpay name=pay0 pt=96"
#define AUDIO_PIPELINE "audiotestsrc ! " \
  "audio/x-raw,rate=8000 ! " \
  "rtpgstpay name=pay1 pt=97"

static gchar *session_id;
static gint cseq;
static guint expected_session_timeout = 60;
static const gchar *expected_unsupported_header;
static const gchar *expected_scale_header;
static const gchar *expected_speed_header;
static gdouble fake_rate_value = 0;
static gdouble fake_applied_rate_value = 0;

static gboolean
test_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_play_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  gchar **session_hdr_params;
  gchar *pattern;

  fail_unless_equals_int (gst_rtsp_message_get_type (response),
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless_equals_int (code, GST_RTSP_STS_OK);
  fail_unless_equals_string (reason, "OK");
  fail_unless_equals_int (version, GST_RTSP_VERSION_1_0);

  /* Verify mandatory headers according to RFC 2326 */
  /* verify mandatory CSeq header */
  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  /* verify mandatory Session header */
  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION,
          &str, 0) == GST_RTSP_OK);
  session_hdr_params = g_strsplit (str, ";", -1);
  fail_unless (session_hdr_params[0] != NULL);
  g_strfreev (session_hdr_params);

  /* verify mandatory RTP-Info header */
  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_RTP_INFO,
          &str, 0) == GST_RTSP_OK);
  pattern = g_strdup_printf ("^url=rtsp://.+;seq=[0-9]+;rtptime=[0-9]+");
  fail_unless (g_regex_match_simple (pattern, str, 0, 0),
      "GST_RTSP_HDR_RTP_INFO '%s' doesn't match pattern '%s'", str, pattern);
  g_free (pattern);

  return TRUE;
}

static gboolean
test_response_400 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_BAD_REQUEST);
  fail_unless (g_str_equal (reason, "Bad Request"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_404 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_NOT_FOUND);
  fail_unless (g_str_equal (reason, "Not Found"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_454 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_SESSION_NOT_FOUND);
  fail_unless (g_str_equal (reason, "Session Not Found"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_551 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *options;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless (g_str_equal (reason, "Option not supported"));
  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_UNSUPPORTED,
          &options, 0) == GST_RTSP_OK);
  fail_unless (!g_strcmp0 (expected_unsupported_header, options));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static void
create_connection (GstRTSPConnection ** conn)
{
  GSocket *sock;
  GError *error = NULL;

  sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
      G_SOCKET_PROTOCOL_TCP, &error);
  g_assert_no_error (error);
  fail_unless (gst_rtsp_connection_create_from_socket (sock, "127.0.0.1", 444,
          NULL, conn) == GST_RTSP_OK);
  g_object_unref (sock);
}

static GstRTSPClient *
setup_client (const gchar * launch_line, const gchar * mount_point,
    gboolean enable_rtcp)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPThreadPool *thread_pool;

  client = gst_rtsp_client_new ();

  session_pool = gst_rtsp_session_pool_new ();
  gst_rtsp_client_set_session_pool (client, session_pool);

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  if (launch_line == NULL)
    gst_rtsp_media_factory_set_launch (factory,
        "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  else
    gst_rtsp_media_factory_set_launch (factory, launch_line);

  gst_rtsp_media_factory_set_enable_rtcp (factory, enable_rtcp);

  gst_rtsp_mount_points_add_factory (mount_points, mount_point, factory);
  gst_rtsp_client_set_mount_points (client, mount_points);

  thread_pool = gst_rtsp_thread_pool_new ();
  gst_rtsp_client_set_thread_pool (client, thread_pool);

  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (thread_pool);

  return client;
}

static void
teardown_client (GstRTSPClient * client)
{
  gst_rtsp_client_set_thread_pool (client, NULL);
  g_object_unref (client);
}

static gchar *
check_requirements_cb (GstRTSPClient * client, GstRTSPContext * ctx,
    gchar ** req, gpointer user_data)
{
  int index = 0;
  GString *result = g_string_new ("");

  while (req[index] != NULL) {
    if (g_strcmp0 (req[index], "test-requirements")) {
      if (result->len > 0)
        g_string_append (result, ", ");
      g_string_append (result, req[index]);
    }
    index++;
  }

  return g_string_free (result, FALSE);
}

GST_START_TEST (test_require)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* require header without handler */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_signal_connect (G_OBJECT (client), "check-requirements",
      G_CALLBACK (check_requirements_cb), NULL);

  /* one supported option */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-requirements");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* unsupported option */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* more than one unsupported options */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-not-supported2");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1, test-not-supported2";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* supported and unsupported together */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-requirements");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-not-supported2");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1, test-not-supported2";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

GST_START_TEST (test_request)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPConnection *conn;

  client = gst_rtsp_client_new ();

  /* OPTIONS with invalid url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "foopy://padoop/") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_400, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  /* OPTIONS with unknown session id */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, "foobar");

  gst_rtsp_client_set_send_func (client, test_response_454, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  /* OPTIONS with an absolute path instead of an absolute url */
  /* set host information */
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* OPTIONS with an absolute path instead of an absolute url with invalid
   * host information */
  g_object_unref (client);
  client = gst_rtsp_client_new ();
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_400, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

static gboolean
test_option_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  GstRTSPMethod methods;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_PUBLIC, &str,
          0) == GST_RTSP_OK);

  methods = gst_rtsp_options_from_text (str);
  fail_if (methods == 0);
  fail_unless (methods == (GST_RTSP_DESCRIBE |
          GST_RTSP_ANNOUNCE |
          GST_RTSP_OPTIONS |
          GST_RTSP_PAUSE |
          GST_RTSP_PLAY |
          GST_RTSP_RECORD |
          GST_RTSP_SETUP |
          GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN));

  return TRUE;
}

GST_START_TEST (test_options)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* simple OPTIONS */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_option_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

static void
test_describe_sub (const gchar * mount_point, const gchar * url)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* simple DESCRIBE for non-existing url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          url) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_404, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);

  /* simple DESCRIBE for an existing url */
  client = setup_client (NULL, mount_point, TRUE);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          url) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_START_TEST (test_describe)
{
  test_describe_sub ("/test", "rtsp://localhost/test");
}

GST_END_TEST;

GST_START_TEST (test_describe_root_mount_point)
{
  test_describe_sub ("/", "rtsp://localhost");
}

GST_END_TEST;

static const gchar *expected_transport = NULL;

static gboolean
test_setup_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  gchar *pattern;
  GstRTSPSessionPool *session_pool;
  GstRTSPSession *session;
  gchar **session_hdr_params;

  fail_unless (expected_transport != NULL);

  fail_unless_equals_int (gst_rtsp_message_get_type (response),
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless_equals_int (code, GST_RTSP_STS_OK);
  fail_unless_equals_string (reason, "OK");
  fail_unless_equals_int (version, GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_TRANSPORT,
          &str, 0) == GST_RTSP_OK);

  pattern = g_strdup_printf ("^%s$", expected_transport);
  fail_unless (g_regex_match_simple (pattern, str, 0, 0),
      "Transport '%s' doesn't match pattern '%s'", str, pattern);
  g_free (pattern);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION,
          &str, 0) == GST_RTSP_OK);
  session_hdr_params = g_strsplit (str, ";", -1);

  /* session-id value */
  fail_unless (session_hdr_params[0] != NULL);

  if (expected_session_timeout != 60) {
    /* session timeout param */
    gchar *timeout_str = g_strdup_printf ("timeout=%u",
        expected_session_timeout);

    fail_unless (session_hdr_params[1] != NULL);
    g_strstrip (session_hdr_params[1]);
    fail_unless (g_strcmp0 (session_hdr_params[1], timeout_str) == 0);
    g_free (timeout_str);
  }

  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);

  session = gst_rtsp_session_pool_find (session_pool, session_hdr_params[0]);
  g_strfreev (session_hdr_params);

  /* remember session id to be able to send teardown */
  if (session_id)
    g_free (session_id);
  session_id = g_strdup (gst_rtsp_session_get_sessionid (session));
  fail_unless (session_id != NULL);

  fail_unless (session != NULL);
  g_object_unref (session);

  g_object_unref (session_pool);


  return TRUE;
}

static gboolean
test_setup_response_461 (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;

  fail_unless (expected_transport == NULL);

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_UNSUPPORTED_TRANSPORT);
  fail_unless (g_str_equal (reason, "Unsupported transport"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);


  return TRUE;
}

static gboolean
test_teardown_response_200 (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static void
send_teardown (GstRTSPClient * client, const gchar * url)
{
  GstRTSPMessage request = { 0, };
  gchar *str;

  fail_unless (session_id != NULL);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN,
          url) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_teardown_response_200,
      NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  g_free (session_id);
  session_id = NULL;
}

static void
test_setup_tcp_sub (const gchar * mount_point, const gchar * url1,
    const gchar * url2)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL, mount_point, TRUE);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          url1) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast");

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  send_teardown (client, url2);
  teardown_client (client);
}

GST_START_TEST (test_setup_tcp)
{
  test_setup_tcp_sub ("/test", "rtsp://localhost/test/stream=0",
      "rtsp://localhost/test");
}

GST_END_TEST;

GST_START_TEST (test_setup_tcp_root_mount_point)
{
  test_setup_tcp_sub ("/", "rtsp://localhost/stream=0", "rtsp://localhost");
}

GST_END_TEST;

GST_START_TEST (test_setup_no_rtcp)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL, "/test", FALSE);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;unicast;client_port=5000-5001");

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  /* We want to verify that server_port holds a single number, not a range */
  expected_transport =
      "RTP/AVP;unicast;client_port=5000-5001;server_port=[0-9]+;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  send_teardown (client, "rtsp://localhost/test");
  teardown_client (client);
}

GST_END_TEST;

static void
test_setup_tcp_two_streams_same_channels_sub (const gchar * mount_point,
    const gchar * url1, const gchar * url2, const gchar * url3)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL, mount_point, TRUE);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  /* test SETUP of a video stream with 0-1 as interleaved channels */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          url1) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast;interleaved=0-1");
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* test SETUP of an audio stream with *the same* interleaved channels.
   * we expect the server to allocate new channel numbers */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          url2) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast;interleaved=0-1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=2-3;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client, url3);
  teardown_client (client);
}

GST_START_TEST (test_setup_tcp_two_streams_same_channels)
{
  test_setup_tcp_two_streams_same_channels_sub ("/test",
      "rtsp://localhost/test/stream=0", "rtsp://localhost/test/stream=1",
      "rtsp://localhost/test");
}

GST_END_TEST;

GST_START_TEST (test_setup_tcp_two_streams_same_channels_root_mount_point)
{
  test_setup_tcp_two_streams_same_channels_sub ("/",
      "rtsp://localhost/stream=0", "rtsp://localhost/stream=1",
      "rtsp://localhost");
}

GST_END_TEST;

static GstRTSPClient *
setup_multicast_client (guint max_ttl, const gchar * mount_point)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;

  client = gst_rtsp_client_new ();

  session_pool = gst_rtsp_session_pool_new ();
  gst_rtsp_client_set_session_pool (client, session_pool);

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (address_pool,
          "233.252.0.1", "233.252.0.1", 5000, 5010, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, mount_point, factory);
  gst_rtsp_client_set_mount_points (client, mount_points);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, max_ttl);

  thread_pool = gst_rtsp_thread_pool_new ();
  gst_rtsp_client_set_thread_pool (client, thread_pool);

  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);

  return client;
}

GST_START_TEST (test_client_multicast_transport_404)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1, "/test");

  /* simple SETUP for non-existing url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test2/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");

  gst_rtsp_client_set_send_func (client, test_response_404, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_END_TEST;

static void
new_session_cb (GObject * client, GstRTSPSession * session, gpointer user_data)
{
  GST_DEBUG ("%p: new session %p", client, session);
  gst_rtsp_session_set_timeout (session, expected_session_timeout);
}

GST_START_TEST (test_client_multicast_transport)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1, "/test");

  expected_session_timeout = 20;
  g_signal_connect (G_OBJECT (client), "new-session",
      G_CALLBACK (new_session_cb), NULL);

  /* simple SETUP with a valid URI and multicast */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");

  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;
  expected_session_timeout = 60;

  send_teardown (client, "rtsp://localhost/test");

  teardown_client (client);
}

GST_END_TEST;

GST_START_TEST (test_client_multicast_ignore_transport_specific)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1, "/test");

  /* simple SETUP with a valid URI and multicast and a specific dest,
   * but ignore it  */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast;destination=233.252.0.2;ttl=2;port=5001-5006;");

  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  send_teardown (client, "rtsp://localhost/test");

  teardown_client (client);
}

GST_END_TEST;

static void
multicast_transport_specific (void)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (1, "/test");

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  /* simple SETUP with a valid URI */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      expected_transport);

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (session_pool) == 1);
  g_object_unref (session_pool);

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client, "rtsp://localhost/test");
  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);
}

/* CASE: multicast address requested by the client exists in the address pool */
GST_START_TEST (test_client_multicast_transport_specific)
{
  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  multicast_transport_specific ();
  expected_transport = NULL;
}

GST_END_TEST;

/* CASE: multicast address requested by the client does not exist in the address pool */
GST_START_TEST (test_client_multicast_transport_specific_no_address_in_pool)
{
  expected_transport = "RTP/AVP;multicast;destination=234.252.0.3;"
      "ttl=1;port=10002-10004;mode=\"PLAY\"";
  multicast_transport_specific ();
  expected_transport = NULL;
}

GST_END_TEST;

static gboolean
test_response_sdp (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  guint8 *data;
  guint size;
  GstSDPMessage *sdp_msg;
  const GstSDPMedia *sdp_media;
  const GstSDPBandwidth *bw;
  gint bandwidth_val = GPOINTER_TO_INT (user_data);

  fail_unless (gst_rtsp_message_get_body (response, &data, &size)
      == GST_RTSP_OK);
  gst_sdp_message_new (&sdp_msg);
  fail_unless (gst_sdp_message_parse_buffer (data, size, sdp_msg)
      == GST_SDP_OK);

  /* session description */
  /* v= */
  fail_unless (gst_sdp_message_get_version (sdp_msg) != NULL);
  /* o= */
  fail_unless (gst_sdp_message_get_origin (sdp_msg) != NULL);
  /* s= */
  fail_unless (gst_sdp_message_get_session_name (sdp_msg) != NULL);
  /* t=0 0 */
  fail_unless (gst_sdp_message_times_len (sdp_msg) == 0);

  /* verify number of medias */
  fail_unless (gst_sdp_message_medias_len (sdp_msg) == 1);

  /* media description */
  sdp_media = gst_sdp_message_get_media (sdp_msg, 0);
  fail_unless (sdp_media != NULL);

  /* m= */
  fail_unless (gst_sdp_media_get_media (sdp_media) != NULL);

  /* media bandwidth */
  if (bandwidth_val) {
    fail_unless (gst_sdp_media_bandwidths_len (sdp_media) == 1);
    bw = gst_sdp_media_get_bandwidth (sdp_media, 0);
    fail_unless (bw != NULL);
    fail_unless (g_strcmp0 (bw->bwtype, "AS") == 0);
    fail_unless (bw->bandwidth == bandwidth_val);
  } else {
    fail_unless (gst_sdp_media_bandwidths_len (sdp_media) == 0);
  }

  gst_sdp_message_free (sdp_msg);

  return TRUE;
}

static void
test_client_sdp (const gchar * launch_line, guint * bandwidth_val)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  /* simple DESCRIBE for an existing url */
  client = setup_client (launch_line, "/test", TRUE);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_sdp,
      (gpointer) bandwidth_val, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_START_TEST (test_client_sdp_with_max_bitrate_tag)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"maximum-bitrate=(uint)50000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (50000));


  /* max-bitrate=0: no bandwidth line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_bitrate_tag)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (7000));

  /* bitrate=0: no bandwdith line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_max_bitrate_and_bitrate_tags)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000,maximum-bitrate=(uint)50000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (50000));

  /* max-bitrate is zero: fallback to bitrate */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000,maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (7000));

  /* max-bitrate=bitrate=0o: no bandwidth line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)0,maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_no_bitrate_tags)
{
  test_client_sdp ("videotestsrc "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96", NULL);
}

GST_END_TEST;

static void
mcast_transport_two_clients (gboolean shared, const gchar * transport1,
    const gchar * expected_transport1, const gchar * addr1,
    const gchar * transport2, const gchar * expected_transport2,
    const gchar * addr2, gboolean bind_mcast_address)
{
  GstRTSPClient *client1, *client2;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };
  GstRTSPContext ctx2 = { NULL };
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;
  gchar *session_id1;
  gchar *client_addr = NULL;

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  if (shared)
    gst_rtsp_media_factory_set_shared (factory, TRUE);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 5);
  gst_rtsp_media_factory_set_bind_mcast_address (factory, bind_mcast_address);
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (address_pool,
          "233.252.0.1", "233.252.0.1", 5000, 5001, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  session_pool = gst_rtsp_session_pool_new ();
  thread_pool = gst_rtsp_thread_pool_new ();

  /* first multicast client with transport specific request */
  client1 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client1, session_pool);
  gst_rtsp_client_set_mount_points (client1, mount_points);
  gst_rtsp_client_set_thread_pool (client1, thread_pool);

  ctx.client = client1;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  expected_transport = expected_transport1;

  /* send SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport1);

  gst_rtsp_client_set_send_func (client1, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client1, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* check address */
  client_addr = gst_rtsp_stream_get_multicast_client_addresses (ctx.stream);
  fail_if (client_addr == NULL);
  fail_unless (g_str_equal (client_addr, addr1));
  g_free (client_addr);

  gst_rtsp_context_pop_current (&ctx);
  session_id1 = g_strdup (session_id);

  /* second multicast client with transport specific request */
  cseq = 0;
  client2 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client2, session_pool);
  gst_rtsp_client_set_mount_points (client2, mount_points);
  gst_rtsp_client_set_thread_pool (client2, thread_pool);

  ctx2.client = client2;
  ctx2.auth = gst_rtsp_auth_new ();
  ctx2.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx2);

  expected_transport = expected_transport2;

  /* send SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport2);

  gst_rtsp_client_set_send_func (client2, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client2, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* check addresses */
  client_addr = gst_rtsp_stream_get_multicast_client_addresses (ctx2.stream);
  fail_if (client_addr == NULL);
  if (shared) {
    if (g_str_equal (addr1, addr2)) {
      fail_unless (g_str_equal (client_addr, addr1));
    } else {
      gchar *addr_str = g_strdup_printf ("%s,%s", addr2, addr1);
      fail_unless (g_str_equal (client_addr, addr_str));
      g_free (addr_str);
    }
  } else {
    fail_unless (g_str_equal (client_addr, addr2));
  }
  g_free (client_addr);

  send_teardown (client2, "rtsp://localhost/test");
  gst_rtsp_context_pop_current (&ctx2);

  gst_rtsp_context_push_current (&ctx);
  session_id = session_id1;
  send_teardown (client1, "rtsp://localhost/test");
  gst_rtsp_context_pop_current (&ctx);

  teardown_client (client1);
  teardown_client (client2);
  g_object_unref (ctx.auth);
  g_object_unref (ctx2.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_token_unref (ctx2.token);
  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);
}

/* CASE: media is shared.
 * client 1: SETUP    --->
 * client 1: PLAY     --->
 * client 2: SETUP    --->
 * client 1: TEARDOWN --->
 * client 2: PLAY     --->
 * client 2: TEARDOWN --->
 */
static void
mcast_transport_two_clients_teardown_play (const gchar * transport1,
    const gchar * expected_transport1, const gchar * transport2,
    const gchar * expected_transport2, gboolean bind_mcast_address,
    gboolean is_shared)
{
  GstRTSPClient *client1, *client2;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };
  GstRTSPContext ctx2 = { NULL };
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;
  gchar *session_id1, *session_id2;

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, is_shared);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 5);
  gst_rtsp_media_factory_set_bind_mcast_address (factory, bind_mcast_address);
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  if (is_shared)
    fail_unless (gst_rtsp_address_pool_add_range (address_pool,
            "233.252.0.1", "233.252.0.1", 5000, 5001, 1));
  else
    fail_unless (gst_rtsp_address_pool_add_range (address_pool,
            "233.252.0.1", "233.252.0.1", 5000, 5003, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  session_pool = gst_rtsp_session_pool_new ();
  thread_pool = gst_rtsp_thread_pool_new ();

  /* client 1 configuration */
  client1 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client1, session_pool);
  gst_rtsp_client_set_mount_points (client1, mount_points);
  gst_rtsp_client_set_thread_pool (client1, thread_pool);

  ctx.client = client1;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  expected_transport = expected_transport1;

  /* client 1 sends SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport1);

  gst_rtsp_client_set_send_func (client1, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;


  /* client 1 sends PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client1, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  gst_rtsp_context_pop_current (&ctx);
  session_id1 = g_strdup (session_id);

  /* client 2 configuration */
  cseq = 0;
  client2 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client2, session_pool);
  gst_rtsp_client_set_mount_points (client2, mount_points);
  gst_rtsp_client_set_thread_pool (client2, thread_pool);

  ctx2.client = client2;
  ctx2.auth = gst_rtsp_auth_new ();
  ctx2.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx2);

  expected_transport = expected_transport2;

  /* client 2 sends SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport2);

  gst_rtsp_client_set_send_func (client2, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  session_id2 = g_strdup (session_id);
  g_free (session_id);
  gst_rtsp_context_pop_current (&ctx2);

  /* the first client sends TEARDOWN request */
  gst_rtsp_context_push_current (&ctx);
  session_id = session_id1;
  send_teardown (client1, "rtsp://localhost/test");
  gst_rtsp_context_pop_current (&ctx);
  teardown_client (client1);

  /* the second client sends PLAY request */
  gst_rtsp_context_push_current (&ctx2);
  session_id = session_id2;
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client2, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* client 2 sends TEARDOWN request */
  send_teardown (client2, "rtsp://localhost/test");
  gst_rtsp_context_pop_current (&ctx2);

  teardown_client (client2);
  g_object_unref (ctx.auth);
  g_object_unref (ctx2.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_token_unref (ctx2.token);
  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);
}

/* test if two multicast clients can choose different transport settings
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients can choose different transport settings
 * CASE: media is not shared */
GST_START_TEST (test_client_multicast_transport_specific_two_clients)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients can choose the same ports but different
 * multicast destinations
 * CASE: media is not shared */
GST_START_TEST (test_client_multicast_transport_specific_two_clients_same_ports)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=9000-9001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:9000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9000-9001;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:9000";

  /* configure the multicast socket to be bound to the requested multicast address instead of INADDR_ANY.
   * The clients request the same rtp/rtcp borts and having the socket that are bound to ANY would result
   * in bind() failure */
  gboolean allow_bind_mcast_address = TRUE;

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, allow_bind_mcast_address);
}

GST_END_TEST;

/* test if two multicast clients can choose the same multicast destination but different
 * ports
 * CASE: media is not shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_same_destination) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9002-9003;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.2:9002";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9004-9005;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:9004";

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
/* test if two multicast clients can choose the same transport settings.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_shared_media_same_transport)
{

  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients get the same transport settings without
 * requesting specific transport.
 * CASE: media is shared */
GST_START_TEST (test_client_multicast_two_clients_shared_media)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if it's possible to play the shared media, after one of the clients
 * has terminated its session.
 */
GST_START_TEST (test_client_multicast_two_clients_shared_media_teardown_play)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;

  mcast_transport_two_clients_teardown_play (transport_client_1,
      expected_transport_1, transport_client_2, expected_transport_2, FALSE,
      TRUE);
}

GST_END_TEST;

/* test if it's possible to play the shared media, after one of the clients
 * has terminated its session.
 */
GST_START_TEST
    (test_client_multicast_two_clients_not_shared_media_teardown_play) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";

  mcast_transport_two_clients_teardown_play (transport_client_1,
      expected_transport_1, transport_client_2, expected_transport_2, FALSE,
      FALSE);
}

GST_END_TEST;

/* test if two multicast clients get the different transport settings: the first client
 * requests the specific transport configuration while the second client lets
 * the server select the multicast address and the ports.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_two_clients_first_specific_transport_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
/* test if two multicast clients get the different transport settings: the first client lets
 * the server select the multicast address and the ports while the second client requests
 * the specific transport configuration.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_two_clients_second_specific_transport_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=2;port=5004-5005;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5004";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if the maximum ttl multicast value is chosen by the server
 * CASE: the first client provides the highest ttl value */
GST_START_TEST (test_client_multicast_max_ttl_first_client)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=3;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 =
      "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=3;port=5002-5003;mode=\"PLAY\"";
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if the maximum ttl multicast value is chosen by the server
 * CASE: the second client provides the highest ttl value */
GST_START_TEST (test_client_multicast_max_ttl_second_client)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=2;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=4;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
GST_START_TEST (test_client_multicast_invalid_ttl)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (3, "/test");

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  /* simple SETUP with an invalid ttl=0 */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast;destination=233.252.0.1;ttl=0;port=5000-5001;");

  gst_rtsp_client_set_send_func (client, test_setup_response_461, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (session_pool) == 0);
  g_object_unref (session_pool);

  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);
}

GST_END_TEST;

static gboolean
test_response_scale_speed (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *header_value;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_RANGE,
          &header_value, 0) == GST_RTSP_OK);

  if (expected_scale_header != NULL) {
    fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SCALE,
            &header_value, 0) == GST_RTSP_OK);
    ck_assert_str_eq (header_value, expected_scale_header);
  } else {
    fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SCALE,
            &header_value, 0) == GST_RTSP_ENOTIMPL);
  }

  if (expected_speed_header != NULL) {
    fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SPEED,
            &header_value, 0) == GST_RTSP_OK);
    ck_assert_str_eq (header_value, expected_speed_header);
  } else {
    fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SPEED,
            &header_value, 0) == GST_RTSP_ENOTIMPL);
  }

  return TRUE;
}

/* Probe that tweaks segment events according to the values of the
 * fake_rate_value and fake_applied_rate_value variables. Used to simulate
 * seek results with different combinations of rate and applied rate.
 */
static GstPadProbeReturn
rate_tweaking_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
  GstSegment segment;

  if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
    GST_DEBUG ("got segment event %" GST_PTR_FORMAT, event);
    gst_event_copy_segment (event, &segment);
    if (fake_applied_rate_value)
      segment.applied_rate = fake_applied_rate_value;
    if (fake_rate_value)
      segment.rate = fake_rate_value;
    gst_event_unref (event);
    info->data = gst_event_new_segment (&segment);
    GST_DEBUG ("forwarding segment event %" GST_PTR_FORMAT,
        GST_EVENT (info->data));
  }

  return GST_PAD_PROBE_OK;
}

static void
attach_rate_tweaking_probe (void)
{
  GstRTSPContext *ctx;
  GstRTSPMedia *media;
  GstRTSPStream *stream;
  GstPad *srcpad;

  fail_unless ((ctx = gst_rtsp_context_get_current ()) != NULL);

  media = ctx->media;
  fail_unless (media != NULL);
  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);

  srcpad = gst_rtsp_stream_get_srcpad (stream);
  fail_unless (srcpad != NULL);

  GST_DEBUG ("adding rate_tweaking_probe");

  gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
      rate_tweaking_probe, NULL, NULL);
  gst_object_unref (srcpad);
}

static void
do_test_scale_and_speed (const gchar * scale, const gchar * speed,
    GstRTSPStatusCode expected_response_code)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (1, "/test");

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  expected_session_timeout = 20;
  g_signal_connect (G_OBJECT (client), "new-session",
      G_CALLBACK (new_session_cb), NULL);

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");
  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=.*;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;
  expected_session_timeout = 60;

  if (fake_applied_rate_value || fake_rate_value)
    attach_rate_tweaking_probe ();

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);

  if (scale != NULL)
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, scale);
  if (speed != NULL)
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, speed);

  if (expected_response_code == GST_RTSP_STS_BAD_REQUEST)
    gst_rtsp_client_set_send_func (client, test_response_400, NULL, NULL);
  else
    gst_rtsp_client_set_send_func (client, test_response_scale_speed, NULL,
        NULL);

  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client, "rtsp://localhost/test");
  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);

}

GST_START_TEST (test_scale_and_speed)
{
  /* no scale/speed requested, no scale/speed should be received */
  expected_scale_header = NULL;
  expected_speed_header = NULL;
  do_test_scale_and_speed (NULL, NULL, GST_RTSP_STS_OK);

  /* scale requested, scale should be received */
  fake_applied_rate_value = 2;
  fake_rate_value = 1;
  expected_scale_header = "2.000";
  expected_speed_header = NULL;
  do_test_scale_and_speed ("2.000", NULL, GST_RTSP_STS_OK);

  /* speed requested, speed should be received */
  fake_applied_rate_value = 0;
  fake_rate_value = 0;
  expected_scale_header = NULL;
  expected_speed_header = "2.000";
  do_test_scale_and_speed (NULL, "2.000", GST_RTSP_STS_OK);

  /* both requested, both should be received */
  fake_applied_rate_value = 2;
  fake_rate_value = 2;
  expected_scale_header = "2.000";
  expected_speed_header = "2.000";
  do_test_scale_and_speed ("2", "2", GST_RTSP_STS_OK);

  /* scale requested but media doesn't handle scaling so both should be
   * received, with scale set to 1.000 and speed set to (requested scale
   * requested speed) */
  fake_applied_rate_value = 0;
  fake_rate_value = 5;
  expected_scale_header = "1.000";
  expected_speed_header = "5.000";
  do_test_scale_and_speed ("5", NULL, GST_RTSP_STS_OK);

  /* both requested but media only handles scaling so both should be received,
   * with scale set to (requested scale * requested speed) and speed set to 1.00
   */
  fake_rate_value = 1.000;
  fake_applied_rate_value = 4.000;
  expected_scale_header = "4.000";
  expected_speed_header = "1.000";
  do_test_scale_and_speed ("2", "2", GST_RTSP_STS_OK);

  /* test invalid values */
  fake_applied_rate_value = 0;
  fake_rate_value = 0;
  expected_scale_header = NULL;
  expected_speed_header = NULL;

  /* scale or speed not decimal values */
  do_test_scale_and_speed ("x", NULL, GST_RTSP_STS_BAD_REQUEST);
  do_test_scale_and_speed (NULL, "y", GST_RTSP_STS_BAD_REQUEST);

  /* scale or speed illegal decimal values */
  do_test_scale_and_speed ("0", NULL, GST_RTSP_STS_BAD_REQUEST);
  do_test_scale_and_speed (NULL, "0", GST_RTSP_STS_BAD_REQUEST);
  do_test_scale_and_speed (NULL, "-2", GST_RTSP_STS_BAD_REQUEST);
}

GST_END_TEST static void
test_client_play_sub (const gchar * mount_point, const gchar * url1,
    const gchar * url2)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (1, mount_point);

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          url1) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");
  /* destination is from adress pool */
  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=.*;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          url2) == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_response_play_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client, url2);
  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);
}

GST_START_TEST (test_client_play)
{
  test_client_play_sub ("/test", "rtsp://localhost/test/stream=0",
      "rtsp://localhost/test");
}

GST_END_TEST;

GST_START_TEST (test_client_play_root_mount_point)
{
  test_client_play_sub ("/", "rtsp://localhost/stream=0", "rtsp://localhost");
}

GST_END_TEST;

#define RTSP_CLIENT_TEST_TYPE (rtsp_client_test_get_type ())
#define RTSP_CLIENT_TEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), RTSP_CLIENT_TEST_TYPE, RtspClientTestClass))

typedef struct RtspClientTest
{
  GstRTSPClient parent;
} RtspClientTest;

typedef struct RtspClientTestClass
{
  GstRTSPClientClass parent_class;
} RtspClientTestClass;

GType rtsp_client_test_get_type (void);

G_DEFINE_TYPE (RtspClientTest, rtsp_client_test, GST_TYPE_RTSP_CLIENT);

static void
rtsp_client_test_init (RtspClientTest * client)
{
}

static void
rtsp_client_test_class_init (RtspClientTestClass * klass)
{
}

static GstRTSPStatusCode
adjust_error_code_cb (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPStatusCode code)
{
  return GST_RTSP_STS_NOT_FOUND;
}

GST_START_TEST (test_adjust_error_code)
{
  RtspClientTest *client;
  RtspClientTestClass *klass;
  GstRTSPClientClass *base_klass;
  GstRTSPMessage request = { 0, };

  client = g_object_new (RTSP_CLIENT_TEST_TYPE, NULL);

  /* invalid request to trigger error response */
  ck_assert (gst_rtsp_message_init_request (&request, GST_RTSP_INVALID,
          "foopy://padoop/") == GST_RTSP_OK);

  /* expect non-adjusted error response 400 */
  gst_rtsp_client_set_send_func (GST_RTSP_CLIENT (client), test_response_400,
      NULL, NULL);
  ck_assert (gst_rtsp_client_handle_message (GST_RTSP_CLIENT (client),
          &request) == GST_RTSP_OK);

  /* override virtual function for adjusting error code */
  klass = RTSP_CLIENT_TEST_GET_CLASS (client);
  base_klass = GST_RTSP_CLIENT_CLASS (klass);
  base_klass->adjust_error_code = adjust_error_code_cb;

  /* expect error adjusted to 404 */
  gst_rtsp_client_set_send_func (GST_RTSP_CLIENT (client), test_response_404,
      NULL, NULL);
  ck_assert (gst_rtsp_client_handle_message (GST_RTSP_CLIENT (client),
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);
  g_object_unref (client);
}

GST_END_TEST;

static GstRTSPFilterResult
filter_session_media (GstRTSPSession * sess, GstRTSPSessionMedia * sessmedia,
    gpointer user_data)
{
  GstRTSPMedia *media;

  media = gst_rtsp_session_media_get_media (sessmedia);
  /* expect the media to remain valid after a pre-closed notification */
  ck_assert_ptr_ne (media, NULL);

  return GST_RTSP_FILTER_KEEP;
}

static GstRTSPFilterResult
cleanup_session (GstRTSPClient * client, GstRTSPSession * sess,
    gpointer user_data)
{
  GList *tmp G_GNUC_UNUSED;
  GST_INFO ("session is about to be terminated");

  tmp = gst_rtsp_session_filter (sess, filter_session_media, user_data);
  g_assert (tmp == NULL);

  return GST_RTSP_FILTER_KEEP;
}

static void
client_pre_closed_cb (GstRTSPClient * client, gpointer user_data)
{
  GList *tmp G_GNUC_UNUSED;
  gboolean *pre_closed_called = (gboolean *) user_data;

  *pre_closed_called = TRUE;
  tmp = gst_rtsp_client_session_filter (client, cleanup_session, NULL);
  g_assert (tmp == NULL);
}


/* CASE: a client terminates its session by sending a TCP RST.
 * The application stll has a possibility to retrieve information about
 * the client's session before the client's data is cleaned up. */
GST_START_TEST (test_pre_closed)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPContext ctx = { NULL };
  gboolean pre_closed_called = FALSE;

  client = setup_multicast_client (1, "/test");
  ctx.client = client;
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));
  ctx.conn = conn;
  gst_rtsp_context_push_current (&ctx);

  /* request a notification when the client's connection terminates */
  g_signal_connect (G_OBJECT (client), "pre-closed",
      G_CALLBACK (client_pre_closed_cb), &pre_closed_called);

  /* send SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");

  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  ck_assert (ctx.conn != NULL);

  /* prepare for a "connection closed" notification */
  gst_rtsp_client_attach (client, g_main_context_default ());

  /* close the connection */
  gst_rtsp_connection_close (ctx.conn);
  while (!g_main_context_iteration (NULL, TRUE));

  /* expect pre-closed signal being emitted */
  ck_assert (pre_closed_called);

  teardown_client (client);
  gst_rtsp_context_pop_current (&ctx);
}

GST_END_TEST;

static Suite *
rtspclient_suite (void)
{
  Suite *s = suite_create ("rtspclient");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_require);
  tcase_add_test (tc, test_request);
  tcase_add_test (tc, test_options);
  tcase_add_test (tc, test_describe);
  tcase_add_test (tc, test_describe_root_mount_point);
  tcase_add_test (tc, test_setup_tcp);
  tcase_add_test (tc, test_setup_tcp_root_mount_point);
  tcase_add_test (tc, test_setup_no_rtcp);
  tcase_add_test (tc, test_setup_tcp_two_streams_same_channels);
  tcase_add_test (tc,
      test_setup_tcp_two_streams_same_channels_root_mount_point);
  tcase_add_test (tc, test_client_multicast_transport_404);
  tcase_add_test (tc, test_client_multicast_transport);
  tcase_add_test (tc, test_client_multicast_ignore_transport_specific);
  tcase_add_test (tc, test_client_multicast_transport_specific);
  tcase_add_test (tc, test_client_sdp_with_max_bitrate_tag);
  tcase_add_test (tc, test_client_sdp_with_bitrate_tag);
  tcase_add_test (tc, test_client_sdp_with_max_bitrate_and_bitrate_tags);
  tcase_add_test (tc, test_client_sdp_with_no_bitrate_tags);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_shared_media);
  tcase_add_test (tc, test_client_multicast_transport_specific_two_clients);
#ifndef G_OS_WIN32
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_same_ports);
#else
  /* skip the test on windows as the test restricts the multicast sockets to multicast traffic only,
   * by specifying the multicast IP as the bind address and this currently doesn't work on Windows */
  tcase_skip_broken_test (tc,
      test_client_multicast_transport_specific_two_clients_same_ports);
#endif
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_same_destination);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_shared_media_same_transport);
  tcase_add_test (tc, test_client_multicast_two_clients_shared_media);
  tcase_add_test (tc,
      test_client_multicast_two_clients_shared_media_teardown_play);
  tcase_add_test (tc,
      test_client_multicast_two_clients_not_shared_media_teardown_play);
  tcase_add_test (tc,
      test_client_multicast_two_clients_first_specific_transport_shared_media);
  tcase_add_test (tc,
      test_client_multicast_two_clients_second_specific_transport_shared_media);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_no_address_in_pool);
  tcase_add_test (tc, test_client_multicast_max_ttl_first_client);
  tcase_add_test (tc, test_client_multicast_max_ttl_second_client);
  tcase_add_test (tc, test_client_multicast_invalid_ttl);
  tcase_add_test (tc, test_scale_and_speed);
  tcase_add_test (tc, test_client_play);
  tcase_add_test (tc, test_client_play_root_mount_point);
  tcase_add_test (tc, test_adjust_error_code);
  tcase_add_test (tc, test_pre_closed);

  return s;
}

GST_CHECK_MAIN (rtspclient);
 07070100000077000081A400000000000000000000000169D554BF00007330000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/tests/check/gst/media.c    /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-media-factory.h>

/* Check if the media can return a SDP. We don't actually check whether
 * the contents are valid or not */
static gboolean
media_has_sdp (GstRTSPMedia * media)
{
  GstSDPInfo info;
  GstSDPMessage *sdp;
  gchar *sdp_str;

  info.is_ipv6 = FALSE;
  info.server_ip = "0.0.0.0";

  /* Check if media can generate a SDP */
  gst_sdp_message_new (&sdp);
  GST_DEBUG ("Getting SDP");
  if (!gst_rtsp_sdp_from_media (sdp, &info, media)) {
    GST_WARNING ("failed to get the SDP");
    gst_sdp_message_free (sdp);
    return FALSE;
  }
  sdp_str = gst_sdp_message_as_text (sdp);
  GST_DEBUG ("Got SDP\n%s", sdp_str);
  g_free (sdp_str);
  gst_sdp_message_free (sdp);

  return TRUE;
}

GST_START_TEST (test_media_seek)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;
  GstRTSPTimeRange *range;
  gchar *str;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPTransport *transport;
  gdouble rate = 0;
  gdouble applied_rate = 0;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));

  /* define transport */
  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_TCP;

  fail_unless (gst_rtsp_stream_complete_stream (stream, transport));

  fail_unless (gst_rtsp_transport_free (transport) == GST_RTSP_OK);
  fail_unless (gst_rtsp_range_parse ("npt=5.0-", &range) == GST_RTSP_OK);

  /* the media is seekable now */
  fail_unless (gst_rtsp_media_seek (media, range));

  str = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  fail_unless (g_str_equal (str, "npt=5-"));
  g_free (str);

  /* seeking without rate should result in rate == 1.0 */
  fail_unless (gst_rtsp_media_seek (media, range));
  fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate));
  fail_unless (rate == 1.0);
  fail_unless (applied_rate == 1.0);

  /* seeking with rate set to 1.5 should result in rate == 1.5 */
  fail_unless (gst_rtsp_media_seek_trickmode (media, range,
          GST_SEEK_FLAG_NONE, 1.5, 0));
  fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate));
  fail_unless (rate == 1.5);
  fail_unless (applied_rate == 1.0);

  gst_rtsp_range_free (range);

  /* seeking with rate set to -2.0 should result in rate == -2.0 */
  fail_unless (gst_rtsp_range_parse ("npt=10-5", &range) == GST_RTSP_OK);
  fail_unless (gst_rtsp_media_seek_trickmode (media, range,
          GST_SEEK_FLAG_NONE, -2.0, 0));
  fail_unless (gst_rtsp_media_get_rates (media, &rate, &applied_rate));
  fail_unless (rate == -2.0);
  fail_unless (applied_rate == 1.0);

  gst_rtsp_range_free (range);

  fail_unless (gst_rtsp_media_unprepare (media));
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

GST_END_TEST;

static void
media_playback_seek_one_active_stream (const gchar * launch_line)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream1;
  GstRTSPStream *stream2;
  GstRTSPTimeRange *range;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPTransport *transport;
  char *range_str;
  GstRTSPTimeRange *play_range;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory, launch_line);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 2);

  stream1 = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream1 != NULL);

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));

  /* define transport */
  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_TCP;

  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);

  /* video stream is complete and seekable */
  fail_unless (gst_rtsp_stream_complete_stream (stream1, transport));
  fail_unless (gst_rtsp_stream_seekable (stream1));

  /* audio stream is blocked (it does not contain any transport based part),
   * but it's seekable */
  stream2 = gst_rtsp_media_get_stream (media, 1);
  fail_unless (stream2 != NULL);
  fail_unless (gst_rtsp_stream_seekable (stream2));

  fail_unless (gst_rtsp_transport_free (transport) == GST_RTSP_OK);
  fail_unless (gst_rtsp_range_parse ("npt=3.0-5.0", &range) == GST_RTSP_OK);

  /* the media is seekable now */
  fail_unless (gst_rtsp_media_seek (media, range));

  /* verify that we got the expected range, 'npt=3.0-5.0' */
  range_str = gst_rtsp_media_get_range_string (media, TRUE, GST_RTSP_RANGE_NPT);
  fail_unless (gst_rtsp_range_parse (range_str, &play_range) == GST_RTSP_OK);
  fail_unless (play_range->min.seconds == range->min.seconds);
  fail_unless (play_range->max.seconds == range->max.seconds);

  gst_rtsp_range_free (range);
  gst_rtsp_range_free (play_range);
  g_free (range_str);

  fail_unless (gst_rtsp_media_unprepare (media));
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

/* case: media is complete and contains two streams but only one is active,
   audio & video sources */
GST_START_TEST (test_media_playback_seek_one_active_stream)
{
  media_playback_seek_one_active_stream
      ("( videotestsrc ! rtpvrawpay pt=96 name=pay0 "
      " audiotestsrc ! audioconvert ! rtpL16pay name=pay1 )");
}

GST_END_TEST;

/* case: media is complete and contains two streams but only one is active,
   demux */
GST_START_TEST (test_media_playback_demux_seek_one_active_stream)
{
  /* FIXME: this test produces "Failed to push event" error messages in the
   * GST_DEBUG logs because the incomplete stream has no sinks */
  media_playback_seek_one_active_stream ("( filesrc location="
      GST_TEST_FILES_PATH "/test.avi !"
      " avidemux name=demux demux.audio_0 ! queue ! decodebin ! audioconvert !"
      " audioresample ! rtpL16pay pt=97 name=pay1"
      " demux.video_0 ! queue ! decodebin ! rtpvrawpay pt=96 name=pay0 )");
}

GST_END_TEST;

GST_START_TEST (test_media_seek_no_sinks)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;
  GstRTSPTimeRange *range;
  gchar *str;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);

  /* fails, need to be prepared */
  str = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  fail_unless (str == NULL);

  fail_unless (gst_rtsp_range_parse ("npt=5.0-", &range) == GST_RTSP_OK);
  /* fails, need to be prepared */
  fail_if (gst_rtsp_media_seek (media, range));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));

  str = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  fail_unless (g_str_equal (str, "npt=0-"));
  g_free (str);

  str = gst_rtsp_media_get_range_string (media, TRUE, GST_RTSP_RANGE_NPT);
  fail_unless (g_str_equal (str, "npt=0-"));
  g_free (str);

  /* fails, need to be prepared and contain sink elements */
  fail_if (gst_rtsp_media_seek (media, range));

  fail_unless (gst_rtsp_media_unprepare (media));

  /* should fail again */
  str = gst_rtsp_media_get_range_string (media, FALSE, GST_RTSP_RANGE_NPT);
  fail_unless (str == NULL);
  fail_if (gst_rtsp_media_seek (media, range));

  gst_rtsp_range_free (range);
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_media)
{
  GstRTSPMedia *media;
  GstElement *bin, *e1, *e2;

  bin = gst_bin_new ("bin");
  fail_if (bin == NULL);

  e1 = gst_element_factory_make ("videotestsrc", NULL);
  fail_if (e1 == NULL);

  e2 = gst_element_factory_make ("rtpvrawpay", "pay0");
  fail_if (e2 == NULL);
  g_object_set (e2, "pt", 96, NULL);

  gst_bin_add_many (GST_BIN_CAST (bin), e1, e2, NULL);
  gst_element_link_many (e1, e2, NULL);

  media = gst_rtsp_media_new (bin);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  g_object_unref (media);
}

GST_END_TEST;

static void
test_prepare_reusable (const gchar * launch_line, gboolean is_live)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPThread *thread;
  GstRTSPThreadPool *pool;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory, launch_line);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  g_object_set (G_OBJECT (media), "reusable", TRUE, NULL);

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));
  if (is_live) {                /* Live is not seekable */
    fail_unless_equals_int64 (gst_rtsp_media_seekable (media), -1);
  } else {
    fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  }
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));
  fail_unless (gst_rtsp_media_unprepare (media));

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

GST_START_TEST (test_media_reusable)
{

  /* test reusable media */
  test_prepare_reusable ("( videotestsrc ! rtpvrawpay pt=96 name=pay0 )",
      FALSE);
  test_prepare_reusable
      ("( videotestsrc is-live=true ! rtpvrawpay pt=96 name=pay0 )", TRUE);
}

GST_END_TEST;

GST_START_TEST (test_media_prepare)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  pool = gst_rtsp_thread_pool_new ();

  /* test non-reusable media first */
  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_if (gst_rtsp_media_prepare (media, thread));

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

GST_END_TEST;

enum _SyncState
{
  SYNC_STATE_INIT,
  SYNC_STATE_1,
  SYNC_STATE_2,
  SYNC_STATE_RACE
};
typedef enum _SyncState SyncState;

struct _help_thread_data
{
  GstRTSPThreadPool *pool;
  GstRTSPMedia *media;
  GstRTSPTransport *transport;
  GstRTSPStream *stream;
  SyncState *state;
  GMutex *sync_mutex;
  GCond *sync_cond;
};
typedef struct _help_thread_data help_thread_data;

static gpointer
help_thread_main (gpointer user_data)
{
  help_thread_data *data;
  GstRTSPThread *thread;
  GPtrArray *transports;
  GstRTSPStreamTransport *stream_transport;

  data = (help_thread_data *) user_data;
  GST_INFO ("Another thread sharing media");

  /* wait SYNC_STATE_1 */
  g_mutex_lock (data->sync_mutex);
  while (*data->state < SYNC_STATE_1)
    g_cond_wait (data->sync_cond, data->sync_mutex);
  g_mutex_unlock (data->sync_mutex);

  /* prepare */
  thread = gst_rtsp_thread_pool_get_thread (data->pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (data->media, thread));

  /* set SYNC_STATE_2 */
  g_mutex_lock (data->sync_mutex);
  *data->state = SYNC_STATE_2;
  g_cond_signal (data->sync_cond);
  g_mutex_unlock (data->sync_mutex);

  /* wait SYNC_STATE_RACE */
  g_mutex_lock (data->sync_mutex);
  while (*data->state < SYNC_STATE_RACE)
    g_cond_wait (data->sync_cond, data->sync_mutex);
  g_mutex_unlock (data->sync_mutex);

  /* set state */
  transports = g_ptr_array_new_with_free_func (g_object_unref);
  fail_unless (transports != NULL);
  stream_transport =
      gst_rtsp_stream_transport_new (data->stream, data->transport);
  fail_unless (stream_transport != NULL);
  g_ptr_array_add (transports, stream_transport);
  fail_unless (gst_rtsp_media_set_state (data->media, GST_STATE_NULL,
          transports));

  /* clean up */
  GST_INFO ("Thread exit");
  fail_unless (gst_rtsp_media_unprepare (data->media));
  g_ptr_array_unref (transports);
  return NULL;
}

GST_START_TEST (test_media_shared_race_test_unsuspend_vs_set_state_null)
{
  help_thread_data data;
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GThread *sharing_media_thread;
  GstRTSPTransport *transport;
  GstRTSPStream *stream;
  SyncState state = SYNC_STATE_INIT;
  GMutex sync_mutex;
  GCond sync_cond;

  g_mutex_init (&sync_mutex);
  g_cond_init (&sync_cond);

  pool = gst_rtsp_thread_pool_new ();

  /* test non-reusable media first */
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);
  gst_rtsp_media_set_suspend_mode (media, GST_RTSP_SUSPEND_MODE_RESET);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));

  /* help thread */
  data.pool = pool;
  data.media = media;
  data.stream = stream;
  data.state = &state;
  data.sync_mutex = &sync_mutex;
  data.sync_cond = &sync_cond;
  sharing_media_thread = g_thread_new ("new thread", help_thread_main, &data);
  fail_unless (sharing_media_thread != NULL);

  /* set state SYNC_STATE_1 */
  g_mutex_lock (&sync_mutex);
  state = SYNC_STATE_1;
  g_cond_signal (&sync_cond);
  g_mutex_unlock (&sync_mutex);

  /* wait SYNC_STATE_2 */
  g_mutex_lock (&sync_mutex);
  while (state < SYNC_STATE_2)
    g_cond_wait (&sync_cond, &sync_mutex);
  g_mutex_unlock (&sync_mutex);

  gst_rtsp_media_suspend (media);

  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_TCP;
  fail_unless (gst_rtsp_stream_complete_stream (stream, transport));
  data.transport = transport;

  /* set state SYNC_STATE_RACE let the race begin unsuspend <-> set state GST_STATE_NULL */
  g_mutex_lock (&sync_mutex);
  state = SYNC_STATE_RACE;
  g_cond_signal (&sync_cond);
  g_mutex_unlock (&sync_mutex);

  fail_unless (gst_rtsp_media_unsuspend (media));

  /* sync end of other thread */
  g_thread_join (sharing_media_thread);

  /* clean up */
  g_cond_clear (&sync_cond);
  g_mutex_clear (&sync_mutex);
  fail_unless (gst_rtsp_media_unprepare (media));
  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;


#define FLAG_HAVE_CAPS GST_ELEMENT_FLAG_LAST
static void
on_notify_caps (GstPad * pad, GParamSpec * pspec, GstElement * pay)
{
  GstCaps *caps;

  g_object_get (pad, "caps", &caps, NULL);

  GST_DEBUG ("notify %" GST_PTR_FORMAT, caps);

  if (caps) {
    if (!GST_OBJECT_FLAG_IS_SET (pay, FLAG_HAVE_CAPS)) {
      g_signal_emit_by_name (pay, "pad-added", pad);
      g_signal_emit_by_name (pay, "no-more-pads", NULL);
      GST_OBJECT_FLAG_SET (pay, FLAG_HAVE_CAPS);
    }
    gst_caps_unref (caps);
  } else {
    if (GST_OBJECT_FLAG_IS_SET (pay, FLAG_HAVE_CAPS)) {
      g_signal_emit_by_name (pay, "pad-removed", pad);
      GST_OBJECT_FLAG_UNSET (pay, FLAG_HAVE_CAPS);
    }
  }
}

GST_START_TEST (test_media_dyn_prepare)
{
  GstRTSPMedia *media;
  GstElement *bin, *src, *pay;
  GstElement *pipeline;
  GstPad *srcpad;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  bin = gst_bin_new ("bin");
  fail_if (bin == NULL);

  src = gst_element_factory_make ("videotestsrc", NULL);
  fail_if (src == NULL);

  pay = gst_element_factory_make ("rtpvrawpay", "dynpay0");
  fail_if (pay == NULL);
  g_object_set (pay, "pt", 96, NULL);

  gst_bin_add_many (GST_BIN_CAST (bin), src, pay, NULL);
  gst_element_link_many (src, pay, NULL);

  media = gst_rtsp_media_new (bin);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  g_object_set (G_OBJECT (media), "reusable", TRUE, NULL);

  pipeline = gst_pipeline_new ("media-pipeline");
  gst_rtsp_media_take_pipeline (media, GST_PIPELINE_CAST (pipeline));

  gst_rtsp_media_collect_streams (media);

  srcpad = gst_element_get_static_pad (pay, "src");

  g_signal_connect (srcpad, "notify::caps", (GCallback) on_notify_caps, pay);

  pool = gst_rtsp_thread_pool_new ();

  fail_unless (gst_rtsp_media_n_streams (media) == 0);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 0);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (gst_rtsp_media_n_streams (media) == 1);
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless (gst_rtsp_media_n_streams (media) == 0);

  gst_object_unref (srcpad);
  g_object_unref (media);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_media_take_pipeline)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstElement *pipeline;

  factory = gst_rtsp_media_factory_new ();
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);
  gst_rtsp_media_factory_set_launch (factory,
      "( fakesrc ! text/plain ! rtpgstpay name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  pipeline = gst_pipeline_new ("media-pipeline");
  gst_rtsp_media_take_pipeline (media, GST_PIPELINE_CAST (pipeline));

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_media_reset)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  pool = gst_rtsp_thread_pool_new ();

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  gst_rtsp_url_parse ("rtsp://localhost:8554/test", &url);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_suspend (media));
  fail_unless (gst_rtsp_media_unprepare (media));
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  gst_rtsp_media_set_suspend_mode (media, GST_RTSP_SUSPEND_MODE_RESET);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_suspend (media));
  fail_unless (gst_rtsp_media_unprepare (media));
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_media_multidyn_prepare)
{
  GstRTSPMedia *media;
  GstElement *bin, *src0, *pay0, *src1, *pay1;
  GstElement *pipeline;
  GstPad *srcpad0, *srcpad1;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  bin = gst_bin_new ("bin");
  fail_if (bin == NULL);

  src0 = gst_element_factory_make ("videotestsrc", NULL);
  fail_if (src0 == NULL);

  pay0 = gst_element_factory_make ("rtpvrawpay", "dynpay0");
  fail_if (pay0 == NULL);
  g_object_set (pay0, "pt", 96, NULL);

  src1 = gst_element_factory_make ("videotestsrc", NULL);
  fail_if (src1 == NULL);

  pay1 = gst_element_factory_make ("rtpvrawpay", "dynpay1");
  fail_if (pay1 == NULL);
  g_object_set (pay1, "pt", 97, NULL);

  gst_bin_add_many (GST_BIN_CAST (bin), src0, pay0, src1, pay1, NULL);
  gst_element_link_many (src0, pay0, NULL);
  gst_element_link_many (src1, pay1, NULL);

  media = gst_rtsp_media_new (bin);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  g_object_set (G_OBJECT (media), "reusable", TRUE, NULL);

  pipeline = gst_pipeline_new ("media-pipeline");
  gst_rtsp_media_take_pipeline (media, GST_PIPELINE_CAST (pipeline));

  gst_rtsp_media_collect_streams (media);

  srcpad0 = gst_element_get_static_pad (pay0, "src");
  srcpad1 = gst_element_get_static_pad (pay1, "src");

  g_signal_connect (srcpad0, "notify::caps", (GCallback) on_notify_caps, pay0);
  g_signal_connect (srcpad1, "notify::caps", (GCallback) on_notify_caps, pay1);

  pool = gst_rtsp_thread_pool_new ();

  fail_unless_equals_int (gst_rtsp_media_n_streams (media), 0);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless_equals_int (gst_rtsp_media_n_streams (media), 2);
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless_equals_int (gst_rtsp_media_n_streams (media), 0);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  fail_unless (gst_rtsp_media_prepare (media, thread));
  fail_unless_equals_int (gst_rtsp_media_n_streams (media), 2);
  fail_unless (media_has_sdp (media));
  fail_unless_equals_int64 (gst_rtsp_media_seekable (media), G_MAXINT64);
  fail_unless (gst_rtsp_media_unprepare (media));
  fail_unless_equals_int (gst_rtsp_media_n_streams (media), 0);

  gst_object_unref (srcpad0);
  gst_object_unref (srcpad1);
  g_object_unref (media);
  g_object_unref (pool);
}

GST_END_TEST;

static gboolean
pipeline_error (GstRTSPMedia * media, GstMessage * message, guint * data)
{
  GError *gerror = NULL;

  /* verify that the correct error was received */
  gst_message_parse_error (message, &gerror, NULL);
  ck_assert_str_eq (GST_MESSAGE_SRC_NAME (message), "src0");
  ck_assert_ptr_ne (gerror, NULL);
  ck_assert_int_eq (gerror->domain, GST_STREAM_ERROR);
  ck_assert_int_eq (gerror->code, GST_STREAM_ERROR_FAILED);
  ck_assert_str_eq (gerror->message, "Internal data stream error.");
  (*data)++;
  g_error_free (gerror);

  return TRUE;
}

GST_START_TEST (test_media_pipeline_error)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  guint handled_messages = 0;

  pool = gst_rtsp_thread_pool_new ();

  factory = gst_rtsp_media_factory_new ();
  ck_assert (!gst_rtsp_media_factory_is_shared (factory));
  ck_assert (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  /* add faulty caps filter to fail linking when preparing media, this will
   * result in an error being posted on the pipelines bus. */
  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc name=src0 ! video/fail_prepare ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  ck_assert (GST_IS_RTSP_MEDIA (media));
  ck_assert_int_eq (gst_rtsp_media_n_streams (media), 1);

  /* subscribe to pipeline errors */
  g_signal_connect (media, "handle-message::error", G_CALLBACK (pipeline_error),
      &handled_messages);

  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);
  ck_assert (!gst_rtsp_media_prepare (media, thread));
  ck_assert_uint_eq (handled_messages, 1);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);

  g_object_unref (pool);
}

GST_END_TEST;



static void
teardown (void)
{
  gst_rtsp_thread_pool_cleanup ();
}

static Suite *
rtspmedia_suite (void)
{
  Suite *s = suite_create ("rtspmedia");
  TCase *tc = tcase_create ("general");
  gboolean has_avidemux;

  suite_add_tcase (s, tc);
  tcase_add_checked_fixture (tc, NULL, teardown);
  tcase_set_timeout (tc, 20);

  has_avidemux = gst_registry_check_feature_version (gst_registry_get (),
      "avidemux", GST_VERSION_MAJOR, GST_VERSION_MINOR, 0);

  tcase_add_test (tc, test_media_seek);
  tcase_add_test (tc, test_media_seek_no_sinks);
  tcase_add_test (tc, test_media_playback_seek_one_active_stream);
  if (has_avidemux) {
    tcase_add_test (tc, test_media_playback_demux_seek_one_active_stream);
  } else {
    GST_INFO ("Skipping test, missing plugins: avidemux");
  }
  tcase_add_test (tc, test_media);
  tcase_add_test (tc, test_media_prepare);
  tcase_add_test (tc, test_media_shared_race_test_unsuspend_vs_set_state_null);
  tcase_add_test (tc, test_media_reusable);
  tcase_add_test (tc, test_media_dyn_prepare);
  tcase_add_test (tc, test_media_take_pipeline);
  tcase_add_test (tc, test_media_reset);
  tcase_add_test (tc, test_media_multidyn_prepare);
  tcase_add_test (tc, test_media_pipeline_error);

  return s;
}

GST_CHECK_MAIN (rtspmedia);
07070100000078000081A400000000000000000000000169D554BF0000386E000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/tests/check/gst/mediafactory.c /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-media-factory.h>

GST_START_TEST (test_parse_error)
{
  GstRTSPMediaFactory *factory;
  GstRTSPUrl *url;
  GstRTSPMedia *ignored G_GNUC_UNUSED;

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_launch (factory, "foo");
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);
  ASSERT_CRITICAL (gst_rtsp_media_factory_create_element (factory, url));
  ASSERT_CRITICAL (ignored = gst_rtsp_media_factory_construct (factory, url));

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_launch)
{
  GstRTSPMediaFactory *factory;
  GstElement *element;
  GstRTSPUrl *url;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  element = gst_rtsp_media_factory_create_element (factory, url);
  fail_unless (GST_IS_BIN (element));
  fail_if (GST_IS_PIPELINE (element));
  gst_object_unref (element);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_launch_construct)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media, *media2;
  GstRTSPUrl *url;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  gst_rtsp_media_unlock (media);

  media2 = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media2));
  fail_if (media == media2);
  gst_rtsp_media_unlock (media2);

  g_object_unref (media);
  g_object_unref (media2);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_shared)
{
  GstRTSPMediaFactory *factory;
  GstElement *element;
  GstRTSPMedia *media, *media2;
  GstRTSPUrl *url;

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  fail_unless (gst_rtsp_media_factory_is_shared (factory));

  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  element = gst_rtsp_media_factory_create_element (factory, url);
  fail_unless (GST_IS_BIN (element));
  fail_if (GST_IS_PIPELINE (element));
  gst_object_unref (element);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  gst_rtsp_media_unlock (media);

  media2 = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media2));
  fail_unless (media == media2);
  gst_rtsp_media_unlock (media2);

  g_object_unref (media);
  g_object_unref (media2);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_addresspool)
{
  GstRTSPMediaFactory *factory;
  GstElement *element;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPAddressPool *pool, *tmppool;
  GstRTSPStream *stream;
  GstRTSPAddress *addr;

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 "
      " audiotestsrc ! audioconvert ! rtpL16pay name=pay1 )");

  pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "233.252.0.1", 5000, 5001, 3));

  gst_rtsp_media_factory_set_address_pool (factory, pool);

  tmppool = gst_rtsp_media_factory_get_address_pool (factory);
  fail_unless (pool == tmppool);
  g_object_unref (tmppool);

  element = gst_rtsp_media_factory_create_element (factory, url);
  fail_unless (GST_IS_BIN (element));
  fail_if (GST_IS_PIPELINE (element));
  gst_object_unref (element);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  tmppool = gst_rtsp_media_get_address_pool (media);
  fail_unless (pool == tmppool);
  g_object_unref (tmppool);

  fail_unless (gst_rtsp_media_n_streams (media) == 2);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);

  tmppool = gst_rtsp_stream_get_address_pool (stream);
  fail_unless (pool == tmppool);
  g_object_unref (tmppool);

  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr != NULL);
  fail_unless (addr->port == 5000);
  fail_unless (addr->n_ports == 2);
  fail_unless (addr->ttl == 3);
  gst_rtsp_address_free (addr);

  stream = gst_rtsp_media_get_stream (media, 1);
  fail_unless (stream != NULL);

  tmppool = gst_rtsp_stream_get_address_pool (stream);
  fail_unless (pool == tmppool);
  g_object_unref (tmppool);

  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr == NULL);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  g_object_unref (pool);
  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_permissions)
{
  GstRTSPMediaFactory *factory;
  GstRTSPPermissions *perms;
  GstRTSPMedia *media;
  GstRTSPUrl *url;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  gst_rtsp_media_factory_add_role (factory, "admin",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);

  perms = gst_rtsp_media_factory_get_permissions (factory);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.access"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.construct"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "missing",
          "media.factory.access"));
  gst_rtsp_permissions_unref (perms);

  perms = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (perms, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, FALSE, NULL);
  gst_rtsp_media_factory_set_permissions (factory, perms);
  gst_rtsp_permissions_unref (perms);

  perms = gst_rtsp_media_factory_get_permissions (factory);
  fail_if (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.access"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.construct"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user",
          "media.factory.access"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user",
          "media.factory.construct"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "missing",
          "media.factory.access"));
  gst_rtsp_permissions_unref (perms);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  perms = gst_rtsp_media_get_permissions (media);
  fail_if (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.access"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "admin",
          "media.factory.construct"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user",
          "media.factory.access"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user",
          "media.factory.construct"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "missing",
          "media.factory.access"));
  gst_rtsp_permissions_unref (perms);
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_reset)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  gst_rtsp_url_parse ("rtsp://localhost:8554/test", &url);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  fail_if (gst_rtsp_media_get_suspend_mode (media) !=
      GST_RTSP_SUSPEND_MODE_NONE);
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_media_factory_set_suspend_mode (factory,
      GST_RTSP_SUSPEND_MODE_RESET);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));
  fail_if (gst_rtsp_media_get_suspend_mode (media) !=
      GST_RTSP_SUSPEND_MODE_RESET);
  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

GST_START_TEST (test_mcast_ttl)
{
  GstRTSPMediaFactory *factory;
  GstElement *element;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 "
      " audiotestsrc ! audioconvert ! rtpL16pay name=pay1 )");

  /* try to set an invalid ttl and make sure that the default ttl value (255) is
   * set */
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 0);
  fail_unless (gst_rtsp_media_factory_get_max_mcast_ttl (factory) == 255);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 300);
  fail_unless (gst_rtsp_media_factory_get_max_mcast_ttl (factory) == 255);

  /* set a valid ttl value */
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 3);
  fail_unless (gst_rtsp_media_factory_get_max_mcast_ttl (factory) == 3);

  element = gst_rtsp_media_factory_create_element (factory, url);
  fail_unless (GST_IS_BIN (element));
  fail_if (GST_IS_PIPELINE (element));
  gst_object_unref (element);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 2);
  fail_unless (gst_rtsp_media_get_max_mcast_ttl (media) == 3);

  /* verify that the correct ttl value has been propageted to the media
   * streams */
  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);
  fail_unless (gst_rtsp_stream_get_max_mcast_ttl (stream) == 3);

  stream = gst_rtsp_media_get_stream (media, 1);
  fail_unless (stream != NULL);
  fail_unless (gst_rtsp_stream_get_max_mcast_ttl (stream) == 3);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);

  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;


GST_START_TEST (test_allow_bind_mcast)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, TRUE);
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 "
      " audiotestsrc ! audioconvert ! rtpL16pay name=pay1 )");

  /* verify that by default binding sockets to multicast addresses is not enabled */
  fail_unless (gst_rtsp_media_factory_is_bind_mcast_address (factory) == FALSE);

  /* allow multicast sockets to be bound to multicast addresses */
  gst_rtsp_media_factory_set_bind_mcast_address (factory, TRUE);
  /* verify that the socket binding to multicast address has been enabled */
  fail_unless (gst_rtsp_media_factory_is_bind_mcast_address (factory) == TRUE);

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  /* verify that the correct socket binding configuration has been propageted to the media */
  fail_unless (gst_rtsp_media_is_bind_mcast_address (media) == TRUE);

  fail_unless (gst_rtsp_media_n_streams (media) == 2);

  /* verify that the correct socket binding configuration has been propageted to the media streams */
  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (stream != NULL);
  fail_unless (gst_rtsp_stream_is_bind_mcast_address (stream) == TRUE);

  stream = gst_rtsp_media_get_stream (media, 1);
  fail_unless (stream != NULL);
  fail_unless (gst_rtsp_stream_is_bind_mcast_address (stream) == TRUE);

  gst_rtsp_media_unlock (media);
  g_object_unref (media);
  gst_rtsp_url_free (url);
  g_object_unref (factory);
}

GST_END_TEST;

static Suite *
rtspmediafactory_suite (void)
{
  Suite *s = suite_create ("rtspmediafactory");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_parse_error);
  tcase_add_test (tc, test_launch);
  tcase_add_test (tc, test_launch_construct);
  tcase_add_test (tc, test_shared);
  tcase_add_test (tc, test_addresspool);
  tcase_add_test (tc, test_permissions);
  tcase_add_test (tc, test_reset);
  tcase_add_test (tc, test_mcast_ttl);
  tcase_add_test (tc, test_allow_bind_mcast);

  return s;
}

GST_CHECK_MAIN (rtspmediafactory);
  07070100000079000081A400000000000000000000000169D554BF000012BC000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/tests/check/gst/mountpoints.c  /* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-mount-points.h>

GST_START_TEST (test_create)
{
  GstRTSPMountPoints *mounts;
  GstRTSPUrl *url, *url2;
  GstRTSPMediaFactory *factory;

  mounts = gst_rtsp_mount_points_new ();

  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test",
          &url) == GST_RTSP_OK);
  fail_unless (gst_rtsp_url_parse ("rtsp://localhost:8554/test2",
          &url2) == GST_RTSP_OK);

  fail_unless (gst_rtsp_mount_points_match (mounts, url->abspath,
          NULL) == NULL);

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_mount_points_add_factory (mounts, "/test", factory);

  fail_unless (gst_rtsp_mount_points_match (mounts, url->abspath,
          NULL) == factory);
  g_object_unref (factory);
  fail_unless (gst_rtsp_mount_points_match (mounts, url2->abspath,
          NULL) == NULL);

  gst_rtsp_mount_points_remove_factory (mounts, "/test");

  fail_unless (gst_rtsp_mount_points_match (mounts, url->abspath,
          NULL) == NULL);
  fail_unless (gst_rtsp_mount_points_match (mounts, url2->abspath,
          NULL) == NULL);

  gst_rtsp_url_free (url);
  gst_rtsp_url_free (url2);

  g_object_unref (mounts);
}

GST_END_TEST;

static const gchar *paths[] = {
  "/test",
  "/booz/fooz",
  "/booz/foo/zoop",
  "/tark/bar",
  "/tark/bar/baz",
  "/tark/bar/baz/t",
  "/boozop",
  "/raw",
  "/raw/video",
  "/raw/snapshot",
};

GST_START_TEST (test_match)
{
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *f[G_N_ELEMENTS (paths)], *tmp;
  gint i, matched;

  mounts = gst_rtsp_mount_points_new ();

  for (i = 0; i < G_N_ELEMENTS (paths); i++) {
    f[i] = gst_rtsp_media_factory_new ();
    gst_rtsp_mount_points_add_factory (mounts, paths[i], f[i]);
  }

  tmp = gst_rtsp_mount_points_match (mounts, "/test", &matched);
  fail_unless (tmp == f[0]);
  fail_unless (matched == 5);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/test/stream=1", &matched);
  fail_unless (tmp == f[0]);
  fail_unless (matched == 5);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/booz", &matched);
  fail_unless (tmp == NULL);
  tmp = gst_rtsp_mount_points_match (mounts, "/booz/foo", &matched);
  fail_unless (tmp == NULL);
  tmp = gst_rtsp_mount_points_match (mounts, "/booz/fooz", &matched);
  fail_unless (tmp == f[1]);
  fail_unless (matched == 10);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/booz/fooz/zoo", &matched);
  fail_unless (tmp == f[1]);
  fail_unless (matched == 10);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/booz/foo/zoop", &matched);
  fail_unless (tmp == f[2]);
  fail_unless (matched == 14);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/tark/bar", &matched);
  fail_unless (tmp == f[3]);
  fail_unless (matched == 9);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/tark/bar/boo", &matched);
  fail_unless (tmp == f[3]);
  fail_unless (matched == 9);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/tark/bar/ba", &matched);
  fail_unless (tmp == f[3]);
  fail_unless (matched == 9);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/tark/bar/baz", &matched);
  fail_unless (tmp == f[4]);
  fail_unless (matched == 13);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/raw/video", &matched);
  fail_unless (tmp == f[8]);
  fail_unless (matched == 10);
  g_object_unref (tmp);
  tmp = gst_rtsp_mount_points_match (mounts, "/raw/snapshot", &matched);
  fail_unless (tmp == f[9]);
  fail_unless (matched == 13);
  g_object_unref (tmp);

  g_object_unref (mounts);
}

GST_END_TEST;

static Suite *
rtspmountpoints_suite (void)
{
  Suite *s = suite_create ("rtspmountpoints");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_create);
  tcase_add_test (tc, test_match);

  return s;
}

GST_CHECK_MAIN (rtspmountpoints);
0707010000007A000081A400000000000000000000000169D554BF0000A9E2000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/tests/check/gst/onvif.c    /* GStreamer
 * Copyright (C) 2018 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>
#include <gst/sdp/gstsdpmessage.h>
#include <gst/rtsp/gstrtspmessage.h>
#include <gst/base/gstpushsrc.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>
#include <rtsp-onvif-client.h>
#include <rtsp-onvif-media.h>
#include <rtsp-onvif-media-factory.h>

/* Test source implementation */

#define FRAME_DURATION (GST_MSECOND)

typedef struct
{
  GstPushSrc element;

  GstSegment *segment;
  /* In milliseconds */
  guint trickmode_interval;
  GstClockTime ntp_offset;
} TestSrc;

typedef struct
{
  GstPushSrcClass parent_class;
} TestSrcClass;

/**
 * video/x-dumdum is a very simple encoded video format:
 *
 * - It has I-frames, P-frames and B-frames for the purpose
 *   of testing trick modes, and is infinitely scalable, mimicking server-side
 *   trick modes that would have the server reencode when a trick mode seek with
 *   an absolute rate different from 1.0 is requested.
 *
 * - The only source capable of outputting this format, `TestSrc`, happens
 *   to always output frames following this pattern:
 *
 *   IBBBBPBBBBI
 *
 *   Its framerate is 1000 / 1, each Group of Pictures is thus 10 milliseconds
 *   long. The first frame in the stream dates back to January the first,
 *   1900, at exactly midnight. There are no gaps in the stream.
 *
 *   A nice side effect of this for testing purposes is that as the resolution
 *   of UTC (clock=) seeks is a hundredth of a second, this coincides with the
 *   alignment of our Group of Pictures, which means we don't have to worry
 *   about synchronization points.
 *
 * - Size is used to distinguish the various frame types:
 *
 *   * I frames: 20 bytes
 *   * P frames: 10 bytes
 *   * B frames: 5 bytes
 *
 */

#define TEST_CAPS "video/x-dumdum"

typedef enum
{
  FRAME_TYPE_I,
  FRAME_TYPE_P,
  FRAME_TYPE_B,
} FrameType;

static FrameType
frame_type_for_index (guint64 index)
{
  FrameType ret;

  if (index % 10 == 0)
    ret = FRAME_TYPE_I;
  else if (index % 5 == 0)
    ret = FRAME_TYPE_P;
  else
    ret = FRAME_TYPE_B;

  return ret;
}

static GstStaticPadTemplate test_src_template = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (TEST_CAPS)
    );

GType test_src_get_type (void);

#define test_src_parent_class parent_class
G_DEFINE_TYPE (TestSrc, test_src, GST_TYPE_PUSH_SRC);

#define TEST_SRC(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST ((obj), test_src_get_type(), TestSrc))

#define ROUND_UP_TO_10(x) (((x + 10 - 1) / 10) * 10)
#define ROUND_DOWN_TO_10(x) (x - (x % 10))

/*
 * For now, the theoretical range of our test source is infinite.
 *
 * When creating a buffer, we use the current segment position to
 * determine the PTS, and simply increment it afterwards.
 *
 * When the stop time of a buffer we have created reaches segment->stop,
 * GstBaseSrc will take care of sending an EOS for us, which rtponviftimestamp
 * will translate to setting the T flag in the RTP header extension.
 */
static GstFlowReturn
test_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
{
  GstFlowReturn ret = GST_FLOW_OK;
  gsize buf_size;
  TestSrc *src = (TestSrc *) psrc;
  GstClockTime pts, duration;
  FrameType ftype;
  guint64 n_frames;

  if (src->segment->rate < 1.0) {
    if (src->segment->position < src->segment->start) {
      ret = GST_FLOW_EOS;
      goto done;
    }
  } else if ((src->segment->position >= src->segment->stop)) {
    ret = GST_FLOW_EOS;
    goto done;
  }

  pts = src->segment->position;
  duration = FRAME_DURATION;

  if ((src->segment->flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
    duration =
        MAX (duration * 10,
        duration * ROUND_UP_TO_10 (src->trickmode_interval));
  } else if ((src->
          segment->flags & GST_SEGMENT_FLAG_TRICKMODE_FORWARD_PREDICTED)) {
    duration *= 5;
  }

  n_frames = gst_util_uint64_scale (src->segment->position, 1000, GST_SECOND);

  ftype = frame_type_for_index (n_frames);

  switch (ftype) {
    case FRAME_TYPE_I:
      buf_size = 20;
      break;
    case FRAME_TYPE_P:
      buf_size = 10;
      break;
    case FRAME_TYPE_B:
      buf_size = 5;
      break;
    default:
      g_assert_not_reached ();
      return GST_FLOW_ERROR;
  }

  *buffer = gst_buffer_new_allocate (NULL, buf_size, NULL);

  if (ftype != FRAME_TYPE_I) {
    GST_BUFFER_FLAG_SET (*buffer, GST_BUFFER_FLAG_DELTA_UNIT);
  }

  GST_BUFFER_PTS (*buffer) = pts;
  GST_BUFFER_DURATION (*buffer) = duration;

  src->segment->position = pts + duration;

  if (!GST_CLOCK_TIME_IS_VALID (src->ntp_offset)) {
    GstClock *clock = gst_system_clock_obtain ();
    GstClockTime clock_time = gst_clock_get_time (clock);
    guint64 real_time = g_get_real_time ();
    GstStructure *s;
    GstEvent *onvif_event;

    real_time *= 1000;
    real_time += (G_GUINT64_CONSTANT (2208988800) * GST_SECOND);
    src->ntp_offset = real_time - clock_time;

    s = gst_structure_new ("GstOnvifTimestamp",
        "ntp-offset", G_TYPE_UINT64, src->ntp_offset,
        "discont", G_TYPE_BOOLEAN, FALSE, NULL);

    onvif_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);

    gst_element_send_event (GST_ELEMENT (src), onvif_event);
    gst_object_unref (clock);
  }

  if (src->segment->rate < 1.0) {
    guint64 next_n_frames =
        gst_util_uint64_scale (src->segment->position, 1000, GST_SECOND);

    if (src->segment->position > src->segment->stop
        || next_n_frames / 10 > n_frames / 10) {
      GstStructure *s;
      GstEvent *onvif_event;
      guint n_gops;

      n_gops = MAX (1, ((int) src->trickmode_interval / 10));

      next_n_frames = (n_frames / 10 - n_gops) * 10;

      src->segment->position = next_n_frames * GST_MSECOND;
      s = gst_structure_new ("GstOnvifTimestamp",
          "ntp-offset", G_TYPE_UINT64, src->ntp_offset,
          "discont", G_TYPE_BOOLEAN, TRUE, NULL);

      onvif_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s);

      gst_element_send_event (GST_ELEMENT (src), onvif_event);
    }
  }

done:
  return ret;
}

static void
test_src_init (TestSrc * src)
{
  gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
  gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
  src->segment = NULL;
  src->ntp_offset = GST_CLOCK_TIME_NONE;
}

static void
test_src_finalize (GObject * obj)
{
  TestSrc *src = TEST_SRC (obj);

  if (src->segment != NULL)
    gst_segment_free (src->segment);

  G_OBJECT_CLASS (test_src_parent_class)->finalize (obj);
}

/*
 * We support seeking, both this method and GstBaseSrc.do_seek must
 * be implemented for GstBaseSrc to report TRUE in the seeking query.
 */
static gboolean
test_src_is_seekable (GstBaseSrc * bsrc)
{
  return TRUE;
}

/* Extremely simple seek handling for now, we simply update our
 * segment, which will cause test_src_create to timestamp output
 * buffers as expected.
 */
static gboolean
test_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
{
  TestSrc *src = (TestSrc *) bsrc;

  if ((segment->flags & GST_SEGMENT_FLAG_TRICKMODE
          && ABS (segment->rate) != 1.0)) {
    segment->applied_rate = segment->rate;
    segment->stop =
        segment->start + ((segment->stop -
            segment->start) / ABS (segment->rate));
    segment->rate = segment->rate > 0 ? 1.0 : -1.0;
  }

  if (src->segment)
    gst_segment_free (src->segment);

  src->segment = gst_segment_copy (segment);

  if (src->segment->rate < 0) {
    guint64 n_frames =
        ROUND_DOWN_TO_10 (gst_util_uint64_scale (src->segment->stop - 1, 1000,
            GST_SECOND));

    src->segment->position = n_frames * GST_MSECOND;
  }

  return TRUE;
}

static gboolean
test_src_event (GstBaseSrc * bsrc, GstEvent * event)
{
  TestSrc *src = (TestSrc *) bsrc;

  if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
    GstClockTime interval;

    gst_event_parse_seek_trickmode_interval (event, &interval);

    src->trickmode_interval = interval / 1000000;
  }

  return GST_BASE_SRC_CLASS (parent_class)->event (bsrc, event);
}

static void
test_src_class_init (TestSrcClass * klass)
{
  G_OBJECT_CLASS (klass)->finalize = test_src_finalize;

  gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
      &test_src_template);
  GST_PUSH_SRC_CLASS (klass)->create = test_src_create;
  GST_BASE_SRC_CLASS (klass)->is_seekable = test_src_is_seekable;
  GST_BASE_SRC_CLASS (klass)->do_seek = test_src_do_seek;
  GST_BASE_SRC_CLASS (klass)->event = test_src_event;
}

static GstElement *
test_src_new (void)
{
  return g_object_new (test_src_get_type (), NULL);
}

/* Test media factory */

typedef struct
{
  GstRTSPMediaFactory factory;
} TestMediaFactory;

typedef struct
{
  GstRTSPMediaFactoryClass parent_class;
} TestMediaFactoryClass;

GType test_media_factory_get_type (void);

G_DEFINE_TYPE (TestMediaFactory, test_media_factory,
    GST_TYPE_RTSP_MEDIA_FACTORY);

#define MAKE_AND_ADD(var, pipe, name, label, elem_name) \
G_STMT_START { \
  if (G_UNLIKELY (!(var = (gst_element_factory_make (name, elem_name))))) { \
    GST_ERROR ("Could not create element %s", name); \
    goto label; \
  } \
  if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (pipe), var))) { \
    GST_ERROR ("Could not add element %s", name); \
    goto label; \
  } \
} G_STMT_END

static GstElement *
test_media_factory_create_element (GstRTSPMediaFactory * factory,
    const GstRTSPUrl * url)
{
  GstElement *ret = gst_bin_new (NULL);
  GstElement *pbin = gst_bin_new ("pay0");
  GstElement *src, *pay, *onvifts, *queue;
  GstPad *sinkpad, *srcpad;
  GstPadLinkReturn link_ret;

  src = test_src_new ();
  gst_bin_add (GST_BIN (ret), src);
  MAKE_AND_ADD (pay, pbin, "rtpgstpay", fail, NULL);
  MAKE_AND_ADD (onvifts, pbin, "rtponviftimestamp", fail, NULL);
  MAKE_AND_ADD (queue, pbin, "queue", fail, NULL);

  gst_bin_add (GST_BIN (ret), pbin);
  if (!gst_element_link_many (pay, onvifts, queue, NULL))
    goto fail;

  sinkpad = gst_element_get_static_pad (pay, "sink");
  gst_element_add_pad (pbin, gst_ghost_pad_new ("sink", sinkpad));
  gst_object_unref (sinkpad);

  sinkpad = gst_element_get_static_pad (pbin, "sink");
  srcpad = gst_element_get_static_pad (src, "src");
  link_ret = gst_pad_link (srcpad, sinkpad);
  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);

  if (link_ret != GST_PAD_LINK_OK)
    goto fail;

  srcpad = gst_element_get_static_pad (queue, "src");
  gst_element_add_pad (pbin, gst_ghost_pad_new ("src", srcpad));
  gst_object_unref (srcpad);

  g_object_set (pay, "timestamp-offset", 0, NULL);
  g_object_set (onvifts, "set-t-bit", TRUE, NULL);

done:
  return ret;

fail:
  gst_object_unref (ret);
  ret = NULL;
  goto done;
}

static void
test_media_factory_init (TestMediaFactory * factory)
{
}

static void
test_media_factory_class_init (TestMediaFactoryClass * klass)
{
  GST_RTSP_MEDIA_FACTORY_CLASS (klass)->create_element =
      test_media_factory_create_element;
}

static GstRTSPMediaFactory *
test_media_factory_new (void)
{
  GstRTSPMediaFactory *result;

  result = g_object_new (test_media_factory_get_type (), NULL);

  return result;
}

/* Actual tests implementation */

static gchar *session_id;
static gint cseq;
static gboolean terminal_frame;
static gboolean received_rtcp;

static GstSDPMessage *
sdp_from_message (GstRTSPMessage * msg)
{
  GstSDPMessage *sdp_message;
  guint8 *body = NULL;
  guint body_size;

  fail_unless (gst_rtsp_message_get_body (msg, &body,
          &body_size) == GST_RTSP_OK);
  fail_unless (gst_sdp_message_new (&sdp_message) == GST_SDP_OK);
  fail_unless (gst_sdp_message_parse_buffer (body, body_size,
          sdp_message) == GST_SDP_OK);

  return sdp_message;
}

static gboolean
test_response_x_onvif_track (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstSDPMessage *sdp = sdp_from_message (response);
  guint medias_len = gst_sdp_message_medias_len (sdp);
  guint i;

  fail_unless_equals_int (medias_len, 1);

  for (i = 0; i < medias_len; i++) {
    const GstSDPMedia *smedia = gst_sdp_message_get_media (sdp, i);
    gchar *x_onvif_track = g_strdup_printf ("APPLICATION%03d", i);

    fail_unless_equals_string (gst_sdp_media_get_attribute_val (smedia,
            "x-onvif-track"), x_onvif_track);
    g_free (x_onvif_track);
  }

  gst_sdp_message_free (sdp);

  return TRUE;
}

static gboolean
test_setup_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPSession *session;
  gchar **session_hdr_params;

  fail_unless_equals_int (gst_rtsp_message_get_type (response),
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless_equals_int (code, GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION,
          &str, 0) == GST_RTSP_OK);
  session_hdr_params = g_strsplit (str, ";", -1);

  /* session-id value */
  fail_unless (session_hdr_params[0] != NULL);

  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);

  session = gst_rtsp_session_pool_find (session_pool, session_hdr_params[0]);
  g_strfreev (session_hdr_params);

  /* remember session id to be able to send teardown */
  if (session_id)
    g_free (session_id);
  session_id = g_strdup (gst_rtsp_session_get_sessionid (session));
  fail_unless (session_id != NULL);

  fail_unless (session != NULL);
  g_object_unref (session);

  g_object_unref (session_pool);

  return TRUE;
}

static gboolean
test_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless_equals_int (gst_rtsp_message_get_type (response),
      GST_RTSP_MESSAGE_RESPONSE);
  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless_equals_int (code, GST_RTSP_STS_OK);

  return TRUE;
}

typedef struct
{
  guint32 previous_ts;
  gint32 expected_ts_interval;
  gint32 expected_i_frame_ts_interval;
  guint expected_n_buffers;
  guint n_buffers;
  guint expected_n_i_frames;
  guint n_i_frames;
  guint expected_n_p_frames;
  guint n_p_frames;
  guint expected_n_b_frames;
  guint n_b_frames;
  guint expected_n_clean_points;
  guint n_clean_points;
  gboolean timestamped_rtcp;
} RTPCheckData;

#define EXTENSION_ID 0xABAC
#define EXTENSION_SIZE 3

static gboolean
read_length (guint8 * data, guint size, guint * length, guint * skip)
{
  guint b, len, offset;

  /* start reading the length, we need this to skip to the data later */
  len = offset = 0;
  do {
    if (offset >= size)
      return FALSE;
    b = data[offset++];
    len = (len << 7) | (b & 0x7f);
  } while (b & 0x80);

  /* check remaining buffer size */
  if (size - offset < len)
    return FALSE;

  *length = len;
  *skip = offset;

  return TRUE;
}

static GstCaps *
read_caps (GstBuffer * buf, guint * skip)
{
  guint offset, length;
  GstCaps *caps;
  GstMapInfo map;

  gst_buffer_map (buf, &map, GST_MAP_READ);

  if (!read_length (map.data, map.size, &length, &offset))
    goto too_small;

  if (length == 0 || map.data[offset + length - 1] != '\0')
    goto invalid_buffer;

  /* parse and store in cache */
  caps = gst_caps_from_string ((gchar *) & map.data[offset]);
  gst_buffer_unmap (buf, &map);

  *skip = length + offset;

  return caps;

too_small:
  {
    gst_buffer_unmap (buf, &map);
    return NULL;
  }
invalid_buffer:
  {
    gst_buffer_unmap (buf, &map);
    return NULL;
  }
}

static GstEvent *
read_event (guint type, GstBuffer * buf, guint * skip)
{
  guint offset, length;
  GstStructure *s;
  GstEvent *event;
  GstEventType etype;
  gchar *end;
  GstMapInfo map;

  gst_buffer_map (buf, &map, GST_MAP_READ);

  if (!read_length (map.data, map.size, &length, &offset))
    goto too_small;

  if (length == 0)
    goto invalid_buffer;
  /* backward compat, old payloader did not put 0-byte at the end */
  if (map.data[offset + length - 1] != '\0'
      && map.data[offset + length - 1] != ';')
    goto invalid_buffer;

  /* parse */
  s = gst_structure_from_string ((gchar *) & map.data[offset], &end);
  gst_buffer_unmap (buf, &map);

  if (s == NULL)
    goto parse_failed;

  switch (type) {
    case 1:
      etype = GST_EVENT_TAG;
      break;
    case 2:
      etype = GST_EVENT_CUSTOM_DOWNSTREAM;
      break;
    case 3:
      etype = GST_EVENT_CUSTOM_BOTH;
      break;
    case 4:
      etype = GST_EVENT_STREAM_START;
      break;
    default:
      goto unknown_event;
  }
  event = gst_event_new_custom (etype, s);

  *skip = length + offset;

  return event;

too_small:
  {
    gst_buffer_unmap (buf, &map);
    return NULL;
  }
invalid_buffer:
  {
    gst_buffer_unmap (buf, &map);
    return NULL;
  }
parse_failed:
  {
    return NULL;
  }
unknown_event:
  {
    gst_structure_free (s);
    return NULL;
  }
}

static gboolean
parse_gstpay_payload (GstRTPBuffer * rtp, GstEvent ** event, GstCaps ** caps,
    GstBuffer ** outbuf)
{
  gint payload_len;
  guint8 *payload;
  guint avail, offset;

  payload_len = gst_rtp_buffer_get_payload_len (rtp);

  if (payload_len <= 8)
    goto empty_packet;

  /* We don't need to deal with fragmentation */
  fail_unless (gst_rtp_buffer_get_marker (rtp));

  payload = gst_rtp_buffer_get_payload (rtp);

  *outbuf = gst_rtp_buffer_get_payload_subbuffer (rtp, 8, -1);
  avail = gst_buffer_get_size (*outbuf);
  offset = 0;

  if (payload[0] & 0x80) {
    guint size;

    /* C bit, we have inline caps */
    *caps = read_caps (*outbuf, &size);
    if (*caps == NULL)
      goto no_caps;

    /* skip caps */
    offset += size;
    avail -= size;
  }

  if (payload[1]) {
    guint size;

    /* we have an event */
    *event = read_event (payload[1], *outbuf, &size);
    if (*event == NULL)
      goto no_event;

    /* no buffer after event */
    avail = 0;
  }

  if (avail) {
    if (offset != 0) {
      GstBuffer *temp;

      temp =
          gst_buffer_copy_region (*outbuf, GST_BUFFER_COPY_ALL, offset, avail);

      gst_buffer_unref (*outbuf);
      *outbuf = temp;
    }

    if (payload[0] & 0x8)
      GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
  } else {
    gst_buffer_unref (*outbuf);
    *outbuf = NULL;
  }

  return TRUE;

empty_packet:
  return FALSE;

no_caps:
  {
    gst_buffer_unref (*outbuf);
    return FALSE;
  }
no_event:
  {
    gst_buffer_unref (*outbuf);
    return FALSE;
  }
}

static gboolean
test_play_response_200_and_check_data (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  RTPCheckData *check = (RTPCheckData *) user_data;

  /* We check data in the same send function because client->send_func cannot
   * be changed from client->send_func
   */
  if (gst_rtsp_message_get_type (response) == GST_RTSP_MESSAGE_DATA) {
    GstRTSPStreamTransport *trans;
    guint8 channel = 42;

    gst_rtsp_message_parse_data (response, &channel);
    fail_unless (trans =
        gst_rtsp_client_get_stream_transport (client, channel));

    if (channel == 0) {         /* RTP */
      GstBuffer *buf;
      GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
      guint8 *body = NULL;
      guint body_size;
      guint8 *data;
      guint16 bits;
      guint wordlen;
      guint8 flags;
      gint32 expected_interval = 0;
      GstBuffer *outbuf = NULL;
      GstCaps *outcaps = NULL;
      GstEvent *outevent = NULL;

      fail_unless (gst_rtsp_message_get_body (response, &body,
              &body_size) == GST_RTSP_OK);

      buf = gst_rtp_buffer_new_copy_data (body, body_size);

      fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));

      fail_unless (parse_gstpay_payload (&rtp, &outevent, &outcaps, &outbuf));

      if (outbuf) {
        switch (gst_buffer_get_size (outbuf)) {
          case 20:
            expected_interval = check->expected_i_frame_ts_interval;
            check->n_i_frames += 1;
            break;
          case 10:
            expected_interval = check->expected_ts_interval;
            check->n_p_frames += 1;
            break;
          case 5:
            expected_interval = check->expected_ts_interval;
            check->n_b_frames += 1;
            break;
          default:
            fail ("Invalid payload size %u", gst_buffer_get_size (outbuf));
        }

        gst_buffer_unref (outbuf);
      }

      if (outcaps) {
        gst_caps_unref (outcaps);
      }

      if (outevent) {
        const GstStructure *s;

        fail_unless (GST_EVENT_TYPE (outevent) == GST_EVENT_CUSTOM_DOWNSTREAM);
        s = gst_event_get_structure (outevent);
        fail_unless (gst_structure_has_name (s, "GstOnvifTimestamp"));
        gst_event_unref (outevent);
      }


      fail_unless (gst_rtp_buffer_get_extension_data (&rtp, &bits,
              (gpointer) & data, &wordlen));

      fail_unless (bits == EXTENSION_ID && wordlen == EXTENSION_SIZE);

      flags = GST_READ_UINT8 (data + 8);

      if (expected_interval) {
        if (check->previous_ts) {
          fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp) -
              check->previous_ts, expected_interval);
        }

        check->previous_ts = gst_rtp_buffer_get_timestamp (&rtp);
        check->n_buffers += 1;

        if (flags & (1 << 7)) {
          check->n_clean_points += 1;
        }
      }

      /* T flag is set, we are done */
      if (flags & (1 << 4)) {
        fail_unless_equals_int (check->expected_n_buffers, check->n_buffers);
        fail_unless_equals_int (check->expected_n_i_frames, check->n_i_frames);
        fail_unless_equals_int (check->expected_n_p_frames, check->n_p_frames);
        fail_unless_equals_int (check->expected_n_b_frames, check->n_b_frames);
        fail_unless_equals_int (check->expected_n_clean_points,
            check->n_clean_points);

        terminal_frame = TRUE;

      }

      gst_rtp_buffer_unmap (&rtp);
      gst_buffer_unref (buf);
    } else if (channel == 1) {  /* RTCP */
      GstBuffer *buf;
      guint8 *body = NULL;
      guint body_size;
      GstRTCPPacket packet;
      GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT;
      guint32 ssrc, rtptime, packet_count, octet_count;
      guint64 ntptime;

      received_rtcp = TRUE;
      fail_unless (gst_rtsp_message_get_body (response, &body,
              &body_size) == GST_RTSP_OK);

      buf = gst_rtp_buffer_new_copy_data (body, body_size);
      gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp);
      gst_rtcp_buffer_get_first_packet (&rtcp, &packet);

      gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime,
          &packet_count, &octet_count);

      if (check->timestamped_rtcp) {
        fail_unless (rtptime != 0);
        fail_unless (ntptime != 0);
      } else {
        fail_unless (rtptime == 0);
        fail_unless (ntptime == 0);
      }

      gst_rtcp_buffer_unmap (&rtcp);
      gst_buffer_unref (buf);
    }

    gst_rtsp_stream_transport_message_sent (trans);

    if (terminal_frame && received_rtcp) {
      g_mutex_lock (&check_mutex);
      g_cond_broadcast (&check_cond);
      g_mutex_unlock (&check_mutex);
    }

    return TRUE;
  }

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);

  return TRUE;
}

static gboolean
test_teardown_response_200 (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  /* We might still be seeing stray RTCP messages */
  if (gst_rtsp_message_get_type (response) == GST_RTSP_MESSAGE_DATA)
    return TRUE;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static void
send_teardown (GstRTSPClient * client)
{
  GstRTSPMessage request = { 0, };
  gchar *str;

  fail_unless (session_id != NULL);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_teardown_response_200,
      NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  g_free (session_id);
  session_id = NULL;
}

static GstRTSPClient *
setup_client (const gchar * launch_line)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPThreadPool *thread_pool;

  client = gst_rtsp_onvif_client_new ();

  session_pool = gst_rtsp_session_pool_new ();
  gst_rtsp_client_set_session_pool (client, session_pool);

  mount_points = gst_rtsp_mount_points_new ();
  factory = test_media_factory_new ();

  gst_rtsp_media_factory_set_media_gtype (factory, GST_TYPE_RTSP_ONVIF_MEDIA);

  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  gst_rtsp_client_set_mount_points (client, mount_points);

  thread_pool = gst_rtsp_thread_pool_new ();
  gst_rtsp_client_set_thread_pool (client, thread_pool);

  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (thread_pool);

  return client;
}

static void
teardown_client (GstRTSPClient * client)
{
  gst_rtsp_client_set_thread_pool (client, NULL);
  g_object_unref (client);
}

/**
 * https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf
 * 6.2 RTSP describe
 */
GST_START_TEST (test_x_onvif_track)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_x_onvif_track, NULL,
      NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_END_TEST;

static void
create_connection (GstRTSPConnection ** conn)
{
  GSocket *sock;
  GError *error = NULL;

  sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
      G_SOCKET_PROTOCOL_TCP, &error);
  g_assert_no_error (error);
  fail_unless (gst_rtsp_connection_create_from_socket (sock, "127.0.0.1", 444,
          NULL, conn) == GST_RTSP_OK);
  g_object_unref (sock);
}

static void
test_seek (const gchar * range, const gchar * speed, const gchar * scale,
    const gchar * frames, const gchar * rate_control, RTPCheckData * rtp_check)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast");

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RANGE, range);

  if (scale) {
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, scale);
  }

  if (speed) {
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, speed);
  }

  if (frames) {
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_FRAMES, frames);
  }

  if (rate_control) {
    gst_rtsp_message_add_header (&request, GST_RTSP_HDR_RATE_CONTROL,
        rate_control);
  }

  gst_rtsp_client_set_send_func (client, test_play_response_200_and_check_data,
      rtp_check, NULL);

  terminal_frame = FALSE;
  received_rtcp = FALSE;

  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_mutex_lock (&check_mutex);
  while (!terminal_frame || !received_rtcp)
    g_cond_wait (&check_cond, &check_mutex);
  g_mutex_unlock (&check_mutex);

  send_teardown (client);

  teardown_client (client);
}

GST_START_TEST (test_src_seek_simple)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 90;
  rtp_check.expected_i_frame_ts_interval = 90;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, NULL,
      NULL, &rtp_check);
}

GST_END_TEST;

/**
 * https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf
 * 6.4 RTSP Feature Tag
 */
GST_START_TEST (test_onvif_replay)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, "onvif-replay");

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client);
  teardown_client (client);
}

GST_END_TEST;

GST_START_TEST (test_speed_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 45;
  rtp_check.expected_i_frame_ts_interval = 45;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", "2.0", NULL, NULL,
      NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_scale_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 90;
  rtp_check.expected_i_frame_ts_interval = 90;
  rtp_check.expected_n_buffers = 50;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 5;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 5;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 40;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 5;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, "2.0", NULL,
      NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_intra_frames_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 900;
  rtp_check.expected_i_frame_ts_interval = 900;
  rtp_check.expected_n_buffers = 10;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 0;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL,
      "intra", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_intra_frames_with_interval_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 1800;
  rtp_check.expected_i_frame_ts_interval = 1800;
  rtp_check.expected_n_buffers = 5;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 5;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 0;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 5;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL,
      "intra/20", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_predicted_frames_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 450;
  rtp_check.expected_i_frame_ts_interval = 450;
  rtp_check.expected_n_buffers = 20;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL,
      "predicted", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = -90;
  rtp_check.expected_i_frame_ts_interval = 1710;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", NULL, "-1.0",
      NULL, NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_speed_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = -45;
  rtp_check.expected_i_frame_ts_interval = 855;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", "2.0", "-1.0",
      NULL, NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_scale_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = -90;
  rtp_check.expected_i_frame_ts_interval = 1710;
  rtp_check.expected_n_buffers = 50;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 5;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 5;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 40;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 5;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-2.0",
      NULL, NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_intra_frames_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 0;
  rtp_check.expected_i_frame_ts_interval = 900;
  rtp_check.expected_n_buffers = 10;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 0;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0",
      "intra", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_predicted_frames_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = -450;
  rtp_check.expected_i_frame_ts_interval = 1350;
  rtp_check.expected_n_buffers = 20;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0",
      "predicted", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_intra_frames_with_interval_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 0;
  rtp_check.expected_i_frame_ts_interval = 1800;
  rtp_check.expected_n_buffers = 5;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 5;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 0;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 5;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = TRUE;

  test_seek ("clock=19000101T010001.10Z-19000101T010001.00Z", NULL, "-1.0",
      "intra/20", NULL, &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_rate_control_no_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 90;
  rtp_check.expected_i_frame_ts_interval = 90;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = FALSE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL, NULL,
      "no", &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_rate_control_no_reverse_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 90;
  rtp_check.expected_i_frame_ts_interval = -1710;
  rtp_check.expected_n_buffers = 100;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 10;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 80;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = FALSE;

  test_seek ("clock=19000101T010000.10Z-19000101T010000.00Z", NULL, "-1.0",
      NULL, "no", &rtp_check);
}

GST_END_TEST;

GST_START_TEST (test_rate_control_no_frames_trick_mode)
{
  RTPCheckData rtp_check;

  rtp_check.previous_ts = 0;
  rtp_check.expected_ts_interval = 900;
  rtp_check.expected_i_frame_ts_interval = 900;
  rtp_check.expected_n_buffers = 10;
  rtp_check.n_buffers = 0;
  rtp_check.expected_n_i_frames = 10;
  rtp_check.n_i_frames = 0;
  rtp_check.expected_n_p_frames = 0;
  rtp_check.n_p_frames = 0;
  rtp_check.expected_n_b_frames = 0;
  rtp_check.n_b_frames = 0;
  rtp_check.expected_n_clean_points = 10;
  rtp_check.n_clean_points = 0;
  rtp_check.timestamped_rtcp = FALSE;

  test_seek ("clock=19000101T010000.00Z-19000101T010000.10Z", NULL, NULL,
      "intra", "no", &rtp_check);
}

GST_END_TEST;
static Suite *
onvif_suite (void)
{
  Suite *s = suite_create ("onvif");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);

  tcase_add_test (tc, test_x_onvif_track);
  tcase_add_test (tc, test_onvif_replay);
  tcase_add_test (tc, test_src_seek_simple);
  tcase_add_test (tc, test_speed_trick_mode);
  tcase_add_test (tc, test_scale_trick_mode);
  tcase_add_test (tc, test_intra_frames_trick_mode);
  tcase_add_test (tc, test_predicted_frames_trick_mode);
  tcase_add_test (tc, test_intra_frames_with_interval_trick_mode);
  tcase_add_test (tc, test_reverse_trick_mode);
  tcase_add_test (tc, test_speed_reverse_trick_mode);
  tcase_add_test (tc, test_scale_reverse_trick_mode);
  tcase_add_test (tc, test_intra_frames_reverse_trick_mode);
  tcase_add_test (tc, test_predicted_frames_reverse_trick_mode);
  tcase_add_test (tc, test_intra_frames_with_interval_reverse_trick_mode);
  tcase_add_test (tc, test_rate_control_no_trick_mode);
  tcase_add_test (tc, test_rate_control_no_reverse_trick_mode);
  tcase_add_test (tc, test_rate_control_no_frames_trick_mode);

  return s;
}

GST_CHECK_MAIN (onvif);
  0707010000007B000081A400000000000000000000000169D554BF00001847000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/tests/check/gst/permissions.c  /* GStreamer
 * Copyright (C) 2013 Sebastian Rasmussen <sebras@hotmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-permissions.h>

GST_START_TEST (test_permissions)
{
  GstRTSPPermissions *perms;
  GstRTSPPermissions *copy;
  GstStructure *role_structure;

  perms = gst_rtsp_permissions_new ();
  fail_if (gst_rtsp_permissions_is_allowed (perms, "missing", "permission1"));
  gst_rtsp_permissions_unref (perms);

  perms = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (perms, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "missing"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "missing", "permission1"));
  copy = GST_RTSP_PERMISSIONS (gst_mini_object_copy (GST_MINI_OBJECT (perms)));
  gst_rtsp_permissions_unref (perms);
  fail_unless (gst_rtsp_permissions_is_allowed (copy, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (copy, "user", "permission2"));
  gst_rtsp_permissions_unref (copy);

  perms = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (perms, "admin",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_permissions_add_role (perms, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission1"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission2"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));
  gst_rtsp_permissions_unref (perms);

  perms = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (perms, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));
  gst_rtsp_permissions_add_role (perms, "user",
      "permission1", G_TYPE_BOOLEAN, FALSE,
      "permission2", G_TYPE_BOOLEAN, TRUE, NULL);
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));
  gst_rtsp_permissions_unref (perms);

  perms = gst_rtsp_permissions_new ();
  gst_rtsp_permissions_add_role (perms, "admin",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_permissions_add_role (perms, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission1"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission2"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));
  gst_rtsp_permissions_remove_role (perms, "user");
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission1"));
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission2"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "user", "permission2"));

  /* _add_permission_for_role() should overwrite existing or create new role */
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "admin", "permission1"));
  gst_rtsp_permissions_add_permission_for_role (perms, "admin", "permission1",
      FALSE);
  fail_if (gst_rtsp_permissions_is_allowed (perms, "admin", "permission1"));

  fail_if (gst_rtsp_permissions_is_allowed (perms, "tester", "permission1"));
  gst_rtsp_permissions_add_permission_for_role (perms, "tester", "permission1",
      TRUE);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "tester",
          "permission1"));
  gst_rtsp_permissions_add_permission_for_role (perms, "tester", "permission1",
      FALSE);
  fail_if (gst_rtsp_permissions_is_allowed (perms, "tester", "permission1"));
  gst_rtsp_permissions_add_permission_for_role (perms, "tester", "permission2",
      TRUE);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "tester",
          "permission2"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "tester", "permission3"));

  gst_rtsp_permissions_add_role_empty (perms, "noone");
  fail_if (gst_rtsp_permissions_is_allowed (perms, "noone", "permission1"));

  role_structure = gst_structure_new ("tester", "permission1", G_TYPE_BOOLEAN,
      TRUE, NULL);
  gst_rtsp_permissions_add_role_from_structure (perms, role_structure);
  gst_structure_free (role_structure);
  fail_unless (gst_rtsp_permissions_is_allowed (perms, "tester",
          "permission1"));
  fail_if (gst_rtsp_permissions_is_allowed (perms, "tester", "permission2"));

  gst_rtsp_permissions_unref (perms);
}

GST_END_TEST;

static Suite *
rtsppermissions_suite (void)
{
  Suite *s = suite_create ("rtsppermissions");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_permissions);

  return s;
}

GST_CHECK_MAIN (rtsppermissions);
 0707010000007C000081A400000000000000000000000169D554BF0000202D000000000000000000000000000000000000003800000000gst-rtsp-server-1.28.2/tests/check/gst/rtspclientsink.c   /* GStreamer unit test for rtspclientsink
 * Copyright (C) 2012 Axis Communications <dev-gstreamer at axis dot com>
 *   @author David Svensson Fors <davidsf at axis dot com>
 * Copyright (C) 2015 Centricular Ltd
 *   @author Tim-Philipp Müller <tim@centricular.com>
 *   @author Jan Schmidt <jan@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>
#include <gst/sdp/gstsdpmessage.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>

#include <stdio.h>
#include <netinet/in.h>

#include "rtsp-server.h"

#define TEST_MOUNT_POINT  "/test"

/* tested rtsp server */
static GstRTSPServer *server = NULL;

/* tcp port that the test server listens for rtsp requests on */
static gint test_port = 0;
static gint server_send_rtcp_port;

/* id of the server's source within the GMainContext */
static guint source_id;

/* iterate the default main context until there are no events to dispatch */
static void
iterate (void)
{
  while (g_main_context_iteration (NULL, FALSE)) {
    GST_DEBUG ("iteration");
  }
}

/* start the testing rtsp server for RECORD mode */
static GstRTSPMediaFactory *
start_record_server (const gchar * launch_line)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMountPoints *mounts;
  gchar *service;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_transport_mode (factory,
      GST_RTSP_TRANSPORT_MODE_RECORD);
  gst_rtsp_media_factory_set_launch (factory, launch_line);
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);
  return factory;
}

/* stop the tested rtsp server */
static void
stop_server (void)
{
  g_source_remove (source_id);
  source_id = 0;

  GST_DEBUG ("rtsp server stopped");
}

/* fixture setup function */
static void
setup (void)
{
  server = gst_rtsp_server_new ();
}

/* fixture clean-up function */
static void
teardown (void)
{
  if (server) {
    g_object_unref (server);
    server = NULL;
  }
  test_port = 0;
}

/* create an rtsp connection to the server on test_port */
static gchar *
get_server_uri (gint port, const gchar * mount_point)
{
  gchar *address;
  gchar *uri_string;
  GstRTSPUrl *url = NULL;

  address = gst_rtsp_server_get_address (server);
  uri_string = g_strdup_printf ("rtsp://%s:%d%s", address, port, mount_point);
  g_free (address);

  fail_unless (gst_rtsp_url_parse (uri_string, &url) == GST_RTSP_OK);
  gst_rtsp_url_free (url);

  return uri_string;
}

static GstRTSPFilterResult
check_transport (GstRTSPStream * stream, GstRTSPStreamTransport * strans,
    gpointer user_data)
{
  const GstRTSPTransport *trans =
      gst_rtsp_stream_transport_get_transport (strans);

  server_send_rtcp_port = trans->client_port.max;

  return GST_RTSP_FILTER_KEEP;
}

static void
new_state_cb (GstRTSPMedia * media, gint state, gpointer user_data)
{
  if (state == GST_STATE_PLAYING) {
    GstRTSPStream *stream = gst_rtsp_media_get_stream (media, 0);
    GList *transports G_GNUC_UNUSED;

    transports = gst_rtsp_stream_transport_filter (stream,
        (GstRTSPStreamTransportFilterFunc) check_transport, user_data);
    g_assert (transports == NULL);
  }
}

static void
media_constructed_cb (GstRTSPMediaFactory * mfactory, GstRTSPMedia * media,
    gpointer user_data)
{
  GstElement **p_sink = user_data;
  GstElement *bin;

  g_signal_connect (media, "new-state", G_CALLBACK (new_state_cb), user_data);

  bin = gst_rtsp_media_get_element (media);
  *p_sink = gst_bin_get_by_name (GST_BIN (bin), "sink");
  GST_INFO ("media constructed!: %" GST_PTR_FORMAT, *p_sink);
  gst_object_unref (bin);
}

#define AUDIO_PIPELINE "audiotestsrc num-buffers=%d ! " \
  "audio/x-raw,rate=8000 ! alawenc ! rtspclientsink name=sink location=%s"
#define RECORD_N_BUFS 10

GST_START_TEST (test_record)
{
  GstRTSPMediaFactory *mfactory;
  GstElement *server_sink = NULL;
  gint i;

  mfactory =
      start_record_server ("( rtppcmadepay name=depay0 ! appsink name=sink )");

  g_signal_connect (mfactory, "media-constructed",
      G_CALLBACK (media_constructed_cb), &server_sink);

  /* Create an rtspclientsink and send some data */
  {
    gchar *uri = get_server_uri (test_port, TEST_MOUNT_POINT);
    gchar *pipe_str;
    GstMessage *msg;
    GstElement *pipeline;
    GstBus *bus;

    pipe_str = g_strdup_printf (AUDIO_PIPELINE, RECORD_N_BUFS, uri);
    g_free (uri);

    pipeline = gst_parse_launch (pipe_str, NULL);
    g_free (pipe_str);

    fail_unless (pipeline != NULL);

    bus = gst_element_get_bus (pipeline);
    fail_if (bus == NULL);

    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    msg = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
    fail_if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_EOS);
    gst_message_unref (msg);

    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref (pipeline);
    gst_object_unref (bus);
  }

  iterate ();

  fail_unless (server_send_rtcp_port != 0);

  /* check received data (we assume every buffer created by audiotestsrc and
   * subsequently encoded by mulawenc results in exactly one RTP packet) */
  for (i = 0; i < RECORD_N_BUFS; ++i) {
    GstSample *sample = NULL;

    g_signal_emit_by_name (G_OBJECT (server_sink), "pull-sample", &sample);
    GST_INFO ("%2d recv sample: %p", i, sample);
    if (sample) {
      gst_sample_unref (sample);
      sample = NULL;
    }
  }
  gst_object_unref (server_sink);

  /* clean up and iterate so the clean-up can finish */
  stop_server ();
  iterate ();
}

GST_END_TEST;

/* Make sure we can shut down rtspclientsink while it's still waiting for
 * the initial preroll data */
GST_START_TEST (test_record_no_data)
{

  start_record_server ("( rtppcmadepay name=depay0 ! fakesink )");

  /* Create an rtspclientsink and send some data */
  {
    gchar *uri = get_server_uri (test_port, TEST_MOUNT_POINT);
    gchar *pipe_str;
    GstMessage *msg;
    GstElement *pipeline;
    GstBus *bus;

    pipe_str = g_strdup_printf ("appsrc caps=audio/x-alaw,rate=8000,channels=1"
        " ! rtspclientsink name=sink location=%s", uri);
    g_free (uri);

    pipeline = gst_parse_launch (pipe_str, NULL);
    g_free (pipe_str);

    fail_unless (pipeline != NULL);

    bus = gst_element_get_bus (pipeline);
    fail_if (bus == NULL);

    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    /* wait for a bit */
    msg = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR,
        500 * GST_MSECOND);
    fail_unless (msg == NULL);

    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref (pipeline);
    gst_object_unref (bus);
  }

  /* clean up and iterate so the clean-up can finish */
  stop_server ();
  iterate ();
}

GST_END_TEST;

static Suite *
rtspclientsink_suite (void)
{
  Suite *s = suite_create ("rtspclientsink");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_add_checked_fixture (tc, setup, teardown);
  tcase_set_timeout (tc, 120);
  tcase_add_test (tc, test_record);
  tcase_add_test (tc, test_record_no_data);
  return s;
}

GST_CHECK_MAIN (rtspclientsink);
   0707010000007D000081A400000000000000000000000169D554BF00014A5A000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/tests/check/gst/rtspserver.c   /* GStreamer unit test for GstRTSPServer
 * Copyright (C) 2012 Axis Communications <dev-gstreamer at axis dot com>
 *   @author David Svensson Fors <davidsf at axis dot com>
 * Copyright (C) 2015 Centricular Ltd
 *   @author Tim-Philipp Müller <tim@centricular.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>
#include <gst/sdp/gstsdpmessage.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>

#include <stdio.h>
#include <netinet/in.h>

#include "rtsp-server.h"

#define ERRORIGNORE "errorignore ignore-error=false ignore-notlinked=true " \
  "ignore-notnegotiated=false convert-to=ok"
#define VIDEO_PIPELINE "videotestsrc ! " \
  ERRORIGNORE " ! " \
  "video/x-raw,format=I420,width=352,height=288 ! " \
  "rtpgstpay name=pay0 pt=96"
#define AUDIO_PIPELINE "audiotestsrc ! " \
  ERRORIGNORE " ! " \
  "audio/x-raw,rate=8000 ! " \
  "rtpgstpay name=pay1 pt=97"

#define TEST_MOUNT_POINT  "/test"
#define TEST_PROTO        "RTP/AVP"
#define TEST_ENCODING     "X-GST"
#define TEST_CLOCK_RATE   "90000"

/* tested rtsp server */
static GstRTSPServer *server = NULL;

/* tcp port that the test server listens for rtsp requests on */
static gint test_port = 0;

/* id of the server's source within the GMainContext */
static guint source_id;

/* iterate the default main loop until there are no events to dispatch */
static void
iterate (void)
{
  while (g_main_context_iteration (NULL, FALSE)) {
    GST_DEBUG ("iteration");
  }
}

static void
get_client_ports_full (GstRTSPRange * range, GSocket ** rtp_socket,
    GSocket ** rtcp_socket)
{
  GSocket *rtp = NULL;
  GSocket *rtcp = NULL;
  gint rtp_port = 0;
  gint rtcp_port;
  GInetAddress *anyaddr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
  GSocketAddress *sockaddr;
  gboolean bound;

  for (;;) {
    if (rtp_port != 0)
      rtp_port += 2;

    rtp = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    fail_unless (rtp != NULL);

    sockaddr = g_inet_socket_address_new (anyaddr, rtp_port);
    fail_unless (sockaddr != NULL);
    bound = g_socket_bind (rtp, sockaddr, FALSE, NULL);
    g_object_unref (sockaddr);
    if (!bound) {
      g_object_unref (rtp);
      continue;
    }

    sockaddr = g_socket_get_local_address (rtp, NULL);
    fail_unless (sockaddr != NULL && G_IS_INET_SOCKET_ADDRESS (sockaddr));
    rtp_port =
        g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));
    g_object_unref (sockaddr);

    if (rtp_port % 2 != 0) {
      rtp_port += 1;
      g_object_unref (rtp);
      continue;
    }

    rtcp_port = rtp_port + 1;

    rtcp = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    fail_unless (rtcp != NULL);

    sockaddr = g_inet_socket_address_new (anyaddr, rtcp_port);
    fail_unless (sockaddr != NULL);
    bound = g_socket_bind (rtcp, sockaddr, FALSE, NULL);
    g_object_unref (sockaddr);
    if (!bound) {
      g_object_unref (rtp);
      g_object_unref (rtcp);
      continue;
    }

    sockaddr = g_socket_get_local_address (rtcp, NULL);
    fail_unless (sockaddr != NULL && G_IS_INET_SOCKET_ADDRESS (sockaddr));
    fail_unless (rtcp_port ==
        g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr)));
    g_object_unref (sockaddr);

    break;
  }

  range->min = rtp_port;
  range->max = rtcp_port;
  if (rtp_socket)
    *rtp_socket = rtp;
  else
    g_object_unref (rtp);
  if (rtcp_socket)
    *rtcp_socket = rtcp;
  else
    g_object_unref (rtcp);
  GST_DEBUG ("client_port=%d-%d", range->min, range->max);
  g_object_unref (anyaddr);
}

/* get a free rtp/rtcp client port pair */
static void
get_client_ports (GstRTSPRange * range)
{
  get_client_ports_full (range, NULL, NULL);
}

/* start the tested rtsp server */
static void
start_server (gboolean set_shared_factory)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *pool;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* use an address pool for multicast */
  pool = gst_rtsp_address_pool_new ();
  gst_rtsp_address_pool_add_range (pool,
      "224.3.0.0", "224.3.0.10", 5500, 5510, 16);
  gst_rtsp_address_pool_add_range (pool, GST_RTSP_ADDRESS_POOL_ANY_IPV4,
      GST_RTSP_ADDRESS_POOL_ANY_IPV4, 6000, 6010, 0);
  gst_rtsp_media_factory_set_address_pool (factory, pool);
  gst_rtsp_media_factory_set_shared (factory, set_shared_factory);
  gst_object_unref (pool);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);
}

static void
start_tcp_server (gboolean set_shared_factory)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_protocols (factory, GST_RTSP_LOWER_TRANS_TCP);
  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  gst_rtsp_media_factory_set_shared (factory, set_shared_factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);

}

/* start the testing rtsp server for RECORD mode */
static GstRTSPMediaFactory *
start_record_server (const gchar * launch_line)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMountPoints *mounts;
  gchar *service;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_transport_mode (factory,
      GST_RTSP_TRANSPORT_MODE_RECORD);
  gst_rtsp_media_factory_set_launch (factory, launch_line);
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);
  return factory;
}

/* stop the tested rtsp server */
static void
stop_server (void)
{
  g_source_remove (source_id);
  source_id = 0;

  GST_DEBUG ("rtsp server stopped");
}

/* create an rtsp connection to the server on test_port */
static GstRTSPConnection *
connect_to_server (gint port, const gchar * mount_point)
{
  GstRTSPConnection *conn = NULL;
  gchar *address;
  gchar *uri_string;
  GstRTSPUrl *url = NULL;

  address = gst_rtsp_server_get_address (server);
  uri_string = g_strdup_printf ("rtsp://%s:%d%s", address, port, mount_point);
  g_free (address);
  fail_unless (gst_rtsp_url_parse (uri_string, &url) == GST_RTSP_OK);
  g_free (uri_string);

  fail_unless (gst_rtsp_connection_create (url, &conn) == GST_RTSP_OK);
  gst_rtsp_url_free (url);

  fail_unless (gst_rtsp_connection_connect (conn, NULL) == GST_RTSP_OK);

  return conn;
}

/* create an rtsp request */
static GstRTSPMessage *
create_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control)
{
  GstRTSPMessage *request = NULL;
  gchar *base_uri;
  gchar *full_uri;

  base_uri = gst_rtsp_url_get_request_uri (gst_rtsp_connection_get_url (conn));
  full_uri = g_strdup_printf ("%s/%s", base_uri, control ? control : "");
  g_free (base_uri);
  if (gst_rtsp_message_new_request (&request, method, full_uri) != GST_RTSP_OK) {
    GST_DEBUG ("failed to create request object");
    g_free (full_uri);
    return NULL;
  }
  g_free (full_uri);
  return request;
}

/* send an rtsp request */
static gboolean
send_request (GstRTSPConnection * conn, GstRTSPMessage * request)
{
  if (gst_rtsp_connection_send (conn, request, NULL) != GST_RTSP_OK) {
    GST_DEBUG ("failed to send request");
    return FALSE;
  }
  return TRUE;
}

/* read rtsp response. response must be freed by the caller */
static GstRTSPMessage *
read_response (GstRTSPConnection * conn)
{
  GstRTSPMessage *response = NULL;
  GstRTSPMsgType type;

  if (gst_rtsp_message_new (&response) != GST_RTSP_OK) {
    GST_DEBUG ("failed to create response object");
    return NULL;
  }
  if (gst_rtsp_connection_receive (conn, response, NULL) != GST_RTSP_OK) {
    GST_DEBUG ("failed to read response");
    gst_rtsp_message_free (response);
    return NULL;
  }
  type = gst_rtsp_message_get_type (response);
  fail_unless (type == GST_RTSP_MESSAGE_RESPONSE
      || type == GST_RTSP_MESSAGE_DATA);
  return response;
}

/* send an rtsp request and receive response. gchar** parameters are out
 * parameters that have to be freed by the caller */
static GstRTSPStatusCode
do_request_full (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control, const gchar * session_in, const gchar * transport_in,
    const gchar * range_in, const gchar * require_in,
    gchar ** content_type, gchar ** content_base, gchar ** body,
    gchar ** session_out, gchar ** transport_out, gchar ** range_out,
    gchar ** unsupported_out)
{
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  gchar *value;
  GstRTSPMsgType msg_type;

  /* create request */
  request = create_request (conn, method, control);

  /* add headers */
  if (session_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_SESSION, session_in);
  }
  if (transport_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_TRANSPORT, transport_in);
  }
  if (range_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_RANGE, range_in);
  }
  if (require_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_REQUIRE, require_in);
  }

  /* send request */
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  iterate ();

  /* read response */
  response = read_response (conn);
  fail_unless (response != NULL);

  msg_type = gst_rtsp_message_get_type (response);

  if (msg_type == GST_RTSP_MESSAGE_DATA) {
    do {
      gst_rtsp_message_free (response);
      response = read_response (conn);
      msg_type = gst_rtsp_message_get_type (response);
    } while (msg_type == GST_RTSP_MESSAGE_DATA);
  }

  fail_unless (msg_type == GST_RTSP_MESSAGE_RESPONSE);

  /* check status line */
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);
  if (code != GST_RTSP_STS_OK) {
    if (unsupported_out != NULL && code == GST_RTSP_STS_OPTION_NOT_SUPPORTED) {
      gst_rtsp_message_get_header (response, GST_RTSP_HDR_UNSUPPORTED,
          &value, 0);
      *unsupported_out = g_strdup (value);
    }
    gst_rtsp_message_free (response);
    return code;
  }

  /* get information from response */
  if (content_type) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_TYPE,
        &value, 0);
    *content_type = g_strdup (value);
  }
  if (content_base) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE,
        &value, 0);
    *content_base = g_strdup (value);
  }
  if (body) {
    *body = g_malloc (response->body_size + 1);
    strncpy (*body, (gchar *) response->body, response->body_size);
  }
  if (session_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &value, 0);

    value = g_strdup (value);

    /* Remove the timeout */
    if (value) {
      char *pos = strchr (value, ';');
      if (pos)
        *pos = 0;
    }
    if (session_in) {
      /* check that we got the same session back */
      fail_unless (!g_strcmp0 (value, session_in));
    }
    *session_out = value;
  }
  if (transport_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_TRANSPORT, &value, 0);
    *transport_out = g_strdup (value);
  }
  if (range_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_RANGE, &value, 0);
    *range_out = g_strdup (value);
  }

  gst_rtsp_message_free (response);
  return code;
}

/* send an rtsp request and receive response. gchar** parameters are out
 * parameters that have to be freed by the caller */
static GstRTSPStatusCode
do_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control, const gchar * session_in,
    const gchar * transport_in, const gchar * range_in,
    gchar ** content_type, gchar ** content_base, gchar ** body,
    gchar ** session_out, gchar ** transport_out, gchar ** range_out)
{
  return do_request_full (conn, method, control, session_in, transport_in,
      range_in, NULL, content_type, content_base, body, session_out,
      transport_out, range_out, NULL);
}

/* send an rtsp request with a method and a session, and receive response */
static GstRTSPStatusCode
do_simple_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * session)
{
  return do_request (conn, method, NULL, session, NULL, NULL, NULL,
      NULL, NULL, NULL, NULL, NULL);
}

/* send an rtsp request with a method,session and range in,
 * and receive response. range_in is the Range in req header */
static GstRTSPStatusCode
do_simple_request_rangein (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * session, const gchar * rangein)
{
  return do_request (conn, method, NULL, session, NULL, rangein, NULL,
      NULL, NULL, NULL, NULL, NULL);
}

/* send a DESCRIBE request and receive response. returns a received
 * GstSDPMessage that must be freed by the caller */
static GstSDPMessage *
do_describe (GstRTSPConnection * conn, const gchar * mount_point)
{
  GstSDPMessage *sdp_message;
  gchar *content_type = NULL;
  gchar *content_base = NULL;
  gchar *body = NULL;
  gchar *address;
  gchar *expected_content_base;

  /* send DESCRIBE request */
  fail_unless (do_request (conn, GST_RTSP_DESCRIBE, NULL, NULL, NULL, NULL,
          &content_type, &content_base, &body, NULL, NULL, NULL) ==
      GST_RTSP_STS_OK);

  /* check response values */
  fail_unless (!g_strcmp0 (content_type, "application/sdp"));
  address = gst_rtsp_server_get_address (server);
  expected_content_base =
      g_strdup_printf ("rtsp://%s:%d%s/", address, test_port, mount_point);
  fail_unless (!g_strcmp0 (content_base, expected_content_base));

  /* create sdp message */
  fail_unless (gst_sdp_message_new (&sdp_message) == GST_SDP_OK);
  fail_unless (gst_sdp_message_parse_buffer ((guint8 *) body,
          strlen (body), sdp_message) == GST_SDP_OK);

  /* clean up */
  g_free (content_type);
  g_free (content_base);
  g_free (body);
  g_free (address);
  g_free (expected_content_base);

  return sdp_message;
}

/* send a SETUP request and receive response. if *session is not NULL,
 * it is used in the request. otherwise, *session is set to a returned
 * session string that must be freed by the caller. the returned
 * transport must be freed by the caller. */
static GstRTSPStatusCode
do_setup_full (GstRTSPConnection * conn, const gchar * control,
    GstRTSPLowerTrans lower_transport, const GstRTSPRange * client_ports,
    const gchar * require, gchar ** session, GstRTSPTransport ** transport,
    gchar ** unsupported)
{
  GstRTSPStatusCode code;
  gchar *session_in = NULL;
  GString *transport_string_in = NULL;
  gchar **session_out = NULL;
  gchar *transport_string_out = NULL;

  /* prepare and send SETUP request */
  if (session) {
    if (*session) {
      session_in = *session;
    } else {
      session_out = session;
    }
  }

  transport_string_in = g_string_new (TEST_PROTO);
  switch (lower_transport) {
    case GST_RTSP_LOWER_TRANS_UDP:
      transport_string_in =
          g_string_append (transport_string_in, "/UDP;unicast");
      break;
    case GST_RTSP_LOWER_TRANS_UDP_MCAST:
      transport_string_in =
          g_string_append (transport_string_in, "/UDP;multicast");
      break;
    case GST_RTSP_LOWER_TRANS_TCP:
      transport_string_in =
          g_string_append (transport_string_in, "/TCP;unicast");
      break;
    default:
      g_assert_not_reached ();
      break;
  }

  if (client_ports) {
    g_string_append_printf (transport_string_in, ";client_port=%d-%d",
        client_ports->min, client_ports->max);
  }

  code =
      do_request_full (conn, GST_RTSP_SETUP, control, session_in,
      transport_string_in->str, NULL, require, NULL, NULL, NULL, session_out,
      &transport_string_out, NULL, unsupported);
  g_string_free (transport_string_in, TRUE);

  if (transport_string_out) {
    /* create transport */
    fail_unless (gst_rtsp_transport_new (transport) == GST_RTSP_OK);
    fail_unless (gst_rtsp_transport_parse (transport_string_out,
            *transport) == GST_RTSP_OK);
    g_free (transport_string_out);
  }
  GST_INFO ("code=%d", code);
  return code;
}

/* send a SETUP request and receive response. if *session is not NULL,
 * it is used in the request. otherwise, *session is set to a returned
 * session string that must be freed by the caller. the returned
 * transport must be freed by the caller. */
static GstRTSPStatusCode
do_setup (GstRTSPConnection * conn, const gchar * control,
    const GstRTSPRange * client_ports, gchar ** session,
    GstRTSPTransport ** transport)
{
  return do_setup_full (conn, control, GST_RTSP_LOWER_TRANS_UDP, client_ports,
      NULL, session, transport, NULL);
}

/* fixture setup function */
static void
setup (void)
{
  server = gst_rtsp_server_new ();
}

/* fixture clean-up function */
static void
teardown (void)
{
  if (server) {
    g_object_unref (server);
    server = NULL;
  }
  test_port = 0;
  gst_rtsp_thread_pool_cleanup ();
}

GST_START_TEST (test_connect)
{
  GstRTSPConnection *conn;

  start_server (FALSE);

  /* connect to server */
  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* clean up */
  gst_rtsp_connection_free (conn);
  stop_server ();

  /* iterate so the clean-up can finish */
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  gint32 format;
  gchar *expected_rtpmap;
  const gchar *rtpmap;
  const gchar *control_video;
  const gchar *control_audio;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send DESCRIBE request */
  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);

  /* check video sdp */
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  fail_unless (!g_strcmp0 (gst_sdp_media_get_proto (sdp_media), TEST_PROTO));
  fail_unless (gst_sdp_media_formats_len (sdp_media) == 1);
  sscanf (gst_sdp_media_get_format (sdp_media, 0), "%" G_GINT32_FORMAT,
      &format);
  expected_rtpmap =
      g_strdup_printf ("%d " TEST_ENCODING "/" TEST_CLOCK_RATE, format);
  rtpmap = gst_sdp_media_get_attribute_val (sdp_media, "rtpmap");
  fail_unless (!g_strcmp0 (rtpmap, expected_rtpmap));
  g_free (expected_rtpmap);
  control_video = gst_sdp_media_get_attribute_val (sdp_media, "control");
  fail_unless (!g_strcmp0 (control_video, "stream=0"));

  /* check audio sdp */
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  fail_unless (!g_strcmp0 (gst_sdp_media_get_proto (sdp_media), TEST_PROTO));
  fail_unless (gst_sdp_media_formats_len (sdp_media) == 1);
  sscanf (gst_sdp_media_get_format (sdp_media, 0), "%" G_GINT32_FORMAT,
      &format);
  expected_rtpmap =
      g_strdup_printf ("%d " TEST_ENCODING "/" TEST_CLOCK_RATE, format);
  rtpmap = gst_sdp_media_get_attribute_val (sdp_media, "rtpmap");
  fail_unless (!g_strcmp0 (rtpmap, expected_rtpmap));
  g_free (expected_rtpmap);
  control_audio = gst_sdp_media_get_attribute_val (sdp_media, "control");
  fail_unless (!g_strcmp0 (control_audio, "stream=1"));

  /* clean up and iterate so the clean-up can finish */
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe_record_media)
{
  GstRTSPConnection *conn;

  start_record_server ("( fakesink name=depay0 )");

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send DESCRIBE request */
  fail_unless_equals_int (do_request (conn, GST_RTSP_DESCRIBE, NULL, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL, NULL),
      GST_RTSP_STS_METHOD_NOT_ALLOWED);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe_non_existing_mount_point)
{
  GstRTSPConnection *conn;

  start_server (FALSE);

  /* send DESCRIBE request for a non-existing mount point
   * and check that we get a 404 Not Found */
  conn = connect_to_server (test_port, "/non-existing");
  fail_unless (do_simple_request (conn, GST_RTSP_DESCRIBE, NULL)
      == GST_RTSP_STS_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

static void
do_test_setup (GstRTSPLowerTrans lower_transport)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_ports = { 0 };
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for video */
  fail_unless (do_setup_full (conn, video_control, lower_transport,
          &client_ports, NULL, &session, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == lower_transport);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for audio */
  fail_unless (do_setup_full (conn, audio_control, lower_transport,
          &client_ports, NULL, &session, &audio_transport,
          NULL) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up audio %s with session '%s'", audio_control, session);

  /* check response from SETUP */
  fail_unless (audio_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (audio_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (audio_transport->lower_transport == lower_transport);
  fail_unless (audio_transport->mode_play);
  gst_rtsp_transport_free (audio_transport);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_START_TEST (test_setup_udp)
{
  do_test_setup (GST_RTSP_LOWER_TRANS_UDP);
}

GST_END_TEST;

GST_START_TEST (test_setup_tcp)
{
  do_test_setup (GST_RTSP_LOWER_TRANS_TCP);
}

GST_END_TEST;

GST_START_TEST (test_setup_udp_mcast)
{
  do_test_setup (GST_RTSP_LOWER_TRANS_UDP_MCAST);
}

GST_END_TEST;

GST_START_TEST (test_setup_twice)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_ports;
  GstRTSPTransport *video_transport = NULL;
  gchar *session1 = NULL;
  gchar *session2 = NULL;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* we wan't more than one session for this connection */
  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get the control url */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for one session */
  fail_unless (do_setup (conn, video_control, &client_ports, &session1,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session1);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for another session */
  fail_unless (do_setup (conn, video_control, &client_ports, &session2,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session2);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* session can not be the same */
  fail_unless (strcmp (session1, session2));

  /* send TEARDOWN request for the first session */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session1) == GST_RTSP_STS_OK);

  /* send TEARDOWN request for the second session */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session2) == GST_RTSP_STS_OK);

  g_free (session1);
  g_free (session2);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_with_require_header)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_ports;
  gchar *session = NULL;
  gchar *unsupported = NULL;
  GstRTSPTransport *video_transport = NULL;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for video, with single Require header */
  fail_unless_equals_int (do_setup_full (conn, video_control,
          GST_RTSP_LOWER_TRANS_UDP, &client_ports, "funky-feature", &session,
          &video_transport, &unsupported), GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless_equals_string (unsupported, "funky-feature");
  g_free (unsupported);
  unsupported = NULL;

  /* send SETUP request for video, with multiple Require headers */
  fail_unless_equals_int (do_setup_full (conn, video_control,
          GST_RTSP_LOWER_TRANS_UDP, &client_ports,
          "funky-feature, foo-bar, superburst", &session, &video_transport,
          &unsupported), GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless_equals_string (unsupported, "funky-feature, foo-bar, superburst");
  g_free (unsupported);
  unsupported = NULL;

  /* ok, just do a normal setup then (make sure that still works) */
  fail_unless_equals_int (do_setup (conn, video_control, &client_ports,
          &session, &video_transport), GST_RTSP_STS_OK);

  GST_DEBUG ("set up video %s, got session '%s'", video_control, session);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_non_existing_stream)
{
  GstRTSPConnection *conn;
  GstRTSPRange client_ports;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  get_client_ports (&client_ports);

  /* send SETUP request with a non-existing stream and check that we get a
   * 404 Not Found */
  fail_unless (do_setup (conn, "stream=7", &client_ports, NULL,
          NULL) == GST_RTSP_STS_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

static void
receive_rtp (GSocket * socket, GSocketAddress ** addr)
{
  GstBuffer *buffer = gst_buffer_new_allocate (NULL, 65536, NULL);

  for (;;) {
    gssize bytes;
    GstMapInfo map = GST_MAP_INFO_INIT;
    GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT;

    gst_buffer_map (buffer, &map, GST_MAP_WRITE);
    bytes = g_socket_receive_from (socket, addr, (gchar *) map.data,
        map.maxsize, NULL, NULL);
    fail_unless (bytes > 0);
    gst_buffer_unmap (buffer, &map);
    gst_buffer_set_size (buffer, bytes);

    if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtpbuffer)) {
      gst_rtp_buffer_unmap (&rtpbuffer);
      break;
    }

    if (addr)
      g_clear_object (addr);
  }

  gst_buffer_unref (buffer);
}

static void
receive_rtcp (GSocket * socket, GSocketAddress ** addr, GstRTCPType type)
{
  GstBuffer *buffer = gst_buffer_new_allocate (NULL, 65536, NULL);

  for (;;) {
    gssize bytes;
    GstMapInfo map = GST_MAP_INFO_INIT;

    gst_buffer_map (buffer, &map, GST_MAP_WRITE);
    bytes = g_socket_receive_from (socket, addr, (gchar *) map.data,
        map.maxsize, NULL, NULL);
    fail_unless (bytes > 0);
    gst_buffer_unmap (buffer, &map);
    gst_buffer_set_size (buffer, bytes);

    if (gst_rtcp_buffer_validate (buffer)) {
      GstRTCPBuffer rtcpbuffer = GST_RTCP_BUFFER_INIT;
      GstRTCPPacket packet;

      if (type) {
        fail_unless (gst_rtcp_buffer_map (buffer, GST_MAP_READ, &rtcpbuffer));
        fail_unless (gst_rtcp_buffer_get_first_packet (&rtcpbuffer, &packet));
        do {
          if (gst_rtcp_packet_get_type (&packet) == type) {
            gst_rtcp_buffer_unmap (&rtcpbuffer);
            goto done;
          }
        } while (gst_rtcp_packet_move_to_next (&packet));
        gst_rtcp_buffer_unmap (&rtcpbuffer);
      } else {
        break;
      }
    }

    if (addr)
      g_clear_object (addr);
  }

done:

  gst_buffer_unref (buffer);
}

static void
do_test_play_tcp_full (const gchar * range)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  gchar *range_out = NULL;
  GstRTSPLowerTrans lower_transport = GST_RTSP_LOWER_TRANS_TCP;

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);
  get_client_ports (&client_port);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  /* do SETUP for video and audio */
  fail_unless (do_setup_full (conn, video_control, lower_transport,
          &client_port, NULL, &session, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  fail_unless (do_setup_full (conn, audio_control, lower_transport,
          &client_port, NULL, &session, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_request (conn, GST_RTSP_PLAY, NULL, session, NULL, range,
          NULL, NULL, NULL, NULL, NULL, &range_out) == GST_RTSP_STS_OK);

  if (range)
    fail_unless_equals_string (range, range_out);
  g_free (range_out);

  {
    GstRTSPMessage *message;
    fail_unless (gst_rtsp_message_new (&message) == GST_RTSP_OK);
    fail_unless (gst_rtsp_connection_receive (conn, message,
            NULL) == GST_RTSP_OK);
    fail_unless (gst_rtsp_message_get_type (message) == GST_RTSP_MESSAGE_DATA);
    gst_rtsp_message_free (message);
  }

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* FIXME: The rtsp-server always disconnects the transport before
   * sending the RTCP BYE
   * receive_rtcp (rtcp_socket, NULL, GST_RTCP_TYPE_BYE);
   */

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
}

static void
do_test_play_full (const gchar * range, GstRTSPLowerTrans lower_transport,
    GMutex * lock)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;
  gchar *range_out = NULL;

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for video and audio */
  fail_unless (do_setup_full (conn, video_control, lower_transport,
          &client_port, NULL, &session, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  fail_unless (do_setup_full (conn, audio_control, lower_transport,
          &client_port, NULL, &session, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_request (conn, GST_RTSP_PLAY, NULL, session, NULL, range,
          NULL, NULL, NULL, NULL, NULL, &range_out) == GST_RTSP_STS_OK);
  if (range)
    fail_unless_equals_string (range, range_out);
  g_free (range_out);

  for (;;) {
    receive_rtp (rtp_socket, NULL);
    receive_rtcp (rtcp_socket, NULL, 0);

    if (lock != NULL) {
      if (g_mutex_trylock (lock) == TRUE) {
        g_mutex_unlock (lock);
        break;
      }
    } else {
      break;
    }

  }

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* FIXME: The rtsp-server always disconnects the transport before
   * sending the RTCP BYE
   * receive_rtcp (rtcp_socket, NULL, GST_RTCP_TYPE_BYE);
   */

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
}

static void
do_test_play (const gchar * range)
{
  do_test_play_full (range, GST_RTSP_LOWER_TRANS_UDP, NULL);
}

GST_START_TEST (test_play)
{
  start_server (FALSE);

  do_test_play (NULL);

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_play_tcp)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_ports = { 0 };
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;

  start_tcp_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send DESCRIBE request */
  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for the first media */
  fail_unless (do_setup_full (conn, video_control, GST_RTSP_LOWER_TRANS_TCP,
          &client_ports, NULL, &session, &video_transport,
          NULL) == GST_RTSP_STS_OK);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for the second media */
  fail_unless (do_setup_full (conn, audio_control, GST_RTSP_LOWER_TRANS_TCP,
          &client_ports, NULL, &session, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  /* check response from SETUP */
  fail_unless (audio_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (audio_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (audio_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (audio_transport->mode_play);
  gst_rtsp_transport_free (audio_transport);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_play_without_session)
{
  GstRTSPConnection *conn;

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send PLAY request without a session and check that we get a
   * 454 Session Not Found */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          NULL) == GST_RTSP_STS_SESSION_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_bind_already_in_use)
{
  GstRTSPServer *serv;
  GSocketService *service;
  GError *error = NULL;
  guint16 port;
  gchar *port_str;

  serv = gst_rtsp_server_new ();
  service = g_socket_service_new ();

  /* bind service to port */
  port =
      g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL,
      &error);
  g_assert_no_error (error);

  port_str = g_strdup_printf ("%d\n", port);

  /* try to bind server to the same port */
  g_object_set (serv, "service", port_str, NULL);
  g_free (port_str);

  /* attach to default main context */
  fail_unless (gst_rtsp_server_attach (serv, NULL) == 0);

  /* cleanup */
  g_object_unref (serv);
  g_socket_service_stop (service);
  g_object_unref (service);
}

GST_END_TEST;


GST_START_TEST (test_play_multithreaded)
{
  GstRTSPThreadPool *pool;

  pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (pool, 2);
  g_object_unref (pool);

  start_server (FALSE);

  do_test_play (NULL);

  stop_server ();
  iterate ();
}

GST_END_TEST;

enum
{
  BLOCK_ME,
  BLOCKED,
  UNBLOCK
};


static void
media_constructed_block (GstRTSPMediaFactory * factory,
    GstRTSPMedia * media, gpointer user_data)
{
  gint *block_state = user_data;

  g_mutex_lock (&check_mutex);

  *block_state = BLOCKED;
  g_cond_broadcast (&check_cond);

  while (*block_state != UNBLOCK)
    g_cond_wait (&check_cond, &check_mutex);
  g_mutex_unlock (&check_mutex);
}


GST_START_TEST (test_play_multithreaded_block_in_describe)
{
  GstRTSPConnection *conn;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  gint block_state = BLOCK_ME;
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  GstRTSPThreadPool *pool;

  pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (pool, 2);
  g_object_unref (pool);

  mounts = gst_rtsp_server_get_mount_points (server);
  fail_unless (mounts != NULL);
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  g_signal_connect (factory, "media-constructed",
      G_CALLBACK (media_constructed_block), &block_state);
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT "2", factory);
  g_object_unref (mounts);

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT "2");
  iterate ();

  /* do describe, it will not return now as we've blocked it */
  request = create_request (conn, GST_RTSP_DESCRIBE, NULL);
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  g_mutex_lock (&check_mutex);
  while (block_state != BLOCKED)
    g_cond_wait (&check_cond, &check_mutex);
  g_mutex_unlock (&check_mutex);

  /* Do a second connection while the first one is blocked */
  do_test_play (NULL);

  /* Now unblock the describe */
  g_mutex_lock (&check_mutex);
  block_state = UNBLOCK;
  g_cond_broadcast (&check_cond);
  g_mutex_unlock (&check_mutex);

  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);
  fail_unless (code == GST_RTSP_STS_OK);
  gst_rtsp_message_free (response);


  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();

}

GST_END_TEST;


static void
new_session_timeout_one (GstRTSPClient * client,
    GstRTSPSession * session, gpointer user_data)
{
  gst_rtsp_session_set_timeout (session, 1);

  g_signal_handlers_disconnect_by_func (client, new_session_timeout_one,
      user_data);
}

static void
session_connected_new_session_cb (GstRTSPServer * server,
    GstRTSPClient * client, gpointer user_data)
{

  g_signal_connect (client, "new-session", user_data, NULL);
}

GST_START_TEST (test_play_multithreaded_timeout_client)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (thread_pool, 2);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server (FALSE);


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup_full (conn, video_control, GST_RTSP_LOWER_TRANS_UDP,
          &client_port, NULL, &session, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  fail_unless (do_setup_full (conn, audio_control, GST_RTSP_LOWER_TRANS_UDP,
          &client_port, NULL, &session, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 0);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;


GST_START_TEST (test_play_multithreaded_timeout_session)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session1 = NULL;
  gchar *session2 = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (thread_pool, 2);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server (FALSE);


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session1,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session2,
          &audio_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 2);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session1) == GST_RTSP_STS_OK);
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session2) == GST_RTSP_STS_OK);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);

  /* send TEARDOWN request and check that we get 454 Session Not found */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session1) == GST_RTSP_STS_SESSION_NOT_FOUND);

  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session2) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session1);
  g_free (session2);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;

static void
new_connection_and_session_timeout_one (GstRTSPClient * client,
    GstRTSPSession * session, gpointer user_data)
{
  gint ps_timeout = 0;

  g_object_set (G_OBJECT (client), "post-session-timeout", 1, NULL);
  g_object_get (G_OBJECT (client), "post-session-timeout", &ps_timeout, NULL);
  fail_unless_equals_int (ps_timeout, 1);

  g_object_set (G_OBJECT (session), "extra-timeout", 0, NULL);
  gst_rtsp_session_set_timeout (session, 1);

  g_signal_handlers_disconnect_by_func (client,
      new_connection_and_session_timeout_one, user_data);
}

GST_START_TEST (test_play_timeout_connection)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;
  GstRTSPMessage *request;
  GstRTSPMessage *response;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb),
      new_connection_and_session_timeout_one);

  start_server (FALSE);


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);
  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);
  sleep (2);
  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);
  sleep (3);

  request = create_request (conn, GST_RTSP_TEARDOWN, NULL);

  /* add headers */
  if (session) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_SESSION, session);
  }

  /* send request */
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  iterate ();

  /* read response */
  response = read_response (conn);
  fail_unless (response == NULL);

  if (response) {
    gst_rtsp_message_free (response);
  }

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_no_session_timeout)
{
  GstRTSPSession *session;
  gint64 now;
  gboolean is_expired;

  session = gst_rtsp_session_new ("test-session");
  gst_rtsp_session_set_timeout (session, 0);

  now = g_get_monotonic_time ();
  /* add more than the extra 5 seconds that are usually added in
   * gst_rtsp_session_next_timeout_usec */
  now += 7000000;

  is_expired = gst_rtsp_session_is_expired_usec (session, now);
  fail_unless (is_expired == FALSE);

  g_object_unref (session);
}

GST_END_TEST;

/* media contains two streams: video and audio but only one
 * stream is requested */
GST_START_TEST (test_play_one_active_stream)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (thread_pool, 2);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video only */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);


  /* send TEARDOWN request */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_play_disconnect)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server (FALSE);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  gst_rtsp_connection_free (conn);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);
  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);


  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);

  stop_server ();
  iterate ();
}

GST_END_TEST;

/* Only different with test_play is the specific ports selected */

GST_START_TEST (test_play_specific_server_port)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *pool;
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;
  GSocketAddress *rtp_address, *rtcp_address;
  guint16 rtp_port, rtcp_port;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();
  /* we have to suspend media after SDP in order to make sure that
   * we can reconfigure UDP sink with new UDP ports */
  gst_rtsp_media_factory_set_suspend_mode (factory,
      GST_RTSP_SUSPEND_MODE_RESET);
  pool = gst_rtsp_address_pool_new ();
  gst_rtsp_address_pool_add_range (pool, GST_RTSP_ADDRESS_POOL_ANY_IPV4,
      GST_RTSP_ADDRESS_POOL_ANY_IPV4, 7770, 7780, 0);
  gst_rtsp_media_factory_set_address_pool (factory, pool);
  g_object_unref (pool);
  gst_rtsp_media_factory_set_launch (factory, "( " VIDEO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 1);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for video */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  receive_rtp (rtp_socket, &rtp_address);
  receive_rtcp (rtcp_socket, &rtcp_address, 0);

  fail_unless (G_IS_INET_SOCKET_ADDRESS (rtp_address));
  fail_unless (G_IS_INET_SOCKET_ADDRESS (rtcp_address));
  rtp_port =
      g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtp_address));
  rtcp_port =
      g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtcp_address));
  fail_unless (rtp_port >= 7770 && rtp_port <= 7780 && rtp_port % 2 == 0);
  fail_unless (rtcp_port >= 7770 && rtcp_port <= 7780 && rtcp_port % 2 == 1);
  fail_unless (rtp_port + 1 == rtcp_port);

  g_object_unref (rtp_address);
  g_object_unref (rtcp_address);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* FIXME: The rtsp-server always disconnects the transport before
   * sending the RTCP BYE
   * receive_rtcp (rtcp_socket, NULL, GST_RTCP_TYPE_BYE);
   */

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);


  stop_server ();
  iterate ();
}

GST_END_TEST;


GST_START_TEST (test_play_smpte_range)
{
  start_server (FALSE);

  do_test_play ("npt=5-");
  do_test_play ("smpte=0:00:00-");
  do_test_play ("smpte=1:00:00-");
  do_test_play ("smpte=1:00:03-");
  do_test_play ("clock=20120321T152256Z-");

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_play_smpte_range_tcp)
{
  start_tcp_server (FALSE);

  do_test_play_tcp_full ("npt=5-");
  do_test_play_tcp_full ("smpte=0:00:00-");
  do_test_play_tcp_full ("smpte=1:00:00-");
  do_test_play_tcp_full ("smpte=1:00:03-");
  do_test_play_tcp_full ("clock=20120321T152256Z-");

  stop_server ();
  iterate ();
}

GST_END_TEST;

static gpointer
thread_func_udp (gpointer data)
{
  do_test_play_full (NULL, GST_RTSP_LOWER_TRANS_UDP, (GMutex *) data);
  return NULL;
}

static gpointer
thread_func_tcp (gpointer data)
{
  do_test_play_tcp_full (NULL);
  return NULL;
}

static void
test_shared (gpointer (thread_func) (gpointer data))
{
  GMutex lock1, lock2, lock3, lock4;
  GThread *thread1, *thread2, *thread3, *thread4;

  /* Locks for each thread. Each thread will keep reading data as long as the
   * thread is locked. */
  g_mutex_init (&lock1);
  g_mutex_init (&lock2);
  g_mutex_init (&lock3);
  g_mutex_init (&lock4);

  if (thread_func == thread_func_tcp)
    start_tcp_server (TRUE);
  else
    start_server (TRUE);

  /* Start the first receiver thread. */
  g_mutex_lock (&lock1);
  thread1 = g_thread_new ("thread1", thread_func, &lock1);

  /* Connect and disconnect another client. */
  g_mutex_lock (&lock2);
  thread2 = g_thread_new ("thread2", thread_func, &lock2);
  g_mutex_unlock (&lock2);
  g_mutex_clear (&lock2);
  g_thread_join (thread2);

  /* Do it again. */
  g_mutex_lock (&lock3);
  thread3 = g_thread_new ("thread3", thread_func, &lock3);
  g_mutex_unlock (&lock3);
  g_mutex_clear (&lock3);
  g_thread_join (thread3);

  /* Disconnect the last client. This will clean up the media. */
  g_mutex_unlock (&lock1);
  g_mutex_clear (&lock1);
  g_thread_join (thread1);

  /* Connect and disconnect another client. This will create and clean up the 
   * media. */
  g_mutex_lock (&lock4);
  thread4 = g_thread_new ("thread4", thread_func, &lock4);
  g_mutex_unlock (&lock4);
  g_mutex_clear (&lock4);
  g_thread_join (thread4);

  stop_server ();
  iterate ();
}

/* Test adding and removing clients to a 'Shared' media.
 * CASE: unicast UDP */
GST_START_TEST (test_shared_udp)
{
  test_shared (thread_func_udp);
}

GST_END_TEST;

/* Test adding and removing clients to a 'Shared' media.
 * CASE: unicast TCP */
GST_START_TEST (test_shared_tcp)
{
  test_shared (thread_func_tcp);
}

GST_END_TEST;

GST_START_TEST (test_announce_without_sdp)
{
  GstRTSPConnection *conn;
  GstRTSPStatusCode status;
  GstRTSPMessage *request;
  GstRTSPMessage *response;

  start_record_server ("( fakesink name=depay0 )");

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* create and send ANNOUNCE request */
  request = create_request (conn, GST_RTSP_ANNOUNCE, NULL);

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* try again, this type with content-type, but still no SDP */
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* try again, this type with an unknown content-type */
  gst_rtsp_message_remove_header (request, GST_RTSP_HDR_CONTENT_TYPE, -1);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/x-something");

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_message_free (request);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

static GstRTSPStatusCode
do_announce (GstRTSPConnection * conn, GstSDPMessage * sdp)
{
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  gchar *str;

  /* create request */
  request = create_request (conn, GST_RTSP_ANNOUNCE, NULL);

  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  /* add SDP to the response body */
  str = gst_sdp_message_as_text (sdp);
  gst_rtsp_message_take_body (request, (guint8 *) str, strlen (str));
  gst_sdp_message_free (sdp);

  /* send request */
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  iterate ();

  /* read response */
  response = read_response (conn);

  /* check status line */
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);

  gst_rtsp_message_free (response);
  return code;
}

static void
media_constructed_cb (GstRTSPMediaFactory * mfactory, GstRTSPMedia * media,
    gpointer user_data)
{
  GstElement **p_sink = user_data;
  GstElement *bin;

  bin = gst_rtsp_media_get_element (media);
  *p_sink = gst_bin_get_by_name (GST_BIN (bin), "sink");
  GST_INFO ("media constructed!: %" GST_PTR_FORMAT, *p_sink);
  gst_object_unref (bin);
}

#define RECORD_N_BUFS 10

GST_START_TEST (test_record_tcp)
{
  GstRTSPMediaFactory *mfactory;
  GstRTSPConnection *conn;
  GstRTSPStatusCode status;
  GstRTSPMessage *response;
  GstRTSPMessage *request;
  GstSDPMessage *sdp;
  GstRTSPResult rres;
  GSocketAddress *sa;
  GInetAddress *ia;
  GstElement *server_sink = NULL;
  GSocket *conn_socket;
  const gchar *proto;
  gchar *client_ip, *sess_id, *session = NULL;
  gint i;

  mfactory =
      start_record_server
      ("( rtppcmadepay name=depay0 ! appsink name=sink async=false )");

  g_signal_connect (mfactory, "media-constructed",
      G_CALLBACK (media_constructed_cb), &server_sink);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  conn_socket = gst_rtsp_connection_get_read_socket (conn);

  sa = g_socket_get_local_address (conn_socket, NULL);
  ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));
  client_ip = g_inet_address_to_string (ia);
  if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV6)
    proto = "IP6";
  else if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV4)
    proto = "IP4";
  else
    g_assert_not_reached ();
  g_object_unref (sa);

  gst_sdp_message_new (&sdp);

  /* some standard things first */
  gst_sdp_message_set_version (sdp, "0");

  /* session ID doesn't have to be super-unique in this case */
  sess_id = g_strdup_printf ("%u", g_random_int ());
  gst_sdp_message_set_origin (sdp, "-", sess_id, "1", "IN", proto, client_ip);
  g_free (sess_id);
  g_free (client_ip);

  gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
  gst_sdp_message_set_information (sdp, "rtsp-server-test");
  gst_sdp_message_add_time (sdp, "0", "0", NULL);
  gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");

  /* add stream 0 */
  {
    GstSDPMedia *smedia;

    gst_sdp_media_new (&smedia);
    gst_sdp_media_set_media (smedia, "audio");
    gst_sdp_media_add_format (smedia, "8");     /* pcma/alaw */
    gst_sdp_media_set_port_info (smedia, 0, 1);
    gst_sdp_media_set_proto (smedia, "RTP/AVP");
    gst_sdp_media_add_attribute (smedia, "rtpmap", "8 PCMA/8000");
    gst_sdp_message_add_media (sdp, smedia);
    gst_sdp_media_free (smedia);
  }

  /* send ANNOUNCE request */
  status = do_announce (conn, sdp);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);

  /* create and send SETUP request */
  request = create_request (conn, GST_RTSP_SETUP, NULL);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;interleaved=0;mode=record");
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);
  iterate ();
  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);

  rres =
      gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &session, 0);
  session = g_strdup (session);
  fail_unless_equals_int (rres, GST_RTSP_OK);
  gst_rtsp_message_free (response);

  /* send RECORD */
  request = create_request (conn, GST_RTSP_RECORD, NULL);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_SESSION, session);
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);
  iterate ();
  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);
  gst_rtsp_message_free (response);

  /* send some data */
  {
    GstElement *pipeline, *src, *enc, *pay, *sink;

    pipeline = gst_pipeline_new ("send-pipeline");
    src = gst_element_factory_make ("audiotestsrc", NULL);
    g_object_set (src, "num-buffers", RECORD_N_BUFS,
        "samplesperbuffer", 1000, NULL);
    enc = gst_element_factory_make ("alawenc", NULL);
    pay = gst_element_factory_make ("rtppcmapay", NULL);
    sink = gst_element_factory_make ("appsink", NULL);
    fail_unless (pipeline && src && enc && pay && sink);
    gst_bin_add_many (GST_BIN (pipeline), src, enc, pay, sink, NULL);
    gst_element_link_many (src, enc, pay, sink, NULL);
    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    do {
      GstRTSPMessage *data_msg;
      GstMapInfo map = GST_MAP_INFO_INIT;
      GstRTSPResult rres;
      GstSample *sample = NULL;
      GstBuffer *buf;

      g_signal_emit_by_name (G_OBJECT (sink), "pull-sample", &sample);
      if (sample == NULL)
        break;
      buf = gst_sample_get_buffer (sample);
      rres = gst_rtsp_message_new_data (&data_msg, 0);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_buffer_map (buf, &map, GST_MAP_READ);
      GST_INFO ("sending %u bytes of data on channel 0", (guint) map.size);
      GST_MEMDUMP ("data on channel 0", map.data, map.size);
      rres = gst_rtsp_message_set_body (data_msg, map.data, map.size);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_buffer_unmap (buf, &map);
      rres = gst_rtsp_connection_send (conn, data_msg, NULL);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_rtsp_message_free (data_msg);
      gst_sample_unref (sample);
    } while (TRUE);

    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref (pipeline);
  }

  /* check received data (we assume every buffer created by audiotestsrc and
   * subsequently encoded by mulawenc results in exactly one RTP packet) */
  for (i = 0; i < RECORD_N_BUFS; ++i) {
    GstSample *sample = NULL;

    g_signal_emit_by_name (G_OBJECT (server_sink), "pull-sample", &sample);
    GST_INFO ("%2d recv sample: %p", i, sample);
    gst_sample_unref (sample);
  }

  fail_unless_equals_int (GST_STATE (server_sink), GST_STATE_PLAYING);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
  g_free (session);
  /* release the reference to server_sink, obtained in media_constructed_cb */
  gst_object_unref (server_sink);
}

GST_END_TEST;

static void
do_test_multiple_transports (GstRTSPLowerTrans trans1, GstRTSPLowerTrans trans2)
{
  GstRTSPConnection *conn1;
  GstRTSPConnection *conn2;
  GstSDPMessage *sdp_message1 = NULL;
  GstSDPMessage *sdp_message2 = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port1, client_port2;
  gchar *session1 = NULL;
  gchar *session2 = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;

  conn1 = connect_to_server (test_port, TEST_MOUNT_POINT);
  conn2 = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message1 = do_describe (conn1, TEST_MOUNT_POINT);

  get_client_ports_full (&client_port1, &rtp_socket, &rtcp_socket);
  /* get control strings from DESCRIBE response */
  sdp_media = gst_sdp_message_get_media (sdp_message1, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message1, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  /* do SETUP for video and audio */
  fail_unless (do_setup_full (conn1, video_control, trans1,
          &client_port1, NULL, &session1, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  fail_unless (do_setup_full (conn1, audio_control, trans1,
          &client_port1, NULL, &session1, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);

  sdp_message2 = do_describe (conn2, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  sdp_media = gst_sdp_message_get_media (sdp_message2, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message2, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port2, NULL, NULL);
  /* do SETUP for video and audio */
  fail_unless (do_setup_full (conn2, video_control, trans2,
          &client_port2, NULL, &session2, &video_transport,
          NULL) == GST_RTSP_STS_OK);
  fail_unless (do_setup_full (conn2, audio_control, trans2,
          &client_port2, NULL, &session2, &audio_transport,
          NULL) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_request (conn1, GST_RTSP_PLAY, NULL, session1, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL) == GST_RTSP_STS_OK);
  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_request (conn2, GST_RTSP_PLAY, NULL, session2, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL) == GST_RTSP_STS_OK);


  /* receive UDP data */
  receive_rtp (rtp_socket, NULL);
  receive_rtcp (rtcp_socket, NULL, 0);

  /* receive TCP data */
  {
    GstRTSPMessage *message;
    fail_unless (gst_rtsp_message_new (&message) == GST_RTSP_OK);
    fail_unless (gst_rtsp_connection_receive (conn2, message,
            NULL) == GST_RTSP_OK);
    fail_unless (gst_rtsp_message_get_type (message) == GST_RTSP_MESSAGE_DATA);
    gst_rtsp_message_free (message);
  }

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn1, GST_RTSP_TEARDOWN,
          session1) == GST_RTSP_STS_OK);
  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn2, GST_RTSP_TEARDOWN,
          session2) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session1);
  g_free (session2);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message1);
  gst_sdp_message_free (sdp_message2);
  gst_rtsp_connection_free (conn1);
  gst_rtsp_connection_free (conn2);
}

GST_START_TEST (test_multiple_transports)
{
  start_server (TRUE);
  do_test_multiple_transports (GST_RTSP_LOWER_TRANS_UDP,
      GST_RTSP_LOWER_TRANS_TCP);
  stop_server ();
}

GST_END_TEST;

GST_START_TEST (test_suspend_mode_reset_only_audio)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_suspend_mode (factory,
      GST_RTSP_SUSPEND_MODE_RESET);
  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for audio */
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;


static GstRTSPStatusCode
adjust_play_mode (GstRTSPClient * client, GstRTSPContext * ctx,
    GstRTSPTimeRange ** range, GstSeekFlags * flags, gdouble * rate,
    GstClockTime * trickmode_interval, gboolean * enable_rate_control)
{
  GstRTSPState rtspstate;

  rtspstate = gst_rtsp_session_media_get_rtsp_state (ctx->sessmedia);
  if (rtspstate == GST_RTSP_STATE_PLAYING) {
    if (!gst_rtsp_session_media_set_state (ctx->sessmedia, GST_STATE_PAUSED))
      return GST_RTSP_STS_INTERNAL_SERVER_ERROR;

    if (!gst_rtsp_media_unsuspend (ctx->media))
      return GST_RTSP_STS_INTERNAL_SERVER_ERROR;
  }

  return GST_RTSP_STS_OK;
}

GST_START_TEST (test_double_play)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPTransport *video_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;
  GstRTSPClient *client;
  GstRTSPClientClass *klass;

  client = gst_rtsp_client_new ();
  klass = GST_RTSP_CLIENT_GET_CLASS (client);
  klass->adjust_play_mode = adjust_play_mode;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);


  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for video */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);

  /* do SETUP for audio */
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request_rangein (conn, GST_RTSP_PLAY,
          session, "npt=0-") == GST_RTSP_STS_OK);

  /* let it play for a while, so it needs to seek
   * for next play (npt=0-) */
  g_usleep (30000);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request_rangein (conn, GST_RTSP_PLAY,
          session, "npt=0-") == GST_RTSP_STS_OK);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
  g_object_unref (client);
}

GST_END_TEST;


static Suite *
rtspserver_suite (void)
{
  Suite *s = suite_create ("rtspserver");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_add_checked_fixture (tc, setup, teardown);
  tcase_set_timeout (tc, 120);
  tcase_add_test (tc, test_connect);
  tcase_add_test (tc, test_describe);
  tcase_add_test (tc, test_describe_non_existing_mount_point);
  tcase_add_test (tc, test_describe_record_media);
  tcase_add_test (tc, test_setup_udp);
  tcase_add_test (tc, test_setup_tcp);
  tcase_add_test (tc, test_setup_udp_mcast);
  tcase_add_test (tc, test_setup_twice);
  tcase_add_test (tc, test_setup_with_require_header);
  tcase_add_test (tc, test_setup_non_existing_stream);
  tcase_add_test (tc, test_play);
  tcase_add_test (tc, test_play_tcp);
  tcase_add_test (tc, test_play_without_session);
  tcase_add_test (tc, test_bind_already_in_use);
  tcase_add_test (tc, test_play_multithreaded);
  tcase_add_test (tc, test_play_multithreaded_block_in_describe);
  tcase_add_test (tc, test_play_multithreaded_timeout_client);
  tcase_add_test (tc, test_play_multithreaded_timeout_session);
  tcase_add_test (tc, test_play_timeout_connection);
  tcase_add_test (tc, test_no_session_timeout);
  tcase_add_test (tc, test_play_one_active_stream);
  tcase_add_test (tc, test_play_disconnect);
  tcase_add_test (tc, test_play_specific_server_port);
  tcase_add_test (tc, test_play_smpte_range);
  tcase_add_test (tc, test_play_smpte_range_tcp);
  tcase_add_test (tc, test_shared_udp);
  tcase_add_test (tc, test_shared_tcp);
  tcase_add_test (tc, test_announce_without_sdp);
  tcase_add_test (tc, test_record_tcp);
  tcase_add_test (tc, test_multiple_transports);
  tcase_add_test (tc, test_suspend_mode_reset_only_audio);
  tcase_add_test (tc, test_double_play);

  return s;
}

GST_CHECK_MAIN (rtspserver);
  0707010000007E000081A400000000000000000000000169D554BF00003223000000000000000000000000000000000000003600000000gst-rtsp-server-1.28.2/tests/check/gst/sessionmedia.c /* GStreamer
 * Copyright (C) 2013 Branko Subasic <branko.subasic@axis.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-media-factory.h>
#include <rtsp-session-media.h>

#define TEST_PATH "rtsp://localhost:8554/test"
#define SETUP_URL1 TEST_PATH "/stream=0"
#define SETUP_URL2 TEST_PATH "/stream=1"

GST_START_TEST (test_setup_url)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url, *setup_url;
  GstRTSPStream *stream;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPSessionMedia *sm;
  GstRTSPStreamTransport *trans;
  GstRTSPTransport *ct;
  gint match_len;
  gchar *url_str, *url_str2;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse (TEST_PATH, &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (GST_IS_RTSP_STREAM (stream));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));

  /* create session media and make sure it matches test path
   * note that gst_rtsp_session_media_new takes ownership of the media
   * thus no need to unref it at the bottom of function */
  sm = gst_rtsp_session_media_new (TEST_PATH, media);
  fail_unless (GST_IS_RTSP_SESSION_MEDIA (sm));
  fail_unless (gst_rtsp_session_media_matches (sm, TEST_PATH, &match_len));
  fail_unless (match_len == strlen (TEST_PATH));
  fail_unless (gst_rtsp_session_media_get_media (sm) == media);

  /* make a transport for the stream */
  gst_rtsp_transport_new (&ct);
  trans = gst_rtsp_session_media_set_transport (sm, stream, ct);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 0) == trans);

  /* make sure there's no setup url stored initially */
  fail_unless (gst_rtsp_stream_transport_get_url (trans) == NULL);

  /* now store a setup url and make sure it can be retrieved and that it's correct */
  fail_unless (gst_rtsp_url_parse (SETUP_URL1, &setup_url) == GST_RTSP_OK);
  gst_rtsp_stream_transport_set_url (trans, setup_url);

  url_str = gst_rtsp_url_get_request_uri (setup_url);
  url_str2 =
      gst_rtsp_url_get_request_uri (gst_rtsp_stream_transport_get_url (trans));
  fail_if (g_strcmp0 (url_str, url_str2) != 0);
  g_free (url_str);
  g_free (url_str2);

  /* check that it's ok to try to store the same url again */
  gst_rtsp_stream_transport_set_url (trans, setup_url);

  fail_unless (gst_rtsp_media_unprepare (media));

  gst_rtsp_url_free (setup_url);
  gst_rtsp_url_free (url);

  gst_rtsp_media_unlock (media);
  g_object_unref (sm);

  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_rtsp_state)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPSessionMedia *sm;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse (TEST_PATH, &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (GST_IS_RTSP_STREAM (stream));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));

  sm = gst_rtsp_session_media_new (TEST_PATH, media);
  fail_unless (GST_IS_RTSP_SESSION_MEDIA (sm));
  fail_unless_equals_int (gst_rtsp_session_media_get_rtsp_state (sm),
      GST_RTSP_STATE_INIT);

  gst_rtsp_session_media_set_rtsp_state (sm, GST_RTSP_STATE_READY);
  fail_unless_equals_int (gst_rtsp_session_media_get_rtsp_state (sm),
      GST_RTSP_STATE_READY);

  gst_rtsp_session_media_set_rtsp_state (sm, GST_RTSP_STATE_SEEKING);
  fail_unless_equals_int (gst_rtsp_session_media_get_rtsp_state (sm),
      GST_RTSP_STATE_SEEKING);

  gst_rtsp_session_media_set_rtsp_state (sm, GST_RTSP_STATE_PLAYING);
  fail_unless_equals_int (gst_rtsp_session_media_get_rtsp_state (sm),
      GST_RTSP_STATE_PLAYING);

  gst_rtsp_session_media_set_rtsp_state (sm, GST_RTSP_STATE_RECORDING);
  fail_unless_equals_int (gst_rtsp_session_media_get_rtsp_state (sm),
      GST_RTSP_STATE_RECORDING);

  fail_unless (gst_rtsp_media_unprepare (media));

  gst_rtsp_url_free (url);

  gst_rtsp_media_unlock (media);
  g_object_unref (sm);

  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_transports)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream1, *stream2;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPSessionMedia *sm;
  GstRTSPStreamTransport *trans;
  GstRTSPTransport *ct1, *ct2, *ct3, *ct4;
  gint match_len;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse (TEST_PATH, &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 audiotestsrc ! rtpgstpay pt=97 name=pay1 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 2);

  stream1 = gst_rtsp_media_get_stream (media, 0);
  fail_unless (GST_IS_RTSP_STREAM (stream1));

  stream2 = gst_rtsp_media_get_stream (media, 1);
  fail_unless (GST_IS_RTSP_STREAM (stream2));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));

  sm = gst_rtsp_session_media_new (TEST_PATH, media);
  fail_unless (GST_IS_RTSP_SESSION_MEDIA (sm));
  fail_unless (gst_rtsp_session_media_matches (sm, TEST_PATH, &match_len));
  fail_unless (match_len == strlen (TEST_PATH));

  gst_rtsp_transport_new (&ct1);
  trans = gst_rtsp_session_media_set_transport (sm, stream1, ct1);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 0) == trans);

  gst_rtsp_transport_new (&ct2);
  trans = gst_rtsp_session_media_set_transport (sm, stream1, ct2);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 0) == trans);

  gst_rtsp_transport_new (&ct3);
  trans = gst_rtsp_session_media_set_transport (sm, stream2, ct3);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 1) == trans);

  gst_rtsp_transport_new (&ct4);
  trans = gst_rtsp_session_media_set_transport (sm, stream2, ct4);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 1) == trans);

  fail_unless (gst_rtsp_media_unprepare (media));

  gst_rtsp_url_free (url);

  gst_rtsp_media_unlock (media);
  g_object_unref (sm);

  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_time_and_rtpinfo)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream1, *stream2;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPSessionMedia *sm;
  GstClockTime base_time;
  gchar *rtpinfo;
  GstRTSPTransport *ct1;
  GstRTSPStreamTransport *trans;
  GstRTSPUrl *setup_url;
  gchar **streaminfo;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse (TEST_PATH, &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc do-timestamp=true timestamp-offset=0 ! rtpvrawpay pt=96 name=pay0 "
      "audiotestsrc do-timestamp=true timestamp-offset=1000000000 ! rtpgstpay pt=97 name=pay1 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 2);

  stream1 = gst_rtsp_media_get_stream (media, 0);
  fail_unless (GST_IS_RTSP_STREAM (stream1));

  stream2 = gst_rtsp_media_get_stream (media, 1);
  fail_unless (GST_IS_RTSP_STREAM (stream2));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));

  sm = gst_rtsp_session_media_new (TEST_PATH, media);
  fail_unless (GST_IS_RTSP_SESSION_MEDIA (sm));

  base_time = gst_rtsp_session_media_get_base_time (sm);
  fail_unless_equals_int64 (base_time, 0);

  rtpinfo = gst_rtsp_session_media_get_rtpinfo (sm);
  fail_unless (rtpinfo == NULL);

  gst_rtsp_transport_new (&ct1);
  trans = gst_rtsp_session_media_set_transport (sm, stream1, ct1);
  fail_unless (gst_rtsp_session_media_get_transport (sm, 0) == trans);
  fail_unless (gst_rtsp_url_parse (SETUP_URL1, &setup_url) == GST_RTSP_OK);
  gst_rtsp_stream_transport_set_url (trans, setup_url);

  base_time = gst_rtsp_session_media_get_base_time (sm);
  fail_unless_equals_int64 (base_time, 0);

  rtpinfo = gst_rtsp_session_media_get_rtpinfo (sm);
  streaminfo = g_strsplit (rtpinfo, ",", 1);
  g_free (rtpinfo);

  fail_unless (g_strstr_len (streaminfo[0], -1, "url=") != NULL);
  fail_unless (g_strstr_len (streaminfo[0], -1, "seq=") != NULL);
  fail_unless (g_strstr_len (streaminfo[0], -1, "rtptime=") != NULL);
  fail_unless (g_strstr_len (streaminfo[0], -1, SETUP_URL1) != NULL);

  g_strfreev (streaminfo);

  fail_unless (gst_rtsp_media_unprepare (media));

  rtpinfo = gst_rtsp_session_media_get_rtpinfo (sm);
  fail_unless (rtpinfo == NULL);

  gst_rtsp_url_free (setup_url);
  gst_rtsp_url_free (url);

  gst_rtsp_media_unlock (media);
  g_object_unref (sm);

  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_allocate_channels)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMedia *media;
  GstRTSPUrl *url;
  GstRTSPStream *stream;
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;
  GstRTSPSessionMedia *sm;
  GstRTSPRange range;

  factory = gst_rtsp_media_factory_new ();
  fail_if (gst_rtsp_media_factory_is_shared (factory));
  fail_unless (gst_rtsp_url_parse (TEST_PATH, &url) == GST_RTSP_OK);

  gst_rtsp_media_factory_set_launch (factory,
      "( videotestsrc ! rtpvrawpay pt=96 name=pay0 )");

  media = gst_rtsp_media_factory_construct (factory, url);
  fail_unless (GST_IS_RTSP_MEDIA (media));

  fail_unless (gst_rtsp_media_n_streams (media) == 1);

  stream = gst_rtsp_media_get_stream (media, 0);
  fail_unless (GST_IS_RTSP_STREAM (stream));

  pool = gst_rtsp_thread_pool_new ();
  thread = gst_rtsp_thread_pool_get_thread (pool,
      GST_RTSP_THREAD_TYPE_MEDIA, NULL);

  fail_unless (gst_rtsp_media_prepare (media, thread));

  sm = gst_rtsp_session_media_new (TEST_PATH, media);
  fail_unless (GST_IS_RTSP_SESSION_MEDIA (sm));

  fail_unless (gst_rtsp_session_media_alloc_channels (sm, &range));
  fail_unless_equals_int (range.min, 0);
  fail_unless_equals_int (range.max, 1);

  fail_unless (gst_rtsp_session_media_alloc_channels (sm, &range));
  fail_unless_equals_int (range.min, 2);
  fail_unless_equals_int (range.max, 3);

  fail_unless (gst_rtsp_media_unprepare (media));

  gst_rtsp_url_free (url);

  gst_rtsp_media_unlock (media);
  g_object_unref (sm);

  g_object_unref (factory);
  g_object_unref (pool);
}

GST_END_TEST;
static Suite *
rtspsessionmedia_suite (void)
{
  Suite *s = suite_create ("rtspsessionmedia");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_setup_url);
  tcase_add_test (tc, test_rtsp_state);
  tcase_add_test (tc, test_transports);
  tcase_add_test (tc, test_time_and_rtpinfo);
  tcase_add_test (tc, test_allocate_channels);

  return s;
}

GST_CHECK_MAIN (rtspsessionmedia);
 0707010000007F000081A400000000000000000000000169D554BF00001989000000000000000000000000000000000000003500000000gst-rtsp-server-1.28.2/tests/check/gst/sessionpool.c  /* GStreamer
 * Copyright (C) 2014 Sebastian Rasmussen <sebras@hotmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>
#include <rtsp-session-pool.h>

typedef struct
{
  GstRTSPSession *sessions[3];
  GstRTSPFilterResult response[3];
} Responses;

static GstRTSPFilterResult
filter_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
    gpointer user_data)
{
  Responses *responses = (Responses *) user_data;
  gint i;

  for (i = 0; i < 3; i++)
    if (session == responses->sessions[i])
      return responses->response[i];

  return GST_RTSP_FILTER_KEEP;
}

GST_START_TEST (test_pool)
{
  GstRTSPSessionPool *pool;
  GstRTSPSession *session1, *session2, *session3;
  GstRTSPSession *compare;
  gchar *session1id, *session2id, *session3id;
  GList *list;
  guint maxsessions;
  GSource *source;
  guint sourceid;

  pool = gst_rtsp_session_pool_new ();
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 0);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 0);

  gst_rtsp_session_pool_set_max_sessions (pool, 3);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 3);

  session1 = gst_rtsp_session_pool_create (pool);
  fail_unless (GST_IS_RTSP_SESSION (session1));
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 1);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 3);
  session1id = g_strdup (gst_rtsp_session_get_sessionid (session1));

  session2 = gst_rtsp_session_pool_create (pool);
  fail_unless (GST_IS_RTSP_SESSION (session2));
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 2);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 3);
  session2id = g_strdup (gst_rtsp_session_get_sessionid (session2));

  session3 = gst_rtsp_session_pool_create (pool);
  fail_unless (GST_IS_RTSP_SESSION (session3));
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 3);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 3);
  session3id = g_strdup (gst_rtsp_session_get_sessionid (session3));

  fail_if (GST_IS_RTSP_SESSION (gst_rtsp_session_pool_create (pool)));

  compare = gst_rtsp_session_pool_find (pool, session1id);
  fail_unless (compare == session1);
  g_object_unref (compare);
  compare = gst_rtsp_session_pool_find (pool, session2id);
  fail_unless (compare == session2);
  g_object_unref (compare);
  compare = gst_rtsp_session_pool_find (pool, session3id);
  fail_unless (compare == session3);
  g_object_unref (compare);
  fail_unless (gst_rtsp_session_pool_find (pool, "") == NULL);

  fail_unless (gst_rtsp_session_pool_remove (pool, session2));
  g_object_unref (session2);
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 2);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 3);

  gst_rtsp_session_pool_set_max_sessions (pool, 2);
  fail_unless_equals_int (gst_rtsp_session_pool_get_n_sessions (pool), 2);
  fail_unless_equals_int (gst_rtsp_session_pool_get_max_sessions (pool), 2);

  session2 = gst_rtsp_session_pool_create (pool);
  fail_if (GST_IS_RTSP_SESSION (session2));

  {
    list = gst_rtsp_session_pool_filter (pool, NULL, NULL);
    fail_unless_equals_int (g_list_length (list), 2);
    fail_unless (g_list_find (list, session1) != NULL);
    fail_unless (g_list_find (list, session3) != NULL);
    g_list_free_full (list, (GDestroyNotify) g_object_unref);
  }

  {
    Responses responses = {
      {session1, session2, session3}
      ,
      {GST_RTSP_FILTER_KEEP, GST_RTSP_FILTER_KEEP, GST_RTSP_FILTER_KEEP}
      ,
    };

    list = gst_rtsp_session_pool_filter (pool, filter_func, &responses);
    fail_unless (list == NULL);
  }

  {
    Responses responses = {
      {session1, session2, session3}
      ,
      {GST_RTSP_FILTER_REF, GST_RTSP_FILTER_KEEP, GST_RTSP_FILTER_KEEP}
      ,
    };

    list = gst_rtsp_session_pool_filter (pool, filter_func, &responses);
    fail_unless_equals_int (g_list_length (list), 1);
    fail_unless (g_list_nth_data (list, 0) == session1);
    g_list_free_full (list, (GDestroyNotify) g_object_unref);
  }

  {
    Responses responses = {
      {session1, session2, session3}
      ,
      {GST_RTSP_FILTER_KEEP, GST_RTSP_FILTER_KEEP, GST_RTSP_FILTER_REMOVE}
      ,
    };

    list = gst_rtsp_session_pool_filter (pool, filter_func, &responses);
    fail_unless_equals_int (g_list_length (list), 0);
    g_list_free (list);
  }

  compare = gst_rtsp_session_pool_find (pool, session1id);
  fail_unless (compare == session1);
  g_object_unref (compare);
  fail_unless (gst_rtsp_session_pool_find (pool, session2id) == NULL);
  fail_unless (gst_rtsp_session_pool_find (pool, session3id) == NULL);

  g_object_get (pool, "max-sessions", &maxsessions, NULL);
  fail_unless_equals_int (maxsessions, 2);

  g_object_set (pool, "max-sessions", 3, NULL);
  g_object_get (pool, "max-sessions", &maxsessions, NULL);
  fail_unless_equals_int (maxsessions, 3);

  fail_unless_equals_int (gst_rtsp_session_pool_cleanup (pool), 0);

  gst_rtsp_session_set_timeout (session1, 1);

  source = gst_rtsp_session_pool_create_watch (pool);
  fail_unless (source != NULL);

  sourceid = g_source_attach (source, NULL);
  fail_unless (sourceid != 0);

  while (!g_main_context_iteration (NULL, TRUE));

  g_source_unref (source);

  g_object_unref (session1);
  g_object_unref (session3);

  g_free (session1id);
  g_free (session2id);
  g_free (session3id);

  g_object_unref (pool);
}

GST_END_TEST;

static Suite *
rtspsessionpool_suite (void)
{
  Suite *s = suite_create ("rtspsessionpool");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 15);
  tcase_add_test (tc, test_pool);

  return s;
}

GST_CHECK_MAIN (rtspsessionpool);
   07070100000080000081A400000000000000000000000169D554BF00005A04000000000000000000000000000000000000003000000000gst-rtsp-server-1.28.2/tests/check/gst/stream.c   /* GStreamer
 * Copyright (C) 2013 Axis Communications AB <dev-gstreamer at axis dot com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-stream.h>
#include <rtsp-address-pool.h>

static void
get_sockets (GstRTSPLowerTrans lower_transport, GSocketFamily socket_family)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPAddressPool *pool;
  GSocket *socket;
  gboolean have_ipv4;
  gboolean have_ipv6;
  GstRTSPTransport *transport;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  /* configure address pool for IPv4 and IPv6 unicast addresses */
  pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          GST_RTSP_ADDRESS_POOL_ANY_IPV4, GST_RTSP_ADDRESS_POOL_ANY_IPV4, 50000,
          60000, 0));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          GST_RTSP_ADDRESS_POOL_ANY_IPV6, GST_RTSP_ADDRESS_POOL_ANY_IPV6, 50000,
          60000, 0));
  fail_unless (gst_rtsp_address_pool_add_range (pool, "233.252.0.0",
          "233.252.0.0", 50000, 60000, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool, "FF11:DB8::1",
          "FF11:DB8::1", 50000, 60000, 1));
  gst_rtsp_stream_set_address_pool (stream, pool);

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  /* allocate udp ports first */
  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = lower_transport;

  /* no ports allocated, complete stream should fail */
  fail_if (gst_rtsp_stream_complete_stream (stream, transport));

  /* allocate ports */
  fail_unless (gst_rtsp_stream_allocate_udp_sockets (stream,
          socket_family, transport, FALSE));

  fail_unless (gst_rtsp_stream_complete_stream (stream, transport));
  fail_unless (gst_rtsp_transport_free (transport) == GST_RTSP_OK);

  if (lower_transport == GST_RTSP_LOWER_TRANS_UDP)
    socket = gst_rtsp_stream_get_rtp_socket (stream, G_SOCKET_FAMILY_IPV4);
  else
    socket = gst_rtsp_stream_get_rtp_multicast_socket (stream,
        G_SOCKET_FAMILY_IPV4);
  have_ipv4 = (socket != NULL);
  if (have_ipv4) {
    fail_unless (g_socket_get_fd (socket) >= 0);
    g_object_unref (socket);
  }

  if (lower_transport == GST_RTSP_LOWER_TRANS_UDP)
    socket = gst_rtsp_stream_get_rtcp_socket (stream, G_SOCKET_FAMILY_IPV4);
  else
    socket = gst_rtsp_stream_get_rtcp_multicast_socket (stream,
        G_SOCKET_FAMILY_IPV4);
  if (have_ipv4) {
    fail_unless (socket != NULL);
    fail_unless (g_socket_get_fd (socket) >= 0);
    g_object_unref (socket);
  } else {
    fail_unless (socket == NULL);
  }

  if (lower_transport == GST_RTSP_LOWER_TRANS_UDP)
    socket = gst_rtsp_stream_get_rtp_socket (stream, G_SOCKET_FAMILY_IPV6);
  else
    socket = gst_rtsp_stream_get_rtp_multicast_socket (stream,
        G_SOCKET_FAMILY_IPV6);
  have_ipv6 = (socket != NULL);
  if (have_ipv6) {
    fail_unless (g_socket_get_fd (socket) >= 0);
    g_object_unref (socket);
  }

  if (lower_transport == GST_RTSP_LOWER_TRANS_UDP)
    socket = gst_rtsp_stream_get_rtcp_socket (stream, G_SOCKET_FAMILY_IPV6);
  else
    socket = gst_rtsp_stream_get_rtcp_multicast_socket (stream,
        G_SOCKET_FAMILY_IPV6);
  if (have_ipv6) {
    fail_unless (socket != NULL);
    fail_unless (g_socket_get_fd (socket) >= 0);
    g_object_unref (socket);
  } else {
    fail_unless (socket == NULL);
  }

  /* check that at least one family is available */
  fail_unless (have_ipv4 || have_ipv6);

  g_object_unref (pool);

  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));

  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_START_TEST (test_get_sockets_udp_ipv4)
{
  get_sockets (GST_RTSP_LOWER_TRANS_UDP, G_SOCKET_FAMILY_IPV4);
}

GST_END_TEST;

GST_START_TEST (test_get_sockets_udp_ipv6)
{
  get_sockets (GST_RTSP_LOWER_TRANS_UDP, G_SOCKET_FAMILY_IPV6);
}

GST_END_TEST;

GST_START_TEST (test_get_sockets_mcast_ipv4)
{
  get_sockets (GST_RTSP_LOWER_TRANS_UDP_MCAST, G_SOCKET_FAMILY_IPV4);
}

GST_END_TEST;

GST_START_TEST (test_get_sockets_mcast_ipv6)
{
  get_sockets (GST_RTSP_LOWER_TRANS_UDP_MCAST, G_SOCKET_FAMILY_IPV6);
}

GST_END_TEST;

/* The purpose of this test is to make sure that it's not possible to allocate
 * multicast UDP ports if the address pool does not contain multicast UDP
 * addresses. */
GST_START_TEST (test_allocate_udp_ports_fail)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPAddressPool *pool;
  GstRTSPTransport *transport;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (pool, "192.168.1.1",
          "192.168.1.1", 6000, 6001, 0));
  gst_rtsp_stream_set_address_pool (stream, pool);

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_UDP_MCAST;
  fail_if (gst_rtsp_stream_allocate_udp_sockets (stream, G_SOCKET_FAMILY_IPV4,
          transport, FALSE));
  fail_unless (gst_rtsp_transport_free (transport) == GST_RTSP_OK);

  g_object_unref (pool);
  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_END_TEST;

GST_START_TEST (test_get_multicast_address)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstRTSPAddressPool *pool;
  GstRTSPAddress *addr1;
  GstRTSPAddress *addr2;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);

  pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.0", "233.252.0.0", 5100, 5101, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "FF11:DB8::1", "FF11:DB8::1", 5102, 5103, 1));
  gst_rtsp_stream_set_address_pool (stream, pool);

  addr1 = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr1 != NULL);
  fail_unless_equals_string (addr1->address, "233.252.0.0");
  fail_unless_equals_int (addr1->port, 5100);
  fail_unless_equals_int (addr1->n_ports, 2);

  addr2 = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr2 != NULL);
  fail_unless_equals_string (addr2->address, "233.252.0.0");
  fail_unless_equals_int (addr2->port, 5100);
  fail_unless_equals_int (addr2->n_ports, 2);

  gst_rtsp_address_free (addr1);
  gst_rtsp_address_free (addr2);

  addr1 = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV6);
  fail_unless (addr1 != NULL);
  fail_unless (!g_ascii_strcasecmp (addr1->address, "FF11:DB8::1"));
  fail_unless_equals_int (addr1->port, 5102);
  fail_unless_equals_int (addr1->n_ports, 2);

  addr2 = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV6);
  fail_unless (addr2 != NULL);
  fail_unless (!g_ascii_strcasecmp (addr2->address, "FF11:DB8::1"));
  fail_unless_equals_int (addr2->port, 5102);
  fail_unless_equals_int (addr2->n_ports, 2);

  gst_rtsp_address_free (addr1);
  gst_rtsp_address_free (addr2);

  g_object_unref (pool);

  gst_object_unref (stream);
}

GST_END_TEST;

/*  test case: address pool only contains multicast addresses,
 *  but the client is requesting unicast udp */
GST_START_TEST (test_multicast_address_and_unicast_udp)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPAddressPool *pool;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  pool = gst_rtsp_address_pool_new ();
  /* add a multicast addres to the address pool */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.0", "233.252.0.0", 5200, 5201, 1));
  gst_rtsp_stream_set_address_pool (stream, pool);

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  g_object_unref (pool);
  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_END_TEST;

GST_START_TEST (test_allocate_udp_ports_multicast)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPAddressPool *pool;
  GstRTSPAddress *addr;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  pool = gst_rtsp_address_pool_new ();
  /* add multicast addresses to the address pool */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "233.252.0.1", 6000, 6001, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "FF11:DB8::1", "FF11:DB8::1", 6002, 6003, 1));
  gst_rtsp_stream_set_address_pool (stream, pool);

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  /* check the multicast address and ports for IPv4 */
  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr != NULL);
  fail_unless_equals_string (addr->address, "233.252.0.1");
  fail_unless_equals_int (addr->port, 6000);
  fail_unless_equals_int (addr->n_ports, 2);
  gst_rtsp_address_free (addr);

  /* check the multicast address and ports for IPv6 */
  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV6);
  fail_unless (addr != NULL);
  fail_unless (!g_ascii_strcasecmp (addr->address, "FF11:DB8::1"));
  fail_unless_equals_int (addr->port, 6002);
  fail_unless_equals_int (addr->n_ports, 2);
  gst_rtsp_address_free (addr);

  g_object_unref (pool);
  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_END_TEST;

GST_START_TEST (test_allocate_udp_ports_client_settings)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPAddressPool *pool;
  GstRTSPAddress *addr;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  pool = gst_rtsp_address_pool_new ();
  /* add multicast addresses to the address pool */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.1", "233.252.0.1", 6000, 6001, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "FF11:DB7::1", "FF11:DB7::1", 6004, 6005, 1));
  /* multicast address specified by the client */
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "233.252.0.2", "233.252.0.2", 6002, 6003, 1));
  fail_unless (gst_rtsp_address_pool_add_range (pool,
          "FF11:DB8::1", "FF11:DB8::1", 6006, 6007, 1));
  gst_rtsp_stream_set_address_pool (stream, pool);

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  /* Reserve IPV4 mcast address */
  addr = gst_rtsp_stream_reserve_address (stream, "233.252.0.2", 6002, 2, 1);
  fail_unless (addr != NULL);
  gst_rtsp_address_free (addr);

  /* verify that the multicast address and ports correspond to the requested client
   * transport information for IPv4 */
  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV4);
  fail_unless (addr != NULL);
  fail_unless_equals_string (addr->address, "233.252.0.2");
  fail_unless_equals_int (addr->port, 6002);
  fail_unless_equals_int (addr->n_ports, 2);
  gst_rtsp_address_free (addr);

  /* Reserve IPV6 mcast address */
  addr = gst_rtsp_stream_reserve_address (stream, "FF11:DB8::1", 6006, 2, 1);
  fail_unless (addr != NULL);
  gst_rtsp_address_free (addr);

  /* verify that the multicast address and ports correspond to the requested client
   * transport information for IPv6 */
  addr = gst_rtsp_stream_get_multicast_address (stream, G_SOCKET_FAMILY_IPV6);
  fail_unless (addr != NULL);
  fail_unless (!g_ascii_strcasecmp (addr->address, "FF11:DB8::1"));
  fail_unless_equals_int (addr->port, 6006);
  fail_unless_equals_int (addr->n_ports, 2);
  gst_rtsp_address_free (addr);

  g_object_unref (pool);
  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_END_TEST;

GST_START_TEST (test_tcp_transport)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPRange server_port;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  /* TCP transport */
  gst_rtsp_stream_set_protocols (stream, GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  /* port that the server will use to receive RTCP makes only sense in the UDP
   * case so verify that the received server port is 0 in the TCP case */
  gst_rtsp_stream_get_server_port (stream, &server_port, G_SOCKET_FAMILY_IPV4);
  fail_unless_equals_int (server_port.min, 0);
  fail_unless_equals_int (server_port.max, 0);

  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  gst_object_unref (bin);
  gst_object_unref (stream);
}

GST_END_TEST;

static void
check_multicast_client_address (const gchar * destination, guint port,
    const gchar * expected_addr_str, gboolean expected_res)
{
  GstPad *srcpad;
  GstElement *pay;
  GstRTSPStream *stream;
  GstBin *bin;
  GstElement *rtpbin;
  GstRTSPTransport *transport;
  GstRTSPRange ports = { 0 };
  gchar *addr_str = NULL;

  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_UDP_MCAST;
  transport->destination = g_strdup (destination);
  transport->ttl = 1;
  ports.min = port;
  ports.max = port + 1;
  transport->port = ports;

  /* allocate ports */
  fail_unless (gst_rtsp_stream_allocate_udp_sockets (stream,
          G_SOCKET_FAMILY_IPV4, transport, TRUE) == expected_res);

  fail_unless (gst_rtsp_stream_add_multicast_client_address (stream,
          destination, ports.min, ports.max,
          G_SOCKET_FAMILY_IPV4) == expected_res);

  fail_unless (gst_rtsp_stream_complete_stream (stream,
          transport) == expected_res);

  fail_unless (gst_rtsp_transport_free (transport) == GST_RTSP_OK);
  addr_str = gst_rtsp_stream_get_multicast_client_addresses (stream);

  fail_unless (g_str_equal (addr_str, expected_addr_str));
  g_free (addr_str);

  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));

  gst_object_unref (bin);
  gst_object_unref (stream);
}

/* test if the provided transport destination is correct.
 * CASE: valid multicast address */
GST_START_TEST (test_multicast_client_address)
{
  const gchar *addr = "233.252.0.1";
  guint port = 50000;
  const gchar *expected_addr_str = "233.252.0.1:50000";
  gboolean expected_res = TRUE;

  check_multicast_client_address (addr, port, expected_addr_str, expected_res);
}

GST_END_TEST;

/* test if the provided transport destination is correct.
 * CASE: invalid multicast address */
GST_START_TEST (test_multicast_client_address_invalid)
{
  const gchar *addr = "1.2.3.4";
  guint port = 50000;
  const gchar *expected_addr_str = "";
  gboolean expected_res = FALSE;

  check_multicast_client_address (addr, port, expected_addr_str, expected_res);
}

GST_END_TEST;

static void
add_transports (gboolean add_twice)
{
  GstRTSPTransport *transport;
  GstRTSPStream *stream;
  GstRTSPStreamTransport *tr;
  GstPad *srcpad;
  GstElement *pay;
  GstBin *bin;
  GstElement *rtpbin;

  fail_unless (gst_rtsp_transport_new (&transport) == GST_RTSP_OK);
  transport->lower_transport = GST_RTSP_LOWER_TRANS_TCP;
  transport->destination = g_strdup ("127.0.0.1");
  srcpad = gst_pad_new ("testsrcpad", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  pay = gst_element_factory_make ("rtpgstpay", "testpayloader");
  fail_unless (pay != NULL);
  stream = gst_rtsp_stream_new (0, pay, srcpad);
  fail_unless (stream != NULL);
  gst_object_unref (pay);
  gst_object_unref (srcpad);
  rtpbin = gst_element_factory_make ("rtpbin", "testrtpbin");
  fail_unless (rtpbin != NULL);
  bin = GST_BIN (gst_bin_new ("testbin"));
  fail_unless (bin != NULL);
  fail_unless (gst_bin_add (bin, rtpbin));

  /* TCP transport */
  gst_rtsp_stream_set_protocols (stream, GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (gst_rtsp_stream_join_bin (stream, bin, rtpbin, GST_STATE_NULL));

  tr = gst_rtsp_stream_transport_new (stream, transport);
  fail_unless (tr);

  if (add_twice) {
    fail_unless (gst_rtsp_stream_add_transport (stream, tr));
    fail_unless (gst_rtsp_stream_add_transport (stream, tr));
    fail_unless (gst_rtsp_stream_remove_transport (stream, tr));
  } else {
    fail_unless (gst_rtsp_stream_add_transport (stream, tr));
    fail_unless (gst_rtsp_stream_remove_transport (stream, tr));
    fail_if (gst_rtsp_stream_remove_transport (stream, tr));
  }

  fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin));
  g_object_unref (tr);
  gst_object_unref (bin);
  gst_object_unref (stream);
}


GST_START_TEST (test_add_transport_twice)
{
  add_transports (TRUE);
}

GST_END_TEST;

GST_START_TEST (test_remove_transport_twice)
{
  add_transports (FALSE);
}

GST_END_TEST;

static gboolean
is_ipv6_supported (void)
{
  GError *err = NULL;
  GSocket *sock;

  sock =
      g_socket_new (G_SOCKET_FAMILY_IPV6, G_SOCKET_TYPE_DATAGRAM,
      G_SOCKET_PROTOCOL_DEFAULT, &err);
  if (sock) {
    g_object_unref (sock);
    return TRUE;
  }

  if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
    GST_WARNING ("Unabled to create IPv6 socket: %s", err->message);
  }
  g_clear_error (&err);

  return FALSE;
}

static Suite *
rtspstream_suite (void)
{
  Suite *s = suite_create ("rtspstream");
  TCase *tc = tcase_create ("general");
  gboolean have_ipv6 = is_ipv6_supported ();

  suite_add_tcase (s, tc);
  tcase_add_test (tc, test_get_sockets_udp_ipv4);
  tcase_add_test (tc, test_get_sockets_mcast_ipv4);
  if (have_ipv6) {
    tcase_add_test (tc, test_get_sockets_udp_ipv6);
    tcase_add_test (tc, test_get_sockets_mcast_ipv6);
  }
  tcase_add_test (tc, test_allocate_udp_ports_fail);
  tcase_add_test (tc, test_get_multicast_address);
  tcase_add_test (tc, test_multicast_address_and_unicast_udp);
  tcase_add_test (tc, test_allocate_udp_ports_multicast);
  tcase_add_test (tc, test_allocate_udp_ports_client_settings);
  tcase_add_test (tc, test_tcp_transport);
  tcase_add_test (tc, test_multicast_client_address);
  tcase_add_test (tc, test_multicast_client_address_invalid);
  tcase_add_test (tc, test_add_transport_twice);
  tcase_add_test (tc, test_remove_transport_twice);

  return s;
}

GST_CHECK_MAIN (rtspstream);
07070100000081000081A400000000000000000000000169D554BF00001A04000000000000000000000000000000000000003400000000gst-rtsp-server-1.28.2/tests/check/gst/threadpool.c   /* GStreamer
 * unit tests for GstRTSPThreadPool
 * Copyright (C) 2013 Axis Communications <dev-gstreamer at axis dot com>
 * @author Ognyan Tonchev <ognyan at axis dot com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-thread-pool.h>

GST_START_TEST (test_pool_get_thread)
{
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  pool = gst_rtsp_thread_pool_new ();
  fail_unless (GST_IS_RTSP_THREAD_POOL (pool));

  thread = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread));
  /* one ref is hold by the pool */
  fail_unless_equals_int (GST_MINI_OBJECT_REFCOUNT (thread), 2);

  gst_rtsp_thread_stop (thread);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_pool_get_media_thread)
{
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread;

  pool = gst_rtsp_thread_pool_new ();
  fail_unless (GST_IS_RTSP_THREAD_POOL (pool));

  thread = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_MEDIA,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread));
  /* one ref is hold by the pool */
  fail_unless_equals_int (GST_MINI_OBJECT_REFCOUNT (thread), 2);

  gst_rtsp_thread_stop (thread);
  g_object_unref (pool);
}

GST_END_TEST;

GST_START_TEST (test_pool_get_thread_reuse)
{
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread1;
  GstRTSPThread *thread2;

  pool = gst_rtsp_thread_pool_new ();
  fail_unless (GST_IS_RTSP_THREAD_POOL (pool));

  gst_rtsp_thread_pool_set_max_threads (pool, 1);

  thread1 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread1));

  thread2 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread2));

  fail_unless (thread2 == thread1);
  /* one ref is hold by the pool */
  fail_unless_equals_int (GST_MINI_OBJECT_REFCOUNT (thread1), 3);

  gst_rtsp_thread_stop (thread1);
  gst_rtsp_thread_stop (thread2);
  g_object_unref (pool);
}

GST_END_TEST;

static void
do_test_pool_max_thread (gboolean use_property)
{
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread1;
  GstRTSPThread *thread2;
  GstRTSPThread *thread3;
  gint max_threads;

  pool = gst_rtsp_thread_pool_new ();
  fail_unless (GST_IS_RTSP_THREAD_POOL (pool));

  if (use_property) {
    g_object_get (pool, "max-threads", &max_threads, NULL);
    fail_unless_equals_int (max_threads, 1);
  } else {
    fail_unless_equals_int (gst_rtsp_thread_pool_get_max_threads (pool), 1);
  }

  thread1 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread1));

  thread2 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread2));

  fail_unless (thread1 == thread2);

  gst_rtsp_thread_stop (thread1);
  gst_rtsp_thread_stop (thread2);

  if (use_property) {
    g_object_set (pool, "max-threads", 2, NULL);
    g_object_get (pool, "max-threads", &max_threads, NULL);
    fail_unless_equals_int (max_threads, 2);
  } else {
    gst_rtsp_thread_pool_set_max_threads (pool, 2);
    fail_unless_equals_int (gst_rtsp_thread_pool_get_max_threads (pool), 2);
  }

  thread1 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread1));

  thread2 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread2));

  thread3 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread3));

  fail_unless (thread2 != thread1);
  fail_unless (thread3 == thread2 || thread3 == thread1);

  gst_rtsp_thread_stop (thread1);
  gst_rtsp_thread_stop (thread2);
  gst_rtsp_thread_stop (thread3);

  if (use_property) {
    g_object_set (pool, "max-threads", 0, NULL);
    g_object_get (pool, "max-threads", &max_threads, NULL);
    fail_unless_equals_int (max_threads, 0);
  } else {
    gst_rtsp_thread_pool_set_max_threads (pool, 0);
    fail_unless_equals_int (gst_rtsp_thread_pool_get_max_threads (pool), 0);
  }

  thread1 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_if (GST_IS_RTSP_THREAD (thread1));

  g_object_unref (pool);
}

GST_START_TEST (test_pool_max_threads)
{
  do_test_pool_max_thread (FALSE);
}

GST_END_TEST;

GST_START_TEST (test_pool_max_threads_property)
{
  do_test_pool_max_thread (TRUE);
}

GST_END_TEST;

GST_START_TEST (test_pool_thread_copy)
{
  GstRTSPThreadPool *pool;
  GstRTSPThread *thread1;
  GstRTSPThread *thread2;

  pool = gst_rtsp_thread_pool_new ();
  fail_unless (GST_IS_RTSP_THREAD_POOL (pool));

  thread1 = gst_rtsp_thread_pool_get_thread (pool, GST_RTSP_THREAD_TYPE_CLIENT,
      NULL);
  fail_unless (GST_IS_RTSP_THREAD (thread1));
  fail_unless (GST_IS_MINI_OBJECT_TYPE (thread1, GST_TYPE_RTSP_THREAD));

  thread2 = GST_RTSP_THREAD (gst_mini_object_copy (GST_MINI_OBJECT (thread1)));
  fail_unless (GST_IS_RTSP_THREAD (thread2));
  fail_unless (GST_IS_MINI_OBJECT_TYPE (thread2, GST_TYPE_RTSP_THREAD));

  gst_rtsp_thread_stop (thread1);
  gst_rtsp_thread_stop (thread2);
  g_object_unref (pool);
}

GST_END_TEST;


static void
teardown (void)
{
  gst_rtsp_thread_pool_cleanup ();
}

static Suite *
rtspthreadpool_suite (void)
{
  Suite *s = suite_create ("rtspthreadpool");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_add_checked_fixture (tc, NULL, teardown);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_pool_get_thread);
  tcase_add_test (tc, test_pool_get_media_thread);
  tcase_add_test (tc, test_pool_get_thread_reuse);
  tcase_add_test (tc, test_pool_max_threads);
  tcase_add_test (tc, test_pool_max_threads_property);
  tcase_add_test (tc, test_pool_thread_copy);

  return s;
}

GST_CHECK_MAIN (rtspthreadpool);
07070100000082000081A400000000000000000000000169D554BF000010CB000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/tests/check/gst/token.c    /* GStreamer
 * Copyright (C) 2013 Sebastian Rasmussen <sebras@hotmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-token.h>

GST_START_TEST (test_token)
{
  GstRTSPToken *token;
  GstRTSPToken *token2;
  GstRTSPToken *copy;
  GstStructure *str;

  token = gst_rtsp_token_new_empty ();
  fail_if (gst_rtsp_token_is_allowed (token, "missing"));
  gst_rtsp_token_unref (token);

  token = gst_rtsp_token_new ("role", G_TYPE_STRING, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");
  fail_unless (gst_rtsp_token_is_allowed (token, "permission1"));
  fail_if (gst_rtsp_token_is_allowed (token, "permission2"));
  fail_if (gst_rtsp_token_is_allowed (token, "missing"));
  copy = GST_RTSP_TOKEN (gst_mini_object_copy (GST_MINI_OBJECT (token)));
  gst_rtsp_token_unref (token);
  fail_unless_equals_string (gst_rtsp_token_get_string (copy, "role"), "user");
  fail_unless (gst_rtsp_token_is_allowed (copy, "permission1"));
  fail_if (gst_rtsp_token_is_allowed (copy, "permission2"));
  fail_if (gst_rtsp_token_is_allowed (copy, "missing"));
  gst_rtsp_token_unref (copy);

  token = gst_rtsp_token_new ("role", G_TYPE_STRING, "user",
      "permission1", G_TYPE_BOOLEAN, TRUE,
      "permission2", G_TYPE_BOOLEAN, FALSE, NULL);
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");
  fail_unless (gst_rtsp_token_is_allowed (token, "permission1"));
  fail_if (gst_rtsp_token_is_allowed (token, "permission2"));
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");

  fail_unless (gst_mini_object_is_writable (GST_MINI_OBJECT (token)));
  fail_unless (gst_rtsp_token_writable_structure (token) != NULL);
  fail_unless (gst_rtsp_token_get_structure (token) != NULL);

  token2 = gst_rtsp_token_ref (token);

  fail_if (gst_mini_object_is_writable (GST_MINI_OBJECT (token)));
  ASSERT_CRITICAL (fail_unless (gst_rtsp_token_writable_structure (token) ==
          NULL));
  fail_unless (gst_rtsp_token_get_structure (token) != NULL);

  gst_rtsp_token_unref (token2);

  fail_unless (gst_mini_object_is_writable (GST_MINI_OBJECT (token)));
  fail_unless (gst_rtsp_token_writable_structure (token) != NULL);
  fail_unless (gst_rtsp_token_get_structure (token) != NULL);

  str = gst_rtsp_token_writable_structure (token);
  gst_structure_set (str, "permission2", G_TYPE_BOOLEAN, TRUE, NULL);
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");
  fail_unless (gst_rtsp_token_is_allowed (token, "permission1"));
  fail_unless (gst_rtsp_token_is_allowed (token, "permission2"));
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");

  gst_rtsp_token_set_bool (token, "permission3", FALSE);
  fail_unless (!gst_rtsp_token_is_allowed (token, "permission3"));
  gst_rtsp_token_set_bool (token, "permission4", TRUE);
  fail_unless (gst_rtsp_token_is_allowed (token, "permission4"));

  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"), "user");
  gst_rtsp_token_set_string (token, "role", "admin");
  fail_unless_equals_string (gst_rtsp_token_get_string (token, "role"),
      "admin");

  gst_rtsp_token_unref (token);
}

GST_END_TEST;

static Suite *
rtsptoken_suite (void)
{
  Suite *s = suite_create ("rtsptoken");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_token);

  return s;
}

GST_CHECK_MAIN (rtsptoken);
 07070100000083000081A400000000000000000000000169D554BF000008A0000000000000000000000000000000000000002F00000000gst-rtsp-server-1.28.2/tests/check/meson.build    pluginsdirs = []
if gst_dep.type_name() == 'pkgconfig'
  pbase = dependency('gstreamer-plugins-base-' + api_version, required: true)
  pbad = dependency('gstreamer-plugins-bad-' + api_version, required: true)

  pluginsdirs = [gst_dep.get_variable('pluginsdir'),
                 pbase.get_variable('pluginsdir'),
                 pbad.get_variable('pluginsdir')]

  gst_plugin_scanner_dir = gst_dep.get_variable('pluginscannerdir')
else
  gst_plugin_scanner_dir = subproject('gstreamer').get_variable('gst_scanner_dir')
endif
gst_plugin_scanner_path = join_paths(gst_plugin_scanner_dir, 'gst-plugin-scanner')

fsmod = import('fs')
test_c_args = [
  '-UG_DISABLE_ASSERT',
  '-UG_DISABLE_CAST_CHECKS',
  '-DGST_CHECK_TEST_ENVIRONMENT_BEACON="GST_PLUGIN_LOADING_WHITELIST"',
  '-DGST_TEST_FILES_PATH="' + fsmod.as_posix(meson.current_source_dir()) + '/../files"',
]

rtsp_server_tests = [
  'gst/addresspool',
  'gst/client',
  'gst/mountpoints',
  'gst/mediafactory',
  'gst/media',
  'gst/permissions',
  'gst/rtspserver',
  'gst/sessionmedia',
  'gst/sessionpool',
  'gst/stream',
  'gst/threadpool',
  'gst/token',
  'gst/onvif',
]

if not get_option('rtspclientsink').disabled()
  rtsp_server_tests += ['gst/rtspclientsink']
endif

foreach test_name : rtsp_server_tests
  fname = '@0@.c'.format(test_name)
  test_name = test_name.underscorify()

  env = environment()
  env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '')
  env.set('GST_STATE_IGNORE_ELEMENTS', '')
  env.set('GST_PLUGIN_LOADING_WHITELIST', 'gstreamer:gst-plugins-base:gst-plugins-good:gst-plugins-bad:gst-rtsp-server@' + meson.project_build_root())
  env.set('CK_DEFAULT_TIMEOUT', '120')
  env.set('GST_REGISTRY', join_paths(meson.current_build_dir(), '@0@.registry'.format(test_name)))
  env.set('GST_PLUGIN_PATH_1_0', [meson.global_build_root()] + pluginsdirs)
  env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path)

  exe = executable(test_name, fname,
    include_directories : rtspserver_incs,
    c_args : rtspserver_args + test_c_args,
    dependencies : [gstcheck_dep, gstrtsp_dep, gstrtp_dep, gst_rtsp_server_dep,
      gstvideo_dep]
  )
  test(test_name, exe,
    env : env,
    timeout : 120,
    is_parallel: false
  )
endforeach
07070100000084000041ED00000000000000000000000269D554BF00000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.28.2/tests/files    07070100000085000081A400000000000000000000000169D554BF0005DFEE000000000000000000000000000000000000002C00000000gst-rtsp-server-1.28.2/tests/files/test.avi   RIFF AVI LIST~  hdrlavih8   @          }              (                      LIST  strlstrh8   vidsI420                      }                     strf(   (   (        I420                   JUNK         00db                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            LIST~  strlstrh8   auds                  @       p                   strf     @  @      JUNK         01wb                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            LIST   odmldmlh   }   LIST movi01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       g+)]       դ'*b pxm       %b))       5LwsBb       \ANH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       3d8       D>P2       zJabn       7njք       Q>`6bH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       xベڎ1       dv"kk       BitL       ib       /%_H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       @;'Dz       w}n3       |[]       #%%^|       gߛq4kH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       @t;@+       C6&"i       z*'       eōЛ       .KdgpH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       \kF(       iώ@       {^<J       FPȮ       cbÊ2\UH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       'Uk>3pY       vJnJ$       ȵzcp7       r^/|˨       +~t?oH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8arN\       طnJ       d;!F         [M       ^K>ǅĀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Hbtg       2L#VЅ        Iݭ}       xӠ\P7       RU֓lҡH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ,Qh4ǌ?       hV        (m       4y8;       K+Dh!yH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       te*p       Ç#F/       5`]u x       I#ʁ9       hݤH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Q+%=Ь       bP~;An       J!۶A       JdBc       ݷWlT+RH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       7ÕK       1fEq       +W(b       G,y&R}p       8z~'ÌH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       6|H}       Ԡ>nۛܰ       pX[ڙeH       RR\i֍_       xDV`H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$900db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8CnH       6l#Z&       3zh)Z       սOUs       R2!:`_H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       w|*&       ֣vH!!       _W8J        Q"[$       {8ݭǀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ULۀdD       &       ~\:       ESexl       XzܻkDpH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       \.%K       ',!*       5ܙJDMu       G[gs5>zH       IGܨ+DH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       {~       Ȳ>S3B       ,B5j_       bmބKޫ       1͡3HLoH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       jؗ.Htp)       DZԧs       TYS0       o/       y3"ːH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       /Qgê       11]n       Ԏl       OU<Jv       rPWVI%H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       TxhvSY       YfhMWb       *Vh8+ӽ       @>@`h       <shʉH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ?Eg<       j8n'L0)       aUO+k;       DQJ:;>       ʄZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Nn/h       r6ISzPu       ˫pjL\       iFhqrD       晝1ɜH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       }0-VZ       ¢ؼn       n&bH2{Y       mz4UT       5Ʉ豒H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       zt臱"       A~6bbM       |\#2ZM       RJ;#Phh       dN)afH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       &e>Vu]6W       <4.Jҭ       BK       >`       V\ʱH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       la%.k       iʦ<l}o       ߒeG       n=D       6afB=}H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       nZ|       !+ֱ       %k_3       E3q,       +){cj_H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       sNlk       1#t&&Pʤ       nI;8)C       L^%1b       ^<;?H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ox,D       xmuJT       5>'       wn}zmJz       =FqH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       .^DZζ)       ͇v       8lnu֕       DbIW       OR?H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Dl\d       FQP}q4i       )>B^b       %&       E#ջH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       z뱀D^PI       /3?2D       uФDbp)       G6)7Q%       bK|Ξh H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ./b,\       d.+tatU       XFrby?Z3       P[$JPN       R88%PǩH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       *șݯ&=       ";V>       Lx;       D/|       vnJH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8QD31       mbbX       ]+V%       h6КVnJ       *-jPH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       *nK       3rqo       w<&[       9W3%V       If1},k-H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       -jmAO       ܼbK&և}       Z\d3}       i.VF       ]{PsրH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       P#Pɒ{I?       {h)4tߒ$H       &GT;3޻~       d"#X^za       u"SʾH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ƎJ]Yh       /lnwߖ9       rmx4}       }t}1       n*LPNձH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       <IFQf       aa<=e}[d       3V@c       h:v\       KڑMNH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       WKyV       $]IP*5       "JV       ^˥fR[       f?(hYH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ?7>       R$w_fiK       ^KO}s1       @D`d       c6&πH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       &dW7c!       1[h,ʇ       x`HM_       7N΁za       ޱɕ߇Ȃ|H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       0Ab4P>       KnϹz       Vb÷%$       3$ʁu       `dMz\H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ~Ќ7V       ۡ෌D_D       y,Ы+d       veR       >_o1rݱH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       K"z\f       j(q\!$       Ekk~       ^"魴l.       Sh<a-'H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Z@vJˌ       v{rKck[       1>+       ݤDBCޜ       M()\R>H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       @%ٻ\       T/x!       $Jf       3CM1a_       Zo˥ZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       c6F[       >az~       0Iy2bD       pIo\5       X+㦥ThH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       qSt|O       v5n<z,       >[       <UG(D!t       N9bH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       %asDvyH+       7>DCme{       LWDMw_       8}^h       TiQwH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       PVLD+⅜J       bHDPg2       r?+h       u=4vqH       ⰅkbEDFH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       X"P+p>       ,QjR       1(P       /^ޫn       m6LVH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       b{!%       tsܑ       8gW#]       i:>֩yw       U玵hC.H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       $TpA\Rv       zy       pd_       XPgp       Vea5ĀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ?       zgReb       Bykѽ       DgS֔60V1       2c1^ÀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Z?`_]       SLV!       YC鼣y\~       \`DL       P+S>&mH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       PGSJ       ۛ=1+=       ec1X       XuLR       KFI%nJH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Jt59       pb|       U(T%]JS       )=JTB݄       b\HˀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       \:       ^Ynw       VǓ       V0Jp{d:       &=JVH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       #'%Aʿ]       {gdx       ֙:UÏ%z       +5h2f       q1H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       9n       =VJ4Ӎ       L-N&       b3'zp       W1DH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       4rЕS       N?lA4%g       S!""       t;Փ;E;       8χKb+H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       |趁k       ڭo<       zta a       O~:|       b2ͯyyH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ΂eJ7ʚh       ~N'JD       }w9Hn       TnTI       `(ۜڹH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       zp%       ^7%ۑB       .IQ       P4z2       &W$FJH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       2#1       hL{m_+>       f;h~t       |p.!+       >8`tH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ng+%P       $֥i8b       B|zs       qLhn֍C       ri]dH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       L\hh       2WWn       |BٓI6N\       Z׸%/       fgV,H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK100db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       hY       ~       1r%S       A{ké       vǷ]_̺>H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       bt$ffJ{`       f˱ֽʤ       v<iq       ןt.Ȏ_       AAtH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       gՄ#76?}       CfI?Ȑ=\       Ntmz       hO       %O}ȂH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       >vV6ĥ       *       <+#       j\HD       8~̄%H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       1hz6Y       W~Dַ+       \p2       I.v       ~3=J)mH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       #JO+Ћ       {:|c       "]D=,       n>@       zXuoͅH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       6^7       ohztp       ⎭j]Ṣ       WPhKjǧ       ־>H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       G$]TNh        ݕtJ       kҥ"VP       fL+       }Rʛ"8H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       =zl9       pt       v       aֹ{       B(hx0ÀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Vb*6#18L       88V_1D{       +F_yRԑ       NtJ/J       u?`p++рH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ++tBt       jZLJTX       /kne1"       \ѵdtPD       g% zH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       %h/\       `VJ\ԝ       h_T       z2qTZ       UdMw˟H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       QF>+       B<Dn$       Fd[_/<       aڂ2Zf       V<hիMH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       5]+       bDܜah,8       O}3V       lr       eJX`h)H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       + jPM'       b~v`J       ?b5Cz       vI`[!E       >AH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       nHVvbwEp       %ϰvD1@       ؁%t7o       ]1[O_8       ʨf-\DaH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       :Zmౘq       nVC*       >hz֖       S(ӱr4       +^2H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ⿾1s%       b˕Vo       PH       q%/IX(       3g6hVAH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8h@9҆4~       PuԷ$zJd       ^nmsEkhL       ࢼ1BP       Wh1;26}H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       t&b       Qklj?w       īr       )JgV"l       )ݏ<tH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       LL"\       mտhX       [bVT       e\}ǳ       4M/E_~H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       з>.07)       hȭp<tW̓       I~"Z       zatPbu&b       4-ta>t(xH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ³6"Ф[       @Dnl֫w       28}!       :#<;P       r;!tH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$900db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ynT+(       6#1'       蔢1n       DBr}       +ڂ{$H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       J2Lcކ       ֔Ah4@       XoO4޿       8wbJ       &\8sRH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       fb       ՍC@       <       sԡߟ       %qJH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       }^eJ}U       D+0       ?9$       +J[2tx       'գ~`^&H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       JWy%C       %}"L8'H       xSdAz       kbR}       \ٙ3P-H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       4@z       ٥;+b       YopBD       ѷj4B-Y       f{8t@ۀH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       żg       k"L       (Ӫc       MЩ       nnH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       #VKv%v       fORU       ӑ1vh1       ,fnfC       ٵ倀H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       KQgV1       DT[       a4jКA%       Jfia#t       #vMhDzH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ð0JbV       f|quh       JVZFJ       )%oY`       ŕzN$ŮH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       c8H~6x9       A>H@       J?f@=!)       r,Kg5hz       -RXH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Phʼ(poS       +^2x8#       v$*zx       \1z'       n%H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       t¥Ebn       s#]       vVEg7       he8B       <:u1xH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Dslу       9MǇ=̞8       PC4b6       W,>       P(H1jطH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ǟ*;w       ܏P%C\       R|{[&       kFK~t       <H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       7>T{M.,       r,z+îx<[       3V@L       0qUm       6pYD:pH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8͵<       c=^e       LIeB       fZ_٥       GPxP܀H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       8B       ^a9+06H       ̈hXk       n+ ذp       䖝ʫH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       Bbn:)m;       H"fk8       ^89       r̕zB       '|tdMH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       j@+ߟ       uB[x}       O^V       ОU]϶       ߨȣH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       BvĽ8nz       AiK8x^       zWAG       ^>|       ϊ?+H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       *Nd[?(       \q       ezxy       ,j       @5~_·DV5H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       ?゙w       %x{;XrI       [߯,       ](eDx       p{PZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       aب       ύ4#:n       }Fq%       PU\:       |eR%:&H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       JyX;kq       m1q       ǷLT j       Zgyf&H@D       \>6JbS>H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       2Pm'W9       mPt       9Uŷg=       +<sIb~       x,=CFH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       a$5t\       bGīP       FA=       o(QSӧ       pJ}VdZ@H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       sgwЏH       <7>b       #s"h%x       9Co2fӍ       ޘh]l8H[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       oڷ"T2h       <ޒ>oe       <R.h;       j6۷a       B}ٖyLH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       :e8_r       U2*L       V+       B^5%_       8MjnV>ကH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ00db  ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ))))))ҪjjjjjjQQQQQQ)))))))))))jjjjjj)))))jjjjjj       >V#:       VSM       BbQ5\       \ͼb       4>d#eH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZH[n66ZZZ𸸀HH[[mmmۿɶƀ뀀ƀ뀀Q""nnnQ""nnnQ""nnnQ""nnnQ""nnnQ""nnnwwwwdQQwwwwwwBBGһHHHƀƀ01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼ01wb   yW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'01wb   &<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tv01wb   ʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3No01wb   oN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#01wb   +Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]01wb   å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9Wy01wb   ȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK101wb   1KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-Ff01wb   ԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`01wb   ]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC01wb   +#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3N01wb   oδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /Ii01wb   ҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z01wb   <&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$901wb   WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs01wb   ̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Klжs01wb   Q5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)01wb   A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|01wb   ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7Tvʮ01wb   iI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 01wb   /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc01wb   ־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å01wb   `A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-01wb   "5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11Kl01wb   жsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼyW9$)A`]>'&<Z|ƨcC+#7TvʮiI/ 3NoδoN3 /IiҹvT7#+Cc־|Z<&'>]å`A)$9WyȫfF-"5Qs̱lK11KlжsQ5"-FfԼidx1  01wb         01wb    	     01wb         01wb         00db       01wb    v     00db   ~    00db   6#    00db   '    01wb    ,     00db   0    00db   f5    00db   :    01wb    >     00db   B    00db   G    00db   NL    01wb    Q     00db   U    00db   Y    00db   ~^    00db   6c    01wb    g     00db   k    00db   p    00db   fu    01wb    z     00db   &~    00db   ނ    00db       01wb    N     00db   V    00db       00db   ƙ    01wb    ~     00db       00db   >    00db       01wb         00db       00db   n    00db   &    00db       01wb         00db       00db   V    00db       01wb         00db       00db       00db   >    01wb         00db       00db       00db   n    01wb    &     00db   .   00db      00db      01wb    V    00db   ^   00db      00db      00db   "   01wb    >'    00db   F+   00db   /   00db   4   01wb    n9    00db   v=   00db   .B   00db   F   01wb    K    00db   O   00db   ^T   00db   Y   01wb    ]    00db   a   00db   f   00db   Fk   01wb    o    00db   t   00db   x   00db   v}   00db   .   01wb        00db      00db      00db   ^   01wb        00db      00db   ֡   00db      01wb    F    00db   N   00db      00db      01wb    v    00db   ~   00db   6   00db      01wb        00db      00db   f   00db      00db      01wb        00db      00db   N   00db      01wb        00db      00db   ~   00db   6   01wb    
    00db      00db      00db   f   01wb        00db   &!   00db   %   00db   *   01wb    N/    00db   V3   00db   8   00db   <   00db   ~A   01wb    6F    00db   >J   00db   N   00db   S   01wb    fX    00db   n\   00db   &a   00db   e   01wb    j    00db   n   00db   Vs   00db   x   01wb    |    00db   ΀   00db      00db   >   01wb        00db      00db      00db   n   00db   &   01wb    ޥ    00db      00db      00db   V   01wb        00db      00db      00db      01wb    >    00db   F   00db      00db      01wb    n    00db   v   00db   .   00db      01wb        00db      00db   ^   00db      01wb         01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    !    01wb    %    01wb    )    01wb    &-    01wb    .1    01wb    65    01wb    >9    01wb    F=    01wb    NA    01wb    VE    01wb    ^I    01wb    fM    01wb    nQ    01wb    vU    01wb    ~Y    01wb    ]    01wb    a    01wb    e    01wb    i    01wb    m    01wb    q    01wb    u    01wb    y    01wb    }    01wb    ΁    01wb    օ    01wb    މ    01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    &    01wb    .    01wb    6    01wb    >    01wb    F    01wb    N    01wb    V    01wb    ^    01wb    f    01wb    n    01wb    v    01wb    ~    01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    
    01wb        01wb        01wb        01wb        01wb        01wb    #    01wb    '    01wb    +    01wb    &/    01wb    .3    01wb    67    01wb    >;    01wb    F?    01wb    NC    01wb    VG    01wb    ^K    01wb    fO    01wb    nS    01wb    vW    01wb    ~[    01wb    _    01wb    c    01wb    g    01wb    k    01wb    o    01wb    s    01wb    w    01wb    {    01wb        01wb    ΃    01wb    և    01wb    ދ    01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    &    01wb    .    01wb    6    01wb    >    01wb    F    01wb    N    01wb    V    01wb    ^    01wb    f    01wb    n    01wb    v    01wb    ~    01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb         01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    !    01wb    %    01wb    )    01wb    -    01wb    &1    01wb    .5    01wb    69    01wb    >=    01wb    FA    01wb    NE    01wb    VI    01wb    ^M    01wb    fQ    01wb    nU    01wb    vY    01wb    ~]    01wb    a    01wb    e    01wb    i    01wb    m    01wb    q    01wb    u    01wb    y    01wb    }    01wb    Ɓ    01wb    ΅    01wb    ։    01wb    ލ    01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb        01wb    &    01wb    .    01wb    6    01wb    >    01wb    F    01wb    N      07070100000086000081A400000000000000000000000169D554BF000001D4000000000000000000000000000000000000002900000000gst-rtsp-server-1.28.2/tests/meson.build  # FIXME: make check work on windows
if get_option('tests').disabled() or static_build or host_machine.system() == 'windows'
  subdir_done()
endif

if gstcheck_dep.found()
  subdir('check')
endif

test_cleanup_exe = executable('test-cleanup', 'test-cleanup.c',
  dependencies: gst_rtsp_server_dep)

test_reuse_exe = executable('test-reuse', 'test-reuse.c',
  dependencies: gst_rtsp_server_dep)

test('test-cleanup', test_cleanup_exe)
test('test-reuse', test_reuse_exe)
07070100000087000081A400000000000000000000000169D554BF000006EE000000000000000000000000000000000000002C00000000gst-rtsp-server-1.28.2/tests/test-cleanup.c   /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

static gboolean
timeout (GMainLoop * loop)
{
  g_main_loop_quit (loop);
  return FALSE;
}


int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstRTSPServer *server;
  guint id;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* We just want to bind any port here, so that tests can run in parallel */
  gst_rtsp_server_set_service (server, "0");

  /* attach the server to the default maincontext */
  if ((id = gst_rtsp_server_attach (server, NULL)) == 0)
    goto failed;

  g_timeout_add_seconds (2, (GSourceFunc) timeout, loop);

  /* start serving */
  g_main_loop_run (loop);

  /* cleanup */
  g_source_remove (id);
  g_object_unref (server);
  g_main_loop_unref (loop);

  return 0;

  /* ERRORS */
failed:
  {
    g_print ("failed to attach the server\n");
    return -1;
  }
}
  07070100000088000081A400000000000000000000000169D554BF000008FF000000000000000000000000000000000000002A00000000gst-rtsp-server-1.28.2/tests/test-reuse.c /* GStreamer
 * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <gst/gst.h>

#include <gst/rtsp-server/rtsp-server.h>

#define TIMEOUT 2

static gboolean timeout_1 (GMainLoop * loop);

static guint id;
static gint rounds = 3;
static GstRTSPServer *server;

static gboolean
timeout_2 (GMainLoop * loop)
{
  rounds--;
  if (rounds > 0) {
    id = gst_rtsp_server_attach (server, NULL);
    g_print ("have attached\n");
    g_timeout_add_seconds (TIMEOUT, (GSourceFunc) timeout_1, loop);
  } else {
    g_main_loop_quit (loop);
  }
  return FALSE;
}

static gboolean
timeout_1 (GMainLoop * loop)
{
  g_source_remove (id);
  g_print ("have removed\n");
  g_timeout_add_seconds (TIMEOUT, (GSourceFunc) timeout_2, loop);
  return FALSE;
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);

  /* create a server instance */
  server = gst_rtsp_server_new ();

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach the server to the default maincontext */
  if ((id = gst_rtsp_server_attach (server, NULL)) == 0)
    goto failed;
  g_print ("have attached\n");

  g_timeout_add_seconds (TIMEOUT, (GSourceFunc) timeout_1, loop);

  /* start serving */
  g_main_loop_run (loop);

  /* cleanup */
  g_object_unref (server);
  g_main_loop_unref (loop);

  g_print ("quit\n");
  return 0;

  /* ERRORS */
failed:
  {
    g_object_unref (server);
    g_main_loop_unref (loop);

    g_print ("failed to attach the server\n");
    return -1;
  }
}
 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!                                                                                                                                                                                    4462 blocks
