07070100000000000081A400000000000000000000000168EE879700000016000000000000000000000000000000000000002200000000gst-rtsp-server-1.26.7/.gitignore *~
/build
/_build
/b/
  07070100000001000081A400000000000000000000000168EE87970000002A000000000000000000000000000000000000001F00000000gst-rtsp-server-1.26.7/AUTHORS    Wim Taymans <wim.taymans@collabora.co.uk>
  07070100000002000081A400000000000000000000000168EE87970000673F000000000000000000000000000000000000001F00000000gst-rtsp-server-1.26.7/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!

 07070100000003000081A400000000000000000000000168EE87970000673F000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/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!

 07070100000004000081A400000000000000000000000168EE87970001F9AC000000000000000000000000000000000000001C00000000gst-rtsp-server-1.26.7/NEWS   GStreamer 1.26 Release Notes

GStreamer 1.26.0 was originally released on 11 March 2025.

The latest bug-fix release in the stable 1.26 series is 1.26.7 and was released on 14 October 2025.

See https://gstreamer.freedesktop.org/releases/1.26/ for the latest version of this document.

Last updated: Tuesday 14 October 2025, 16:30 UTC (log)

## Introduction

The GStreamer team is proud to announce a new major feature release in the stable 1.x API series of your favourite
cross-platform multimedia framework!

As always, this release is again packed with many new features, bug fixes, and other improvements.

## Highlights

-   H.266 Versatile Video Coding (VVC) codec support
-   Low Complexity Enhancement Video Coding (LCEVC) support
-   Closed captions: H.264/H.265 extractor/inserter, cea708overlay, cea708mux, tttocea708 and more
-   New hlscmafsink, hlssink3, and hlsmultivariantsink; HLS/DASH client and dashsink improvements
-   New AWS and Speechmatics transcription, translation and TTS services elements, plus translationbin
-   Splitmux lazy loading and dynamic fragment addition support
-   Matroska: H.266 video and rotation tag support, defined latency muxing
-   MPEG-TS: support for H.266, JPEG XS, AV1, VP9 codecs and SMPTE ST-2038 and ID3 meta; mpegtslivesrc
-   ISO MP4: support for H.266, Hap, Lagarith lossless codecs; raw video support; rotation tags
-   SMPTE 2038 ancillary data streams support
-   JPEG XS image codec support
-   Analytics: New TensorMeta; N-to-N relationships; Mtd to carry segmentation masks
-   ONVIF metadata extractor and conversion to/from relation metas
-   New originalbuffer element that can restore buffers again after transformation steps for analytics
-   Improved Python bindings for analytics API
-   Lots of Vulkan integration and Vulkan Video decoder/encoder improvements
-   OpenGL integration improvements, esp. in glcolorconvert, gldownload, glupload
-   Qt5/Qt6 QML GL sinks now support direct DMABuf import from hardware decoders
-   CUDA: New compositor, Jetson NVMM memory support, stream-ordered allocator
-   NVCODEC AV1 video encoder element, and nvdsdewarp
-   New Direct3D12 integration support library
-   New d3d12swapchainsink and d3d12deinterlace elements and D3D12 sink/source for zero-copy IPC
-   Decklink HDR support (PQ + HLG) and frame scheduling enhancements
-   AJA capture source clock handling and signal loss recovery improvements
-   RTP and RTSP: New rtpbin sync modes, client-side MIKEY support in rtspsrc
-   New Rust rtpbin2, rtprecv, rtpsend, and many new Rust RTP payloaders and depayloaders
-   webrtcbin support for basic rollbacks and other improvements
-   webrtcsink: support for more encoders, SDP munging, and a built-in web/signalling server
-   webrtcsrc/sink: support for uncompressed audio/video and NTP & PTP clock signalling and synchronization
-   rtmp2: server authentication improvements incl. Limelight CDN (llnw) authentication
-   New Microsoft WebView2 based web browser source element
-   The GTK3 plugin has gained support for OpenGL/WGL on Windows
-   Many GTK4 paintable sink improvements
-   GstPlay: id-based stream selection and message API improvements
-   Real-time pipeline visualization in a browser using a new dots tracer and viewer
-   New tracers for tracking memory usage, pad push timings, and buffer flow as pcap files
-   VA hardware-acclerated H.266/VVC decoder, VP8 and JPEG encoders, VP9/VP8 alpha decodebins
-   Video4Linux2 elements support DMA_DRM caps negotiation now
-   V4L2 stateless decoders implement inter-frame resolution changes for AV1 and VP9
-   Editing services: support for reverse playback and audio channel reordering
-   New QUIC-based elements for working with raw QUIC streams, RTP-over-QUIC (RoQ) and WebTransport
-   Apple AAC audio encoder and multi-channel support for the Apple audio decoders
-   cerbero: Python bindings and introspection support; improved Windows installer based on WiX5
-   Lots of new plugins, features, performance improvements and bug fixes

## Major new features and changes

### H.266 Versatile Video Coding (VVC) codec support

-   The H.266 / VVC video codec is a successor to H.265 / HEVC and is standardised in ISO/IEC 23090-3.

-   A new h266parse element was added, along with parsing API, typefinding support and some codec utility functions in the
    gst-plugins-base utility library.

-   A H.266 decoder base class for hardware-accelerated decoders was added and used to implement a VA-API-based
    hardware-accelerated H.266 decoder.

-   The FFmpeg H.266 decoder is exposed now (from FFmpeg 7.0 onwards).

-   H.266 / VVC muxing and demuxing support was implemented for MP4, Matroska and MPEG-TS containers.

-   A VVdeC-based H.266 decoder element was added to the Rust plugins, based on the Fraunhofer Versatile Video Decoder library.

### JPEG XS image codec support

-   JPEG XS is a visually lossless, low-latency, intra-only video codec for video production workflows, standardised in ISO/IEC
    21122.

-   JPEG XS encoder and decoder elements based on the SVT JPEG XS library were added, including support for muxing JPEG XS into
    MPEG-TS and demuxing it. Both interlaced and progressive modes are supported.

### Low Complexity Enhancement Video Coding (LCEVC) support

-   LCEVC is a codec that provides an enhancement layer on top of another codec such as H.264 for example. It is standardised as
    MPEG-5 Part 2.

-   LCEVC encoder and decoder elements based on V-Nova’s SDK libraries were added, including support in h264parse for extracting
    the enhancement layer from H.264 and decoding it via a lcevch264decodebin element.

### Closed captions improvements

-   New H.264 and H.265 closed captions extractor and inserter elements.

    -   These extractor elements don’t actually extract captions from the bitstream, but rely on parser elements to do that and
        add them to buffers in form of caption metas. The problem is that streams might contain B-frames, in which case the
        captions in the bitstream will not be in presentation order and extracting them requires frame-reordering in the same
        way that a decoder would do. These new elements will do exactly that and allow you to extract captions in presentation
        order without having to decode the stream.

    -   The inserter elements do something similar and insert caption SEIs into the H.264 or H.265 bitstream, taking into
        account frame ordering.

    -   This is useful if one wants to extract, process and re-insert captions into an existing video bitstream without decoding
        and re-encoding it (in which case the decoder and encoder would handle the caption reordering).

-   cdpserviceinject: New element for injecting a CDP service descriptor into closed caption CDP packets

-   cea708overlay: New element for overlaying CEA608 / CEA708 closed captions over video streams.

-   The cc708overlay element has been deprecated in favour of the cea708overlay element from the Rust plugins set.

-   cea608mux gained a "force-live" property to make it always in live mode and aggregate on timeout regardless of whether any
    live sources are linked upstream.

-   cea708mux: New element that allows to mux multiple CEA708 services into a single stream.

-   cccombiner has two new properties:

    -   "input-meta-processing" controls how input closed caption metas are processed and can be used to e.g. discard closed
        captions from the input pad if the matching video buffer already has closed caption on it.

    -   "schedule-timeout" to support timing out captions without EOS

-   tttocea708: New element for converting timed-text to CEA708 closed captions

-   Miscellaneous improvements and spec compliance fixes

### Speech to Text, Translation and Speech Synthesis

-   awstranscriber2, awstranslate: New elements around the AWS transcription and translation services.

-   polly: New element around the AWS text-to-speech polly services

-   speechmatics: New transcriber / speech-to-text and translation element

-   translationbin: Helper bin around translation elements, similar to the already existing transcriberbin for transcriptions.

### HLS DASH adaptive streaming improvements

-   The adaptivedemux2 client implementation gained support for file:// URIs and as such the ability to play HLS and DASH from
    local files. It also no longer sends spurious flush events when it loses sync in live streams, as that is unexpected and
    will be handled poorly in non-playback scenarios. Lastly, support for HTTP request retries was added via the "max-retries"
    property, along with some exponential backoff logic which can be fine-tuned via properties.

-   dashsink has received period duration fixes for dynamic MPDs and some memory leak fixes.

-   hlscmafsink, hlssink3: New single-variant HLS sink elements that can output CMAF (fMP4) or MPEG-TS fragments.

-   hlsmultivariantsink: New sink element that can output an HLS stream with multiple variants

### splitmuxsrc, splitmuxsink: lazy loading and dynamic fragment addition

-   splitmuxsrc and splitmuxsink were originally designed to handle a small number of large file fragments, e.g. for situations
    where one doesn’t want to exceed a certain file size when recording to legacy file systems. It was also designed for playing
    back a static set of file fragments that have been created by an earlier recording session and no longer changes. Over time
    people have found more applications and use cases for the splitmux elements and have been deploying them in different
    scenarios, exposing the limits of the current implementation.

-   In this release, splitmuxsink and splitmuxsrc gained new abilities aimed at improving support for recordings with a large
    number of files, and for adding fragments on the fly to allow playback of ongoing recordings:

    -   You can now add fragments directly to splitmuxsrc and provide the offset and duration in the stream:

        -   Providing offset and duration means splitmuxsrc doesn’t need to scan the file to measure it and calculate it. That
            makes for much faster startup.

        -   The new "add-fragment" signal can be used to add files to the set dynamically - allowing to be playing an ongoing
            recording and adding files to the playback set as they are finished.

    -   splitmuxsrc no longer keeps all files open, but instead only keeps 100 files open by default, configurable with the
        "num-open-fragments" property.

    -   There is a new "num-lookahead" property on splitmuxsrc to trigger (re)opening files a certain distance ahead of the play
        position.

    -   splitmuxsink will report fragment offset and fragment duration via a message on the bus when closing a file. This
        information can then be used to add the new fragment to a splitmuxsrc.

### MPEG-TS container format improvements

-   The MPEG-TS muxer and demuxer gained support for

    -   H.266 / VVC video muxing and demuxing
    -   JPEG-XS video muxing and demuxing
    -   VP9 video muxing and demuxing (using a custom mapping)
    -   AV1 video muxing and demuxing (using a custom mapping, since the work-in-progress specification effort doesn’t seem to
        be going anywhere anytime soon)
    -   SMPTE ST-2038 ancillary metadata streams (see section above)

-   mpegtsmux gained support for muxing ID3 metadata into the TS stream, as well as SMPTE 302M audio.

-   It’s also possible to disable sending SCTE-35 null (heartbeat) packets now in mpegtsmux by setting the
    "scte-35-null-interval" to 0.

-   tsparse now handles 192-byte M2TS packets

-   mpegtslivesrc: New source element that can wrap a live MPEG-TS source (e.g. SRT or UDP source) and provides a clock based on
    the PCR.

### Matroska container format improvements

-   H.266 / VVC video muxing and demuxing support

-   matroskamux

    -   was ported to the GstAggregator base class, ensuring defined-latency muxing in live streaming pipelines.
    -   gained rotation tag support

-   matroskademux now also supports seeks with a stop position in push mode.

### ISO MP4 container format improvements

-   can mux and demux H.266 / VVC in MP4 now

-   can demux Hap video now, as well as Lagarith lossless video and ISO/IEC 23003-5 raw PCM audio.

-   qtdemux handles keyunit-only trick mode also in push mode now

-   support for ISO/IEC 23001-17 raw video in MP4 in qtdemux and isomp4mux.

-   support for rotation tags in the muxers and demuxers was improved to correctly handle per-media and per-track rotations, and
    support for flips was added as well.

SMPTE 2038 ancillary data streams

-   SMPTE 2038 (pdf) is a generic system to put VBI-style ancillary data into an MPEG-TS container. This could include all kinds
    of metadata such as scoreboard data or game clocks, and of course also closed captions, in this case in form of a distinct
    stream completely separate from any video bitstream.

-   A number of new elements in the GStreamer Rust closedcaption plugin add support for this, along with mappings for it in the
    MPEG-TS muxer and demuxer. The new elements are:

    -   st2038ancdemux: splits SMPTE ST-2038 ancillary metadata (as received from tsdemux) into separate streams per DID/SDID
        and line/horizontal_offset. Will add a sometimes pad with details for each ancillary stream. Also has an always source
        pad that just outputs all ancillary streams for easy forwarding or remuxing, in case none of the ancillary streams need
        to be modified or dropped.

    -   st2038ancmux: muxes SMPTE ST-2038 ancillary metadata streams into a single stream for muxing into MPEG-TS with
        mpegtsmux. Combines ancillary data on the same line if needed, as is required for MPEG-TS muxing. Can accept individual
        ancillary metadata streams as inputs and/or the combined stream from st2038ancdemux.

        If the video framerate is known, it can be signalled to the ancillary data muxer via the output caps by adding a
        capsfilter behind it, with e.g. meta/x-st-2038,framerate=30/1.

        This allows the muxer to bundle all packets belonging to the same frame (with the same timestamp), but that is not
        required. In case there are multiple streams with the same DID/SDID that have an ST-2038 packet for the same frame, it
        will prioritise the one from more recently created request pads over those from earlier created request pads (which
        might contain a combined stream for example if that’s fed first).

    -   st2038anctocc: extracts closed captions (CEA-608 and/or CEA-708) from SMPTE ST-2038 ancillary metadata streams and
        outputs them on the respective sometimes source pad (src_cea608 or src_cea708). The data is output as a closed caption
        stream with caps closedcaption/x-cea-608,format=s334-1a or closedcaption/x-cea-708,format=cdp for further processing by
        other GStreamer closed caption processing elements.

    -   cctost2038anc: takes closed captions (CEA-608 and/or CEA-708) as produced by other GStreamer closed caption processing
        elements and converts them into SMPTE ST-2038 ancillary data that can be fed to st2038ancmux and then to mpegtsmux for
        splicing/muxing into an MPEG-TS container. The line-number and horizontal-offset properties should be set to the desired
        line number and horizontal offset.

### Analytics

-   Added a GstTensorMeta: This meta is designed to carry tensors from the inference element to the model-specific tensor
    decoder. This also includes a basic GstTensor class containing a single tensor. The actual tensor data is a GstBuffer.

-   Add N_TO_N relationship to GstAnalyticsRelationMeta: This makes it possible to describe N-to-N relationships. For example,
    between classes and regions in an instance segmentation.

-   Add a new analytics Mtd to carry segmentation masks: Being part of the GstAnalyticsMeta, it can be in relationship with the
    other Mtd, such as the classification and object detection bounding boxes.

-   onvifmetadataextractor: New element that can extract ONVIF metadata from GstMetas into a separate stream

-   originalbuffer: New plugin with originalbuffersave / originalbufferrestore elements that allow saving an original buffer,
    modifying it for analytics, and then restoring the original buffer content while keeping any additional metadata that was
    added.

-   relationmeta: New plugin with elements converting between GstRelationMeta and ONVIF XML metadata.

-   Improved Python bindings for a more Pythonic interface when iterating over GstRelationMeta’s mtd

### Vulkan integration enhancements

-   Vulkan Integration Improvements:

    -   Memory Management: Non-coherent memory is now invalidated when mapping for read in vkmemory.

    -   Color Space Selection: The vkswapper component now chooses the correct color space based on the format.

    -   Vulkan Plugin Compatibility: Support added for cases where glslc is not available for building Vulkan plugins, along
        with GLSL compiler support for glslang.

    -   Fullscreen Quad Updates: Improved support for setting NULL input/output buffers and added checks for unset video info.

    -   Vulkan Buffer Pool Enhancements: Buffer pool access flags and usage configurations have been refined, offering better
        performance for video decoding and encoding.

-   Decoder/Encoder Improvements:

    -   H264 Decoder: Enhancements to the vkh264dec component for better support of extended profiles and interlaced content
        decoding.

    -   H265 Decoder fixes: vkh265dec updated for proper handling of VPS/SPS on demand, along with fixes to PicOrderCntVal.

    -   Encoder Performance: Various internal optimizations to the Vulkan encoder, including removal of redundant references and
        better management of the DPB view.

-   Vulkan Instance and Device Management:

    -   Device Handling: Added new utility functions for managing Vulkan device instances, including
        gst_vulkan_instance_create_device_with_index and gst_vulkan_ensure_element_device.

    -   Device Context Management: Updates to manage Vulkan context handling more effectively within the application.

### OpenGL integration enhancements

-   glcolorconvert gained support for more formats and conversions:

    -   Planar YUV <-> planar YUV conversions
    -   Converting to and from v210 in general
    -   v210 <-> planar YUV
    -   UYVY and YUY2 <-> planar YUV
    -   v210 <-> UYVY and YUY2
    -   Support for Y444_10, Y444_16, I422_10, I422_12 pixel formats (both little endian and big endian variants)

-   gldownload can import DMABufs from a downstream pool

-   glupload gained a DRM raw uploader

### Qt5 + Qt6 QML integration improvements

-   qmlglsink, qml6glsink now support external-oes textures, which allows direct DMABuf import from hardware decoders. Both also
    support NV12 as an input format now.

-   qmlglsink gained support for RGB16/BGR16 as input format

-   qmlgl6src can now use a downstream buffer pool when available

-   qmlgloverlay make the depth/stencil buffer optional, which reduces memory bandwidth on Windows.

### CUDA / NVCODEC integration and feature additions

-   Added AV1 video encoder nvav1enc

-   CUDA mode nvcuda{CODEC}enc encode elements are renamed to nv{CODEC}enc and old nv{CODEC}enc implementations are removed

-   Added support for CUDA Stream-Ordered allocator

-   Added cudacompositor element which is equivalent to the software compositor element but uses CUDA

-   Added support for CUDA kernel precompile at plugin build time using nvcc and NVCODEC plugin can cache/reuse compiled CUDA
    CUBIN/PTX

-   cudaupload and cudadownload elements can support Jetson platform’s NVMM memory in addition to already supported desktop NVMM
    memory

-   Introduced nvdswrapper plugin which uses NVIDIA DeepStream SDK APIs with gst-cuda in an integrated way:

    -   nvdsdewarp: NVIDIA NVWarp360 API based dewarping element

### GStreamer Direct3D12 integration

-   New gst-d3d12 public library. The following elements are integrated with the gst-d3d12 library:

    -   NVIDIA NVCODEC decoders and encoders can support D3D12 memory
    -   Intel QSV encoders can accept D3D12 memory
    -   All elements in dwrite plugin can support D3D12 memory

-   The d3d12 library and plugin can be built with MinGW toolchain now (in addition to MSVC)

-   D3D12 video decoders and d3d12videosink are promoted to have higher rank than D3D11 elements

-   Added support for multiple mip-levels D3D12 textures:

    -   Newly added d3d12mipmapping element can generate D3D12 textures with multiple mip-levels

    -   max-mip-levels property is added to d3d12convert, d3d12videosink, and d3d12swapchainsink element, so that the elements
        can generate an intermediate texture with multiple mip-levels in order to reduce downscale aliasing artifacts

-   d3d12convert, d3d12videosink, and d3d12swapchainsink support the GstColorBalanceInterface to offer color balancing functions
    such as hue, saturation, and brightness adjustment

-   Added d3d12ipcsink and d3d12ipcsrc elements for zero-copy GPU memory sharing between processes

-   d3d12upload and d3d12download support direct GPU memory copy between D3D12 and D3D12 resources

-   Added d3d12swapchainsink element to support DirectComposition or UWP/WinUI3 SwapChainPanel based applications

-   Added d3d12deinterlace element which performs deinterlacing using a GPU vendor agnostic compute shader.

-   d3d12screencapturesrc element can capture HDR enabled desktop correctly in DDA mode (DXGI Desktop Duplication API)

### Capture and playout cards support

-   ajasrc: Improve clock handling, frame counting, capture timestamping, and signal loss recovery

-   The Blackmagic Decklink plugin gained support

    -   for HDR output and input (PQ + HLG static metadata)

    -   all modes of Quad HDMI recorder

    -   scheduling frames before they need to be displayed in decklinkvideosink

### RTP and RTSP stack improvements

-   rtspsrc now supports client-managed MIKEY key management. Some RTSP servers (e.g. Axis cameras) expect the client to propose
    the encryption key(s) to be used for SRTP / SRTCP. This is required to allow re-keying. This mode can be enabled by enabling
    the "client-managed-mikey-mode" property and comes with a number of new signals ("request-rtp-key" and "request-rtcp-key"),
    action signals ("set-mikey-parameter" and "remove-key") and properties ("hard-limit" and "soft-limit").

-   rtpbin: Add new “never” and “ntp” RTCP sync modes

    -   Never is useful for some RTSP servers that report plain garbage both via RTCP SR and RTP-Info, for example.
    -   NTP is useful if synchronization should only ever happen based on RTCP SR or NTP-64 RTP header extensions.

    This is part of a bigger refactoring of the synchronization / offsetting code in rtpbin, which also makes it regularly emit
    the sync signal even if no new synchronisation information is available, controlled by the new "min-sync-interval" property.

-   rtpjitterbuffer: add RFC7273 active status to jitterbuffer stats so applications can easily check whether RFC7273 sync is
    active.

-   rtph265depay: Add "wait-for-keyframe" "request-keyframe" properties and improve request keyframe logic

-   rtppassthroughpay gained the ability to regenerate RTP timestamps from buffer timestamps via the new "retimestamp-mode"
    property. This is useful in a relay RTSP server if one wants to do full drift compensation and ensure that the stream coming
    out of gst-rtsp-server is not drifting compared to the pipeline clock and also not compared to the RTCP NTP times.

-   New Rust RTP payloaders and depayloaders for AC3, AMR, JPEG, KLV, MPEG-TS (MP2T), MPEG-4 (MP4A, MP4G), Opus, PCMU (uLaw),
    PCMA (aLaw), VP8, VP9.

-   New rtpbin2 based on separate rtprecv and rtpsend elements

### WebRTC improvements

-   webrtcbin improvements

    -   Make basic rollbacks work

    -   Add "reuse-source-pads" property: When set to FALSE, if a transceiver becomes send-only or inactive then pre-existing
        source pads will receive an EOS event and no further traffic even after further renegotiation. When TRUE, pads will
        simply not receive any output when the negotiated transceiver state doesn’t have incoming traffic. If renegotiated
        later, the pad will receive data again.

    -   Early CNAME support (RFC5576): Have CNAME available to the jitterbuffer before the the first RTCP SR is received, for
        rapid synchronization.

    -   New "post-rtp-aux-sender" signal to allow for placement of an object after rtpbin, before sendbin. This is useful for
        objects such as congestion control elements, that don’t want to be burdened by the synchronization requirements of
        rtpsession.

    -   Create and associate transceivers earlier in negotiation, and other spec compliance improvements

    -   Statistics generation improvements for bundled streams

-   webrtcsink improvements:

    -   Support for more encoders: nvv4l2av1enc, vpuenc_h264 (for imx8mp), nvav1enc, av1enc, rav1enc and nvh265enc.

    -   The new "define-encoder-bitrates" signal allows applications to fine-tune the bitrate allocation for individual streams
        in cases where there are multiple encoders. By default the bitrate is split equally between encoders.

    -   A generic mechanism was implemented to forward metas over the control channel.

    -   Added a mechanism for SDP munging to handle server-specific quirks.

    -   Can expose a built-in web server and signalling server for prototyping and debugging purposes.

-   webrtcsink and webrtcsrc enhancements:

    -   Support for raw payloads, i.e. uncompressed audio and video

    -   NTP & PTP clock signalling and synchronization support (RFC 7273)

    -   Generic data channel control mechanism for sending upstream events back to the sender (webrtcsink)

-   webrtcsrc now has support for multiple producers

## New elements and plugins

-   Many exciting new Rust elements, see Rust section below.

-   webview2src: new Microsoft WebView2 based web browser source element

-   h264ccextractor, h264ccinserter: H.264 closed caption extractor / inserter

-   h265ccextractor, h265ccinserter: H.265 closed caption extractor / inserter

-   h266parse

-   lcevch264decodebin

-   New VA elements (see below): vah266dec, vavp8enc, vajpegenc, vavp8alphadecodebin, vavp9alphadecodebin

-   svtjpegxsdec, svtjpegxsenc: SVT JPEG XS decoder/encoder

-   Many other new elements mentioned in other sections (e.g. CUDA, NVCODEC, etc.)

## New element features and additions

-   audioconvert enhancements:

    -   Add possibility to reorder input channels when audioconvert has unpositionned audio channels as input. It can now use
        reordering configurations to automatically position those channels via the new "input-channels-reorder" and
        "input-channels-reorder-mode" properties.

    -   Better handling of setting of the mix matrix at run time

    -   handles new GstRequestMixMatrix custom upstream event

-   audiorate: Take the tolerance into account when filling gaps; better accounting of the number of samples added or dropped.

-   av1enc: Add "timebase" property to allow configuring a specific time base, in line with what exists for vp8enc and vp9enc
    already.

-   av1parse can parse annexb streams now, and typefinding support has been added for annexb streams as well.

-   The GTK3 plugin has gained support for OpenGL/WGL on Windows

-   fdsrc has a new "is-live" property to make it act like a live source and timestamp the received data with the clock running
    time.

-   imagefreeze: Add support for JPEG and PNG

-   kmssink: Extended the functionality to support buffers with DRM formats along with non-linear buffers

-   pitch now supports reverse playback

-   queue can emit the notify signal on queue level changes if the "notify-levels" property has been set.

-   qroverlay: the "pixel-size" property has been removed in favour of a new "size" property with slightly different semantics,
    where the size of the square is expressed in percent of the smallest of width and height.

-   rsvgdec: Negotiate resolution with downstream and scale accordingly

-   rtmp2: server authentication improvements

    -   Mimic librtmp’s behaviour and support additional connection parameters for the connect packet, which are commonly used
        for authentication, via the new "extra-connect-args" property.

    -   Add support for Limelight CDN (llnw) authentication

-   scaletempo has gained support for a “fit-down” mode: In fit-down mode only 1.0 rates are supported, and the element will fit
    audio data in buffers to their advertised duration. This is useful in speech synthesis cases, where elements such as
    awspolly will generate audio data from text, and assign the duration of the input text buffers to their output buffers.
    Translated or synthesised audio might be longer than the original inputs, so this makes sure the audio will be sped up to
    fit the original duration, so it doesn’t go out of sync.

-   souphttpsrc: Add the notion of "retry-backoff" and retry on 503 (service unavailable) and 500 (internal server error) http
    errors.

-   taginject now modifies existing tag events of the selected scope, with the new "merge-mode" property allowing finer control.

-   timecodestamper gained a new running-time source mode that converts the buffer running time into timecodes.

-   playbin3, uridecodebin3, parsebin

    -   lots of stream-collection handling and stability/reliability fixes
    -   error/warning/info message now include the URI (if known) and stream-id
    -   missing plugin messages include the stream-id

-   videocodectestsink gained support for GBR_10LE, GRAY8 and GRAY10_LE{16,32} pixel formats

-   videoflip gained support for the Y444_16LE and Y444_16BE pixel formats

-   videoconvertscale:

    -   Handle pixel aspect ratios with large numerators or denominators
    -   Explicitly handle the overlaycomposition meta caps feature, so it doesn’t get dropped unnecessarily

-   waylandsink prefers DMABuf over system memory now

-   x264enc has a new "nal-hrd" property to make the encoder signal HRD information, which is required for Blu-ray streams,
    television broadcast and a few other specialist areas. It can also be used to force true CBR, and will cause the encoder to
    output null padding packets.

-   zbar: add support for binary mode and getting symbols as raw bytes instead of a text string.

## Plugin and library moves

-   macOS: atdec was moved from the applemedia plugin in -bad to the osxaudio plugin in -good, in order to be able to share
    audio-related helper methods.

## Plugin and element removals

-   None in this cycle

## Miscellaneous API additions

### GStreamer Core

-   gst_meta_api_type_set_params_aggregator() allows setting an GstAllocationMetaParamsAggregator function for metas, which has
    been implemented for GstVideoMeta and is used to aggregate alignment requirements of multiple tee branches.

-   gst_debug_print_object() and gst_debug_print_segment() have been made public API. The can be used to easily get string
    representations of various types of (mini)objects in custom log handlers.

-   Added gst_aggregator_push_src_event(), so subclasses don’t just push events directly onto the source pad bypassing the base
    class without giving it the chance to send out any pending serialised events that should be sent out before.

-   GstMessage has gained APIs to generically add “details” to messages:

    -   gst_message_set_details()
    -   gst_message_get_details()
    -   gst_message_writable_details()
    -   gst_message_parse_error_writable_details()
    -   gst_message_parse_warning_writable_details()
    -   gst_message_parse_info_writable_details() This is used in uridecodebin3 to add the triggering URI to any INFO, WARNING
        or ERROR messages posted on the bus, and in decodebin3 to add the stream ID to any missing plugin messages posted on the
        bus.

-   gst_util_floor_log2() returns smallest integral value not bigger than log2(v).

-   gst_util_fraction_multiply_int64() is a 64-bit variant of gst_util_fraction_multiply().

#### GstIdStr replaces GQuark in structure and caps APIs

-   GQuarks are integer identifiers for strings that are inserted into a global hash table, allowing in theory for cheap
    equality comparisons. In GStreamer they have been used to represent GstStructure names and field names. The problem is that
    these strings once added to the global string table can never be freed again, which can lead to ever-increasing memory usage
    for processes where such name identifiers are created based on external input or on locally-created random identifiers.

-   GstIdStr is a new data structure made to replace quarks in our APIs. It can hold a short string inline, a static string, or
    a reference to a heap-allocated longer string, and allows for cheap storage of short strings and cheap comparisons. It does
    not involve access to a global hash table protected by a global lock, and as most strings used in GStreamer structures are
    very short, it is actually more performant than quarks in many scenarios.

-   GQuark-using APIs in GstStructure or GstCaps have been deprecated and equivalent APIs using GstIdStr have been added
    instead. For more details about this change watch Sebastian’s GStreamer Conference presentation “GQuark in GStreamer
    structures - what nonsense!”.

-   Most applications and plugins will have been using the plain string-based APIs which are not affected by this change.

#### GstVecDeque

-   Moved GstQueueArray as GstVecDeque into core for use in GstBus, the ringbuffer logger and in GstBufferPool, where an overly
    complicated signaling mechanism using GstAtomicQueue and GstPoll was replaced with GstVecDeque and a simple mutex/cond.

-   GstQueueArray in libgstbase was deprecated in favour of GstVecDeque.

-   GstAtomicQueue will be deprecated once all users in GStreamer have been moved over to GstVecDeque.

### Audio Library

-   Added gst_audio_reorder_channels_with_reorder_map() which allows reordering the samples with a pre-calculated reorder map
    instead of re-calculating the reorder map every time.

-   Add top-surround-left and top-surround-right channel positions

-   GstAudioConverter now supports more numerical types for the mix matrix, namely double, int, int64, uint, and uint64 in
    addition to plain floats.

### Plugins Base Utils Library

-   New AV1 caps utility functions for AV1 Codec Configuration Record codec_data handling

-   The GstEncodingProfile (de)serialization functions are now public

-   GstEncodingProfile gained a way to specify a factory-name when specifying caps. In some cases you want to ensure that a
    specific element factory is used while requiring some specific caps, but this was not possible so far. You can now do
    e.g. qtmux:video/x-prores,variant=standard|factory-name=avenc_prores_ks to ensure that the avenc_prores_ks factory is used
    to produce the variant of prores video stream.

### Tag Library

-   EXIF handling now support the CAPTURING_LIGHT_SOURCE tag

-   Vorbis tag handling gained support for the LYRICS tag

### Video Library and OpenGL Library

-   gst_video_convert_sample(), gst_video_convert_sample_async() gained support for D3D12 conversion.

-   GstVideoEncoder: gst_video_encoder_release_frame() and gst_video_encoder_drop_frame() have been made available as public
    API.

-   Navigation: gained mouse double click event support

-   Video element QoS handling was improved so as to not overshoot the QoS earliest time by a factor of 2. This was fixed in the
    video decoder, encoder, aggregator and audiovisualizer base classes, as well as in the adaptivedemux, deinterlace,
    monoscope, shapewipe, and (old) videomixer elements.

-   GstVideoConverter gained fast paths for v210 to/from I420_10 / I422_10

-   New gst_video_dma_drm_format_from_gst_format() helper function that converts a video format into a dma drm fourcc / modifier
    pair, plus gst_video_dma_drm_format_to_gst_format() which will do the reverse.

-   In the same vein gst_gl_dma_buf_transform_gst_formats_to_drm_formats() and
    gst_gl_dma_buf_transform_drm_formats_to_gst_formats() have been added to the GStreamer OpenGL support library.

-   GLDisplay/EGL: Add API (gst_gl_display_egl_set_foreign()) for overriding foreign-ness of the EGLDisplay in order to control
    whether GStreamer should call eglTerminate() or not.

-   Additional DMA DRM format definitions/mappings:

    -   NV15, NV20, NV30
    -   NV12_16L32S, MT2110T, MT2110R as used on Meditek SoCs
    -   NV12_10LE40
    -   RGB15, GRAY8, GRAY16_LE, GRAY16_BE
    -   plus support for big endian DRM formats and DRM vendor modifiers

New Raw Video Formats

-   Packed 4:2:2 YUV with 16 bits per channel:
    -   Y216_LE, Y216_BE
-   Packed 4:4:4:4 YUV with alpha, with 16 bits per channel:
    -   Y416_LE, Y416_BE
-   10-bit grayscale, packed into 16-bit words with left padding:
    -   GRAY10_LE16

### GstPlay Library

-   Add stream-id based selection of streams to better match playbin3’s API:
    -   Add accessors for the stream ID and selection API based on the stream ID:
        -   gst_play_stream_info_get_stream_id()
        -   gst_play_set_audio_track_id()
        -   gst_play_set_video_track_id()
        -   gst_play_set_subtitle_track_id()
        -   gst_play_set_track_ids()
    -   Deprecate the old index-based APIs:
        -   gst_play_stream_info_get_index()
        -   gst_play_set_audio_track()
        -   gst_play_set_video_track()
        -   gst_play_set_subtitle_track()
    -   Remove old playbin support
    -   Implement the track enable API based on stream selection
-   Distinguish missing plugin errors and include more details (uri, and stream-id if available) in error/warning messages:
    -   gst_play_message_get_uri()
    -   gst_play_message_get_stream_id()
    -   GST_PLAY_ERROR_MISSING_PLUGIN
    -   gst_play_message_parse_error_missing_plugin()
    -   gst_play_message_parse_warning_missing_plugin()
-   Improve play message API inconsistencies:
    -   Consistently name parse functions according to their message type:
        -   gst_play_message_parse_duration_changed()
        -   gst_play_message_parse_buffering()
    -   Deprecate the misnamed functions:
        -   gst_play_message_parse_duration_updated()
        -   gst_play_message_parse_buffering_percent()
    -   Add missing parse functions:
        -   gst_play_message_parse_uri_loaded()
        -   gst_play_message_parse_seek_done()
-   Support disabling the selected track at startup

## Miscellaneous performance, latency and memory optimisations

-   dvdspu: use multiple minimal sized PGS overlay rectangles instead of a single large one to minimise the total blitting
    surface in case of disjointed rectangles.

-   video-frame: reduce number of memcpy() calls on frame copies if possible

-   video-converter: added fast path conversions between v210 and I420_10 / I422_10

-   As always there have been plenty of performance, latency and memory optimisations all over the place.

## Miscellaneous other changes and enhancements

-   netclientclock: now also emits the clock synced signal when corrupted to signal that sync has been lost.

-   GstValue, GstStructure: can now (de)serialize string arrays (G_TYPE_STRV)

## Tracing framework and debugging improvements

-   dot files (pipeline graph dumps) are now written to disk atomically

-   tracing: add hooks for gst_pad_send_event_unchecked() and GstMemory init/free

-   tracers: Simplify params handling using GstStructure and object properties and move tracers over to property-based
    configuration (leaks, latency).

-   textoverlay, clockoverlay, timeoverlay: new "response-time-compensation" property that makes the element render the text or
    timestamp twice: Once in the normal location and once in a different sequentially predictable location for every frame. This
    is useful when measuring video latency by taking a photo with a camera of two screens showing the test video overlayed with
    timeoverlay or clockoverlay. In these cases, you will often see ghosting if the display’s pixel response time is not great,
    which makes it difficult to discern what the current timestamp being shown is. Rendering in a different location for each
    frame makes it easy to tell what the latest timestamp is. In addition, you can also use the fade-time of the previous frame
    to measure with sub-framerate accuracy when the photo was taken, not just clamped to the framerate, giving you a higher
    precision latency value.

New tracers

-   memory-tracer: New tracer that can track memory usage over time

-   pad-push-timings: New tracer for tracing pad push timings

-   pcap-writer: New tracer that can store the buffers flowing through a pad as PCAP file

Dot tracer/viewer

-   New dots tracer that simplifies the pipeline visualization workflow:
    -   Automatically configures dot file directory and cleanup
    -   Integrates with the pipeline-snapshotS tracer to allow dumping pipeline on demand from the gst-dots-viewer web interface
    -   Uses GST_DEBUG_DUMP_DOT_DIR or falls back to $XDG_CACHE_HOME/gstreamer-dots
-   New gst-dots-viewer web tool for real-time pipeline visualization
    -   Provides interface to browse and visualize pipeline dot files
    -   Features on-demand pipeline snapshots via “Dump Pipelines” button
    -   WebSocket integration for live updates
    -   Uses GST_DEBUG_DUMP_DOT_DIR or falls back to $XDG_CACHE_HOME/gstreamer-dots
-   Simple usage:
    -   gst-dots-viewer (starts server)
    -   GST_TRACERS=dots gst-launch-1.0 videotestsrc ! autovideosink (runs with tracer)
    -   View at http://localhost:3000

Debug logging system improvements

-   Nothing major in this cycle.

## Tools

-   gst-inspect-1.0 documents tracer properties now and shows element flags

-   gst-launch-1.0 will show error messages posted during pipeline construction

## GStreamer FFmpeg wrapper

-   Add support for H.266/VVC decoder

-   Add mappings for the Hap video codec, the Quite OK Image codec (QOI) and the M101 Matrox uncompressed SD video codec.

-   Don’t register elements for which we have no caps and which were non-functional as a result (showing unknown/unknown caps).

-   The S302M audio encoder now supports up to 8 channels.

-   Various tag handling improvements in the avdemux wrappers, especially when there are both upstream tags and additional local
    tags.

-   Support for 10-bit grayscale formats

## GStreamer RTSP server

-   GstRTSPOnvifMediaFactoryClass gained a ::create_backchannel_stream() vfunc. This allows subclasses to delay creation of the
    backchannel to later in the sequence, which is useful in scenarios where the RTSP server acts as a relay and the supported
    caps depend on the upstream camera, for example.

-   The ONVIF backchannel example now features support for different codecs, including AAC.

## VA-API integration

VA plugin

-   New VA elements:

    -   H.266 / VVC video decoder
    -   VP8 video encoder
    -   JPEG encoder
    -   VP9 + VP8 alpha decodebin

    Remember that the availability of these elements depends on your platform and driver.

-   There are a lot of improvements and bug fixes, to hightlight some of them:

    -   Improved B pyramid mode for both H264 and HEVC encoding when reference frame count exceeds 2, optimizing pyramid level
        handling.
    -   Enabled ICQ and QVBR modes for several encoders, including H264, H265, VP9 and AV1.
    -   Updated rate control features by setting the quality factor parameter, while improving bitrate change handling.
    -   Improved VP9 encoder’s ability to avoid reopening or renegotiating encoder settings when parameters remain stable.
    -   Added functionality to adjust the trellis parameter in encoders.
    -   Optimize encoders throughput with the introduction of output delay.
    -   Added support for new interpolation methods for scaling and improvements for handling interlace modes.

GStreamer-VAAPI is now deprecated

-   gstreamer-vaapi has been deprecated and is no longer actively maintained. Users who rely on gstreamer-vaapi are encouraged
    to migrate to the va plugin’s elements at the earliest opportunity.

-   vaapi*enc encoders have been demoted to a rank of None, so will no longer be autoplugged in encodebin. They have also no
    longer advertise dmabuf caps or unusual pixel formats on their input pad template caps.

## GStreamer Video4Linux2 support

-   Implemented DMA_DRM caps negotiation

-   Framerate negotiation improvements

-   Support for P010 and BGR10A2_LE pixel formats

-   The V4L2 stateless decoders now support inter-frame resolution changes for AV1 and VP9

-   The V4L2 stateful encoders can now handle dynamic frame rates (0/1), and colorimetry negotiation was also improved.

## GStreamer Editing Services and NLE

-   Added support for reverse playback with a new reverse property on nlesource which is exposed child property on GESClip

-   Input channels reordering for flexible audio channel mapping

-   Added support for transition in the ges-launch-1.0 timeline description format

-   Added support for GstContext sharing in GESTimeline

-   Added basic support for duplicated children property in GESTimelineElement

-   validate: Add an action type to group clips

## GStreamer validate

-   Added new action types:

    -   start-http-server: Start a new instance of our HTTP test server
    -   http-requests: Send an HTTP request to a server, designed to work with our test http server

-   HTTP server control endpoints to allow scenarios to control the server behavior, allowing simulating server failures from
    tests

-   Improved the select-streams action type, adding support for selecting the same streams several times

-   Added support for forcing monitoring of all pipelines in validatetest files

-   Enhanced support for expected Error messages on the bus

-   Added ways to retrieve HTTP server port in .validatetest files

-   Added support for lldb in the gst-validate-launcher

## GStreamer Python Bindings

gst-python is an extension of the regular GStreamer Python bindings based on gobject-introspection information and PyGObject,
and provides “syntactic sugar” in form of overrides for various GStreamer APIs that makes them easier to use in Python and more
pythonic; as well as support for APIs that aren’t available through the regular gobject-introspection based bindings, such as
e.g. GStreamer’s fundamental GLib types such as Gst.Fraction, Gst.IntRange etc.

-   The python Meson build option has been renamed to python-exe (and will yield to the monorepo build option of the same name
    if set, in a monorepo build context).

-   Added an iterator for AnalyticsRelationMeta

-   Implement __eq__ for Mtd classes

-   Various build fixes and Windows-related fixes.

## GStreamer C# Bindings

-   The C# bindings have been updated for the latest GStreamer 1.26 API

## GStreamer Rust Bindings and Rust Plugins

The GStreamer Rust bindings and plugins are released separately with a different release cadence that’s tied to the gtk-rs
release cycle.

The latest release of the bindings (0.23) has already been updated for the new GStreamer 1.26 APIs, and works with any GStreamer
version starting at 1.14.

gst-plugins-rs, the module containing GStreamer plugins written in Rust, has also seen lots of activity with many new elements
and plugins. The GStreamer 1.26 binaries will be tracking the main branch of gst-plugins-rs for starters and then track the 0.14
branch once that has been released (around summer 2025). After that, fixes from newer versions will be backported as needed to
the 0.14 branch for future 1.26.x bugfix releases.

Rust plugins can be used from any programming language. To applications they look just like a plugin written in C or C++.

### New Rust elements

-   awstranscriber2, awstranslate: New elements around the AWS transcription and translation services.

-   cea708mux: New element that allows to mux multiple CEA708 services into a single stream.

-   cdpserviceinject: New element for injecting a CDP service descriptor into closed caption CDP packets

-   cea708overlay: New element for overlaying CEA608 / CEA708 closed captions over video streams.

-   gopbuffer: New element that can buffer a minimum duration of data delimited by discrete GOPs (Group of Picture)

-   hlscmafsink, hlssink3: New single-variant HLS sink elements that can output CMAF (fMP4) or MPEG-TS fragments.

-   hlsmultivariantsink: New sink element that can output an HLS stream with multiple variants

-   mpegtslivesrc: New source element that can wrap a live MPEG-TS source (e.g. SRT or UDP source) and provides a clock based on
    the PCR.

-   onvifmetadataextractor: New element that can extract ONVIF metadata from GstMetas into a separate stream

-   originalbuffer: New plugin with originalbuffersave / originalbufferrestore elements that allow saving an original buffer,
    modifying it for analytics, and then restoring the original buffer content while keeping any additional metadata that was
    added.

-   polly: New element around the AWS text-to-speech polly services

-   quinn: New plugin that contains various QUIC-based elements for working with raw QUIC streams, RTP-over-QUIC (RoQ) and
    WebTransport.

-   relationmeta: New plugin with elements converting between GstRelationMeta and ONVIF XML metadata.

-   New Rust RTP payloaders and depayloaders for AC3, AMR, JPEG, KLV, MPEG-TS (MP2T), MPEG-4 (MP4A, MP4G), Opus, PCMU (uLaw),
    PCMA (aLaw), VP8, VP9.

-   New rtpbin2 based on rtprecv / rtpsend elements

-   speechmatics: New transcriber / speech-to-text and translation element

-   New spotifylyricssrc element for grabbing lyrics from Spotify.

-   streamgrouper: New element that takes any number of streams as input and adjusts their stream-start events in such a way
    that they all belong to the same stream group.

-   translationbin: Helper bin around translation elements, similar to the already existing transcriberbin for transcriptions.

-   tttocea708: New element for converting timed-text to CEA708 closed captions

-   A VVdeC-based H.266 decoder element was added to the Rust plugins, based on the Fraunhofer Versatile Video Decoder library.

For a full list of changes in the Rust plugins see the gst-plugins-rs ChangeLog between versions 0.12 (shipped with GStreamer
1.24) and 0.14.x (shipped with GStreamer 1.26).

Note that at the time of GStreamer 1.26.0 gst-plugins-rs 0.14 was not released yet and the git main branch was included instead
(see above). As such, the ChangeLog also did not contain the changes between the latest 0.13 release and 0.14.0.

## Build and Dependencies

-   Meson >= 1.4 is now required for all modules

-   liborc >= 0.4.41 is strongly recommended

-   libnice >= 0.1.22 is strongly recommended, as it is required for WebRTC ICE consent freshness (RFC 7675).

-   The ASIO plugin dropped its external SDK header dependency, so it can always be built and shipped more easily.

-   Require tinyalsa >= 1.1.0 when building the tinyalsa plugin

-   The srtp plugin now requires libsrtp2, support for libsrtp1 was dropped.

Monorepo build

-   The FFmpeg subproject wrap was updated to 7.1

-   Many other wrap updates

gstreamer-full

-   No major changes

Development environment

-   Local pre-commit checks via git hooks have been moved over to pre-commit, including the code indentation check.

-   Code indentation checking no longer relies on a locally installed copy of GNU indent (which had different outcomes depending
    on the exact version installed). Instead pre-commit will automatically install the gst-indent-1.0 indentation tool through
    pip, which also works on Windows and macOS.

-   A pre-commit hook has been added to check documentation cache updates and since tags.

-   Many meson wrap updates, including to FFmpeg 7.1

-   The uninstalled development environment should work better on macOS now, also in combination with homebrew (e.g. when
    libsoup comes from homebrew).

-   New python-exe Meson build option to override the target Python installation to use. This will be picked up by the
    gst-python and gst-editing-sevices subprojects.

## Platform-specific changes and improvements

### Android

-   The recommended mechanism to build Android apps has changed from Android.mk to CMake-in-Gradle using
    FindGStreamerMobile.cmake. Android.mk support has been deprecated and will be removed in the next stable release. For more
    information, see below, in the Cerbero section.
-   More H.264/H.265 profiles and levels have been added to the androidmedia hardware-accelerated video encoder and decoder
    elements, along with mappings for a number of additional pixel formats for P010, packed 4:2:0 variants and RGBA layouts,
    which fixes problems with android decoders refusing to output raw video frames with decoders that announce support for these
    common pixel formats and only allow the ‘hardware surfaces output’ path.

### Apple macOS and iOS

-   atenc: added an Apple AAC audio encoder

-   atdec can now decode audio with more than two channels

-   vtenc has received various bug fixes as well as a number of new features:

    -   Support for HEVC with alpha encoding via the new vtenc_h265a element
    -   additional rate control options for constant bitrate encoding (only supported on macOS 13.0+ and iOS 16.0+ on Apple
        Silicon), setting data rate limits, and emulating CBR mode via data rate limits where CBR is not supported.
    -   HLG color transfer support
    -   new "max-frame-delay" property (for ProRes)

-   Better macOS support for gst-validate tools which now use gst_macos_main() and support lldb

-   The osxaudio device provider exposes more properties including a unique id

-   osxaudio will automatically set up AVAudioSession on iOS and always expose the maximum number of channels a device supports
    with an unpositioned layout.

-   The monorepo development environment should work better on macOS now

-   CMake apps that build macOS and iOS apps can consume GStreamer more easily now, using FindGStreamer.cmake or
    FindGStreamerMobile.cmake respectively.

-   In the near future, CMake in Xcode will be the preferred way of building the iOS tutorials. See below, in the Cerbero
    section.

### Windows

-   webview2src: new Microsoft WebView2 based web browser source element

-   The mediafoundation plugin can also be built with MinGW now.

-   The GTK3 plugin has gained support for OpenGL/WGL on Windows

-   qsv: Add support for d3d12 interop in encoder, via D3D11 textures

### Cerbero

Cerbero is a meta build system used to build GStreamer plus dependencies on platforms where dependencies are not readily
available, such as Windows, Android, iOS, and macOS.

General improvements

-   New features:

    -   Python bindings support has been re-introduced and now supports Linux, Windows (MSVC) and macOS. Simply downloading the
        official binaries and setting PYTHONPATH to the appropriate directory is sufficient.

        -   This should make it easier for macOS and Windows users to use Python libraries, frameworks, and projects that use
            GStreamer such as Pitivi and gst-python-ml.

    -   Introspection support has been re-introduced on Linux, Windows (MSVC), and macOS.

    -   New variants assert and checks to disable GLib assertions and runtime checks for performance reasons. Please note that
        these are not recommended because they have significant behavioural side-effects, make debugging harder, and should only
        be enabled when targeting extremely resource-constrained platforms.

-   API/ABI changes:

    -   Libsoup has been upgraded from 2.74 to 3.6, which is an API and ABI breaking change. The soup and adaptivedemux2 plugins
        are unchanged, but your applications may need updating since libsoup-2.4 and libsoup-3.0 cannot co-exist in the same
        process.

    -   OpenSSL has been updated from 1.1.1 to 3.4, which is an ABI and API breaking change. Plugins are unchanged, but your
        applications may need updating.

-   Plugins added:

    -   The svt-av1 plugin is now shipped in the binary releases for all platforms.

    -   The svt-jpeg-xs plugin is now shipped in the binary releases for all platforms.

    -   The x265 plugin is now shipped in the binary releases for all platforms.

    -   All gst-plugins-rs elements are now shipped in the binary releases for all platforms, except those that have C/C++
        system-deps like GTK4. For a full list, see the Rust section above.

-   Plugins changed:

    -   The rsvg plugin now uses librsvg written in Rust. The only side-effects of this should be better SVG rendering and
        slightly larger plugin size.

    -   The webrtc Rust plugin now also supports aws and livekit integrations .

-   Plugins removed:

    -   webrtc-audio-processing has been updated to 2.0, which means the isac plugin is no longer shipped.

-   Development improvements:

    -   Support for the shell command has been added to cross-macos-universal, since the prefix is executable despite being a
        cross-compile target

    -   More recipes have been ported away from Autotools to Meson and CMake, speeding up the build and increasing platform
        support.

#### macOS

-   Python bindings support on macOS only supports using the Xcode-provided Python 3

-   MoltenVK support in the applemedia plugin now also works on arm64 when doing a cross-universal build.

#### iOS

-   CMake inside Xcode will soon be the recommended way to consume GStreamer when building iOS apps, similar to Android apps.

    -   FindGStreamerMobile.cmake is the recommended way to consume GStreamer now

    -   Tutorials and examples still use Xcode project files, but CMake support will be the active focus going forward

#### Windows

-   The minimum supported OS version is now Windows 10.

    -   GStreamer itself can still be built for an older Windows, so if your project is majorly impacted by this, please open an
        issue with details.

-   The Windows MSI installers are now based on WiX v5, with several improvements including a much faster MSI creation process,
    improved naming in Add/Remove Programs, and more.

    -   Windows installer packages: Starting with 1.26, due to security reasons, the default installation directory has changed
        from C:\gstreamer to the Program Files folder, e.g. C:\Program Files (x86)\gstreamer for the 32-bit package on 64-bit
        Windows. If you upgrade from 1.24 or older versions, the 1.26 installers will NOT keep using the existing folder.
        Nevertheless if you were using C:\gstreamer we strongly recommend you double-check the install location.

    -   Note for MSI packagers: Starting with 1.26, the installers were ported to WiX 5.0. As part of this, the property for
        setting the installation directory has been changed to INSTALLDIR, and it now requires a full path to the desired
        directory, e.g. C:\gstreamer instead of C:\.

    -   Cross-MinGW build no longer supports the creation of MSIs. Please use tarballs.

-   MinGW:

    -   MinGW toolchain has been updated from GCC 8.2 → 14.2 and MinGW 6.0 → 12.0

    -   The mediafoundation plugin is also shipped in the MinGW packages now.

    -   The d3d12 plugin is also shipped in the MinGW packages now.

    -   Rust support has been enabled on MinGW 64-bit. Rust support cannot work on 32-bit MinGW due to differences in exception
        handling between our 32-bit MinGW toolchain and that used by the Rust project

-   The asio plugin is shipped now, since it no longer has a build-time dependency on the ASIO SDK.

-   The new plugin webview2 is shipped with MSVC. It requires the relevant component shipped with Windows.

#### Linux

-   Preliminary support for Alma Linux has been added.

-   RHEL distro support has been improved.

-   Cerbero CI now tests the build on Ubuntu 24.04 LTS.

-   curl is used for downloading sources on Fedora instead of wget, since they have moved to wget2 despite show-stopper
    regressions such as returning a success error code on download failure.

#### Android

-   CMake inside Gradle is now the recommended way to consume GStreamer when building Android apps

    -   FindGStreamerMobile.cmake is the recommended way to consume GStreamer now

    -   1.26 will support both CMake and Make inside Gradle, but the Make support will likely be removed in 1.28

    -   Documentation updates are still a work-in-progress, help is appreciated

-   Android tutorials and examples are now built with gradle + cmake instead of gradle + make on the CI

## Documentation improvements

-   Tracer objects information is now included in the documentation

## Possibly Breaking Changes

-   qroverlay: the "pixel-size" property has been removed in favour of a new "size" property with slightly different semantics,
    where the size of the square is expressed in percent of the smallest of width and height.

-   svtav1enc: The SVT-AV1 3.0.0 API exposes a different mechanism to configure the level of parallelism when encoding, which
    has been exposed as a new "level-of-parallelism" property. The old "logical-processors" property is no longer functional if
    the plugin has been compiled against the new API, which might affect encoder performance if application code setting it is
    not updated.

-   udpsrc: now disables allocated port reuse for unicast to avoid unexpected side-effects of SO_REUSEADDR where the kernel
    allocates the same listening port for multiple udpsrc.

-   uridecodebin3 remove non-functional "source" property that doesn’t make sense and always returned NULL anyway.

## Known Issues

-   GstBuffer now uses C11 atomics for 64 bit atomic operations if available, which may require linking to libatomic on some
    systems, but this is not done automatically yet, see issue #4177.

## Statistics

-   4496 commits

-   2203 Merge requests merged

-   794 Issues closed

-   215+ Contributors

-   ~33% of all commits and Merge Requests were in Rust modules/code

-   4950 files changed

-   515252 lines added

-   201503 lines deleted

-   313749 lines added (net)

Contributors

Aaron Boxer, Adrian Perez de Castro, Adrien De Coninck, Alan Coopersmith, Albert Sjolund, Alexander Slobodeniuk, Alex Ashley,
Alicia Boya García, Andoni Morales Alastruey, Andreas Wittmann, Andrew Yooeun Chun, Angelo Verlain, Aniket Hande, Antonio
Larrosa, Antonio Morales, Armin Begovic, Arnaud Vrac, Artem Martus, Arun Raghavan, Benjamin Gaignard, Benjamin Gräf, Bill
Nottingham, Brad Hards, Brad Reitmeyer, Branko Subasic, Carlo Caione, Carlos Bentzen, Carlos Falgueras García, cdelguercio, Chao
Guo, Cheah, Cheung Yik Pang, chitao1234, Chris Bainbridge, Chris Spencer, Chris Spoelstra, Christian Meissl, Christopher Degawa,
Chun-wei Fan, Colin Kinloch, Corentin Damman, Daniel Morin, Daniel Pendse, Daniel Stone, Dan Yeaw, Dave Lucia, David Rosca, Dean
Zhang (张安迪), Denis Yuji Shimizu, Detlev Casanova, Devon Sookhoo, Diego Nieto, Dongyun Seo, dukesook, Edward Hervey, eipachte,
Eli Mallon, Elizabeth Figura, Elliot Chen, Emil Ljungdahl, Emil Pettersson, eri, F. Duncanh, Fotis Xenakis, Francisco Javier
Velázquez-García, Francis Quiers, François Laignel, George Hazan, Glyn Davies, Guillaume Desmottes, Guillermo E. Martinez,
Haihua Hu, Håvard Graff, He Junyan, Hosang Lee, Hou Qi, Hugues Fruchet, Hyunwoo, iodar, jadarve, Jakub Adam, Jakub Vaněk, James
Cowgill, James Oliver, Jan Alexander Steffens (heftig), Jan Schmidt, Jeffery Wilson, Jendrik Weise, Jerome Colle, Jesper Jensen,
Jimmy Ohn, Jochen Henneberg, Johan Sternerup, Jonas K Danielsson, Jonas Rebmann, Jordan Petridis, Jordan Petridіs, Jordan
Yelloz, Jorge Zapata, Joshua Breeden, Julian Bouzas, Jurijs Satcs, Kévin Commaille, Kevin Wang, Khem Raj, kingosticks, Leonardo
Salvatore, L. E. Segovia, Liam, Lim, Loïc Le Page, Loïc Yhuel, Lyra McMillan, Maksym Khomenko, Marc-André Lureau, Marek Olejnik,
Marek Vasut, Marianna Smidth Buschle, Marijn Suijten, Mark-André Schadow, Mark Nauwelaerts, Markus Ebner, Martin Nordholts, Mart
Raudsepp, Mathieu Duponchelle, Matthew Waters, Maxim P. DEMENTYEV, Max Romanov, Mengkejiergeli Ba, Michael Grzeschik, Michael
Olbrich, Michael Scherle, Michael Tretter, Michiel Westerbeek, Mikhail Rudenko, Nacho Garcia, Nick Steel, Nicolas Dufresne,
Niklas Jang, Nirbheek Chauhan, Ognyan Tonchev, Olivier Crête, Oskar Fiedot, Pablo García, Pablo Sun, Patricia Muscalu, Paweł
Kotiuk, Peter Kjellerstedt, Peter Stensson, pgarciasancho, Philippe Normand, Philipp Zabel, Piotr Brzeziński, Qian Hu (胡骞),
Rafael Caricio, Randy Li (ayaka), Rares Branici, Ray Tiley, Robert Ayrapetyan, Robert Guziolowski, Robert Mader, Roberto Viola,
Robert Rosengren, RSWilli,Ruben González, Ruijing Dong, Sachin Gadag, Sam James, Samuel Thibault, Sanchayan Maity, Scott Moreau,
Sebastian Dröge, Sebastian Gross, Sebastien Cote, Sergey Krivohatskiy, Sergey Radionov, Seungha Yang, Seungmin Kim, Shengqi Yu,
Sid Sethupathi, Silvio Lazzeretti, Simonas Kazlauskas, Stefan Riedmüller, Stéphane Cerveau, Tamas Levai, Taruntej Kanakamalla,
Théo Maillart, Thibault Saunier, Thomas Goodwin, Thomas Klausner, Tihran Katolikian, Tim Blechmann, Tim-Philipp Müller, Tjitte
de Wert, Tomas Granath, Tomáš Polomský, tomaszmi, Tom Schuring, U. Artie Eoff, valadaptive, Víctor Manuel Jáquez Leal, Vivia
Nikolaidou, W. Bartel, Weijian Pan, William Wedler, Will Miller, Wim Taymans, Wojciech Kapsa, Xavier Claessens, Xi Ruoyao,
Xizhen, Yaakov Selkowitz, Yacine Bandou, Zach van Rijn, Zeno Endemann, Zhao, Zhong Hongcheng,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

Stable 1.26 branch

After the 1.26.0 release there will be several 1.26.x bug-fix releases which will contain bug fixes which have been deemed
suitable for a stable branch, but no new features or intrusive changes will be added to a bug-fix release usually. The 1.26.x
bug-fix releases will be made from the git 1.26 branch, which is a stable release series branch.

1.26.1

The first 1.26 bug-fix release (1.26.1) was released on 24 April 2025.

This release only contains bugfixes and security fixes and it should be safe to update from 1.26.0.

Highlighted bugfixes in 1.26.1

-   awstranslate and speechmatics plugin improvements
-   decodebin3 fixes and urisourcebin/playbin3 stability improvements
-   Closed captions: CEA-708 generation and muxing fixes, and H.264/H.265 caption extractor fixes
-   dav1d AV1 decoder: RGB support, plus colorimetry, renegotiation and buffer pool handling fixes
-   Fix regression when rendering VP9 with alpha
-   H.265 decoder base class and caption inserter SPS/PPS handling fixes
-   hlssink3 and hlsmultivariantsink feature enhancements
-   Matroska v4 support in muxer, seeking fixes in demuxer
-   macOS: framerate guessing for cameras or capture devices where the OS reports silly framerates
-   MP4 demuxer uncompressed video handling improvements and sample table handling fixes
-   oggdemux: seeking improvements in streaming mode
-   unixfdsrc: fix gst_memory_resize warnings
-   Plugin loader fixes, especially for Windows
-   QML6 GL source renegotiation fixes
-   RTP and RTSP stability fixes
-   Thread-safety improvements for the Media Source Extension (MSE) library
-   v4l2videodec: fix A/V sync issues after decoding errors
-   Various improvements and fixes for the fragmented and non-fragmented MP4 muxers
-   Video encoder base class segment and buffer timestamp handling fixes
-   Video time code support for 119.88 fps and drop-frames-related conversion fixes
-   WebRTC: Retransmission entry creation fixes and better audio level header extension compatibility
-   YUV4MPEG encoder improvments
-   dots-viewer: make work locally without network access
-   gst-python: fix compatibility with PyGObject >= 3.52.0
-   Cerbero: recipe updates, compatibility fixes for Python < 3.10; Windows Android cross-build improvements
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   Correctly handle whitespace paths when executing gst-plugin-scanner
-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup
-   cmake: Fix PKG_CONFIG_PATH formatting for Windows cross-builds
-   macos: Move macos function documentation to the .h so the introspection has the information
-   meson.build: test for and link against libatomic if it exists
-   pluginloader-win32: Fix helper executable path under devenv
-   pluginloader: fix pending_plugins Glist use-after-free issue
-   unixfdsrc: Complains about resize of memory area
-   tracers: dots: fix debug log

gst-plugins-base

-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup
-   alsadeviceprovider: Fix leak of Alsa longname
-   audioaggregator: fix error added in !8416 when chaining up
-   audiobasesink: Fix custom slaving driftsamples calculation and add custom audio clock slaving callback example
-   decodebin3: Don’t avoid parsebin even if we have a matching decoder
-   decodebin3: Doesn’t plug parsebin for AAC from tsdemux
-   gl: eglimage: warn the reason of export failure
-   glcolorconvert: fix YUVA<->RGBA conversions
-   glcolorconvert: regression when rendering alpha vp9
-   gldownload: Unref glcontext after usage
-   meson.build: test for and link against libatomic if it exists
-   oggdemux: Don’t push new packets if there is a pending seek
-   urisourcebin: Make parsebin activation more reliable
-   urisourcebin: deadlock between parsebin and typefind
-   videoencoder: Use the correct segment and buffer timestamp in the chain function
-   videotimecode: Fix conversion of timecode to datetime with drop-frame timecodes and handle 119.88 fps correctly in all
    places

gst-plugins-good

-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup
-   gst-plugins-good: Matroska mux v4 support
-   matroska-demux: Prevent corrupt cluster duplication
-   qml6glsrc: update buffer pool on renegotiation
-   qt6: Add a missing newline in unsupported platform message
-   qtdemux: Fix stsc size check in qtdemux_merge_sample_table()
-   qtdemux: Next Iteration Of Uncompressed MP4 Decoder
-   qtdemux: unref simple caps after use
-   rtspsrc: Do not emit signal ‘no-more-pads’ too early
-   rtspsrc: Don’t error out on not-linked too early
-   rtpsession: Do not push events while holding SESSION_LOCK
-   rtpsession: deadlock when gst_rtp_session_send_rtcp () is forwarding eos
-   v4l2: drop frame for frames that cannot be decoded
-   v4l2videodec: AV unsync for streams with many frames that cannot be decoded
-   v4l2object: fix memory leak
-   v4l2object: fix type mismatch when ioctl takes int
-   y4menc: fix Y41B format
-   y4menc: handle frames with GstVideoMeta

gst-plugins-bad

-   Add missing Requires in pkg-config
-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup
-   Update docs
-   aja: Use the correct location of the AJA NTV2 SDK in the docs
-   alphacombine: De-couple flush-start/stop events handling
-   alphadecodebin: use a multiqueue instead of a couple of queues
-   avfvideosrc: Guess reasonable framerate values for some 3rd party devices
-   codecalpha: name both queues
-   d3d12converter: Fix cropping when automatic mipmap is enabled
-   dashsink: Make sure to use a non-NULL pad name when requesting a pad from splitmuxsink
-   docs: Fix GstWebRTCICE* class documentation
-   h264ccextractor, h265ccextractor: Handle gap with unknown pts
-   h265decoder, h265ccinserter: Fix broken SPS/PPS link
-   h265parser: Fix num_long_term_pics bound check
-   Segmentation fault in H265 decoder
-   h266decoder: fix leak parsing SEI messages
-   meson.build: test for and link against libatomic if it exists
-   mse: Improved Thread Safety of API
-   mse: Revert ownership transfer API change in gst_source_buffer_append_buffer()
-   tensordecoders: updating element classification
-   unixfd: Fix wrong memory size when offset > 0
-   uvcsink: Respond to control requests with proper error handling
-   v4l2codecs: unref frame in all error paths of end_picture
-   va: Skip codecs that report maximum width or height lower than minimum
-   vapostproc: fix wrong video orientation after restarting the element
-   vavp9enc: fix mem leaks in _vp9_decide_profile
-   vkformat: fix build error
-   vtenc: Avoid deadlocking when changing properties on the fly
-   vulkan: fix memory leak at dynamic registering
-   webrtc: enhance rtx entry creation
-   webrtcbin: add missing warning for caps missmatch
-   ZDI-CAN-26596: New Vulnerability Report (Security)

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   Bump MSRV to 1.83
-   Allow any windows-sys version >= 0.52 and <= 0.59
-   aws/polly: add GstScaletempoTargetDurationMeta to output buffers
-   awstranslate: improve message posted on bus
-   cdg: typefind: Division by zero fix
-   cea708mux: Improve support for overflowing input captions
-   colordetect: Change to videofilter base class
-   dav1ddec: Drain decoder on caps changes if necessary
-   dav1ddec: Only update unknown parts of the upstream colorimetry and not all of it
-   dav1ddec: Support RGB encoded AV1
-   dav1ddec: Use downstream buffer pool for copying if video meta is not supported
-   dav1ddec: Use max-frame-delay value from the decoder instead of calculating it
-   dav1ddec: Use max-frame-delay value from the decoder instead of calculating it
-   doc: Update to latest way of generating hotdoc config files
-   Fix gtk4 compile
-   Fix various clippy 1.86 warnings and update gstreamer-rs / gtk-rs dependencies
-   fmp4mux: Add a couple of minor new features
-   fmp4mux: Add manual-split mode that is triggered by serialized downstream events
-   fmp4mux: Add send-force-keyunit property
-   fmp4mux: Fix latency configuration for properties set during construction
-   fmp4mux: Improve split-at-running-time handling
-   fmp4mux/mp4mux: Handle the case of multiple tags per taglist correctly
-   fmp4mux: Write a v0 tfdt box if the decode time is small enough
-   gstwebrtc-api: Add TypeScript type definitions, build ESM for broader compatibility, improve JSDocs
-   hlsmultivariantsink: Allow users to specify playlist and segment location
-   hlssink3 - Add Support for NTP timestamp from buffer
-   livesync: Notify in/out/drop/duplicate properties on change
-   livesync: Only notify drop/duplicate properties
-   meson: Require gst 1.18 features for dav1d
-   mp4mux: Don’t write composition time offsets if they’re all zero
-   mp4mux, fmp4mux: Use correct timescales for edit lists
-   mpegtslivesrc: increase threshold for PCR <-> PTS DISCONT
-   mpegtslivesrc: Use a separate mutex for the properties
-   mux: use smaller number of samples for testing
-   net/aws: punctuation-related improvements to our span_tokenize_items function
-   pcap_writer: Mark target-factory and pad-path props as construct-only
-   speechmatics: Handle multiple stream-start event
-   tracers: buffer-lateness: don’t panic on add overflow + reduce graph legend entry font size a bit
-   tracers: Update to etherparse 0.17
-   transcriberbin: make auto passthrough work when transcriber is a bin
-   ts-jitterbuffer: improve scheduling of lost events
-   tttocea708: fix origin-row handling for roll-up in CEA-708
-   Update Cargo.lock to remove old windows-targets 0.48.5
-   Update dependencies
-   Update gtk-rs / gstreamer-rs dependencies and update for API changes
-   Update to bitstream-io 3
-   uriplaylistbin: skip cache test when offline
-   webrtc: Port to reqwest 0.12
-   webrtcsink: Fix compatibility with audio level header extension

gst-libav

-   No changes

gst-rtsp-server

-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

-   gst-python: fix compatibility with PyGObject >= 3.52.0
-   gst-python: Segmentation Fault since PyGObject >= 3.52.0 due to missing _introspection_module attribute

gst-editing-services

-   Ensure properties are freed before (re)setting with g_value_dup_string() and during cleanup

gst-devtools, gst-validate + gst-integration-testsuites

-   Add missing Requires in pkg-config
-   devtools: dots-viewer: Bundle js dependencies using webpack
-   devtools: dots-viewer: Update dependencies and make windows dependencies conditional

gst-examples

-   examples: Update Rust dependencies
-   examples: webrtc: rust: Move from async-std to tokio

gstreamer-docs

-   Update docs

Development build environment

-   No changes

Cerbero build tool and packaging changes in 1.26.1

-   FindGStreamerMobile: Override pkg-config on Windows -> Android cross builds
-   Fix BuildTools not using recipes_remotes and recipes_commits
-   bootstrap, meson: Use pathlib.Path.glob to allow Python < 3.10
-   Use of ‘glob(…, root_dir)’ requires Python >=3.10, cerbero enforces >= 3.7
-   harfbuzz: update to 10.4.0
-   Update fontconfig to 2.16.1, pango to 1.56.2

Contributors to 1.26.1

Alexander Slobodeniuk, Alyssa Ross, Artem Martus, Arun Raghavan, Brad Hards, Carlos Bentzen, Carlos Rafael Giani, Daniel Morin,
David Smitmanis, Detlev Casanova, Dongyun Seo, Doug Nazar, dukesook, Edward Hervey, eipachte, Eli Mallon, François Laignel,
Guillaume Desmottes, Gustav Fahlen, Hou Qi, Jakub Adam, Jan Schmidt, Jan Tojnar, Jordan Petridis, Jordan Yelloz, L. E. Segovia,
Marc Leeman, Marek Olejnik, Mathieu Duponchelle, Matteo Bruni, Matthew Waters, Michael Grzeschik, Nirbheek Chauhan, Ognyan
Tonchev, Olivier Blin, Olivier Crête, Philippe Normand, Piotr Brzeziński, Razvan Grigore, Robert Mader, Sanchayan Maity,
Sebastian Dröge, Seungha Yang, Shengqi Yu (喻盛琪), Stefan Andersson, Stéphane Cerveau, Thibault Saunier, Tim-Philipp Müller,
tomaszmi, Víctor Manuel Jáquez Leal, Xavier Claessens,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.1

-   List of Merge Requests applied in 1.26.1
-   List of Issues fixed in 1.26.1

1.26.2

The second 1.26 bug-fix release (1.26.2) was released on 29 May 2025.

This release only contains bugfixes as well as a number of security fixes and important playback fixes, and it should be safe to
update from 1.26.0.

Highlighted bugfixes in 1.26.2

-   Various security fixes and playback fixes
-   aggregator base class fixes to not produce buffers too early in live mode
-   AWS translate element improvements
-   D3D12 video decoder workarounds for crashes on NVIDIA cards on resolution changes
-   dav1d AV1-decoder performance improvements
-   fmp4mux: tfdt and composition time offset fixes, plus AC-3 / EAC-3 audio support
-   GStreamer editing services fixes for sources with non-1:1 aspect ratios
-   MIDI parser improvements for tempo changes
-   MP4 demuxer atom parsing improvements and security fixes
-   New skia-based video compositor element
-   Subtitle parser security fixes
-   Subtitle rendering and seeking fixes
-   Playbin3 and uridecodebin3 stability fixes
-   GstPlay stream selection improvements
-   WAV playback regression fix
-   GTK4 paintable sink colorimetry support and other improvements
-   WebRTC: allow webrtcsrc to wait for a webrtcsink producer to initiate the connection
-   WebRTC: new Janus Video Room WebRTC source element
-   vah264enc profile decision making logic fixes
-   Python bindings gained support for handling mini object writability (buffers, caps, etc.)
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   aggregator: Various state related fixes
-   element: ref-sink the correct pad template when replacing an existing one
-   pipeline: Store the actual latency even if no static latency was configured
-   structure: Add gst_structure_is_writable() API to allow python bindings to be able to handle writability of MiniObjects
-   tracerutils: Do not warn on empty string as tracername
-   tracerutils: Fix leak in gst_tracer_utils_create_tracer()
-   Ensure properties are freed before (re)setting with g_value_dup_object() or g_value_dup_boxed() and during cleanup
-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes

gst-plugins-base

-   alsa: Avoid infinite loop in DSD rate detection
-   gl: Implement basetransform meta transform function
-   glshader: free shader on stop
-   glupload: Only add texture-target field to GL caps
-   gstaudioutilsprivate: Fix gcc 15 compiler error with function pointer
-   mikey: Avoid infinite loop while parsing MIKEY payload with unhandled payload types
-   properties: add G_PARAM_STATIC_STRINGS where missing
-   riff-media: fix MS and DVI ADPCM av_bps calculations
-   subtitleoverlay: Remove 0.10 hardware caps handling
-   subtitleoverlay: Missing support for DMABuf(?)
-   tests: opus: Update channel support and add to meson
-   textoverlay: fix shading for RGBx / RGBA pixel format variants
-   textoverlay background is wrong while cropping
-   uridecodebin3: Don’t hold play items lock while releasing pads
-   uridecodebin3: deadlock on PLAY_ITEMS_LOCK
-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   Fix Qt detection in various places

gst-plugins-good

-   adaptivedemux2: Fixes for collection handling
-   adaptivedemux2: Fix several races
-   dash: mpdclient: Don’t pass terminating NUL to adapter
-   gl: Implement basetransform meta transform function
-   imagefreeze: Set seqnum from segment too
-   interleave: Don’t hold object lock while querying caps downstream
-   matroskamux: Write stream headers before finishing file, so that a correct file with headers is written if we finish without
    any data
-   meson: Add build_rpath for qt6 plugin on macOS
-   meson: Fix qt detection in various places
-   properties: add G_PARAM_STATIC_STRINGS where missing
-   qtdemux: Check length of JPEG2000 colr box before parsing it
-   qtdemux: Parse chan box and improve raw audio channel layout handling
-   qtdemux: Improve track parsing
-   qtdemux: Use byte reader to parse mvhd box
-   qtdemux: cmpd box is only mandatory for uncompressed video with uncC version 0
-   rtph264pay: Reject stream-format=avc without codec_data
-   rtputils: Add debug category
-   v4l2: pool: Send drop frame signal after dqbuf success
-   v4l2: pool: fix assert when mapping video frame with DMA_DRM caps
-   v4l2videoenc: report error only when buffer pool parameters are invalid
-   wavparse: Ignore EOS when parsing the headers
-   wavparse: Regression leading to unplaybable wav files that were working before
-   Ensure properties are freed before (re)setting with g_value_dup_object() or g_value_dup_boxed() and during cleanup
-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   Fixes for big endian
-   Switch to GST_AUDIO_NE()
-   Valgrind fixes

gst-plugins-bad

-   alphacombine: Fix seeking after EOS
-   cuda: Fix runtime PTX compile, fix example code build with old CUDA SDK
-   curl: Fix build with MSVC
-   curl: small fixups p3
-   d3d12: Fix gstreamer-full subproject build with gcc
-   d3d12: Generate gir file
-   d3d12decoder: Workaround for NVIDIA crash on resolution change
-   d3d12memory: Allow set_fence() only against writable memory
-   d3d12memory: Make D3D12 map flags inspectable
-   d3d12screencapturesrc: Fix desktop handle leak
-   dash: mpdclient: Don’t pass terminating NUL to adapter
-   dvbsuboverlay: Actually make use of subtitle running time instead of using PTS
-   dvbsuboverlay: No subtitles after seek
-   h264parse: Never output stream-format=avc/avc3 caps without codec_data
-   lcevc: Use portable printf formatting macros
-   midiparse: Consider tempo changes when calculating duration
-   nvencoder: Fix GstVideoCodecFrame leak on non-flow-ok return
-   play: Improve stream selection
-   properties: add G_PARAM_STATIC_STRINGS where missing
-   rtpsender: fix ‘priority’ GValue get/set
-   va: Fix H264 profile decision logic
-   vulkan/wayland: Init debug category before usage
-   Ensure properties are freed before (re)setting with g_value_dup_object() or g_value_dup_boxed() and during cleanup
-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   Fixes for big endian
-   Fix Qt detection in various places
-   Switch to GST_AUDIO_NE()
-   Valgrind fixes

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   awstranslate: improve control over accumulator behavior
-   awstranslate: output buffer lists
-   cea608tott: make test text less shocking by having more cues as context
-   dav1ddec: Directly decode into downstream allocated buffers if possible
-   deny: Allow webpki-root-certs license
-   fmp4mux: Add support for AC-3 / EAC-3
-   fmp4mux: Use earliest PTS for the base media decode time (tfdt)
-   fmp4mux: Fix handling of negative DTS in composition time offset
-   fmp4mux: Write lmsg as compatible brand into the last fragment
-   mp4mux: add extra brands
-   mp4: avoid dumping test output into build directory
-   mp4: migrate to mp4-atom to check muxing
-   mp4: test the trak structure
-   gtk4: Update and adapt to texture builder API changes
-   gtk4: Initial colorimetry support
-   gtk4: Update default GTK4 target version to 4.10
-   rtp: Update to bitstream-io 4.0
-   skia: Implement a video compositor using skia
-   webrtc: addressing a few deadlocks
-   webrtc: Support for producer sessions targeted at a given consumer
-   webrtc: add new JanusVR source element
-   webrtc: janus: clean up and refactoring
-   webrtcsink: Use seq number instead of Uuid for discovery
-   webrtc: Make older peers less likely to crash when webrtcsrc is used
-   Fix or silence various new clippy warnings
-   Update Cargo.lock to fix duplicated target-lexicon

gst-libav

-   Valgrind fixes
-   libav: Only allocate extradata while decoding

gst-rtsp-server

-   properties: add G_PARAM_STATIC_STRINGS where missing
-   properties: ensure properties are freed before (re)setting with g_value_dup_object() or g_value_dup_boxed() and during
    cleanup
-   tests: Valgrind fixes

gstreamer-vaapi

-   Ensure properties are freed before (re)setting with g_value_dup_object() or g_value_dup_boxed() and during cleanup

gstreamer-sharp

-   No changes

gst-python

This release includes important fixes for the GStreamer Python bindings.

Since pygobject 3.13 around 10 years ago, it wasn’t possible anymore to modify GStreamer miniobjects, e.g. modify caps or set
buffer timestamps, as an implicit copy of the original would always be made. This should finally work again now.

-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   python: Add overrides to be able to handle writability of MiniObjects
-   python: Convert buffer metadata API to use @property decorators
-   REGRESSION: pygobject 3.13 now copies the GstStructure when getting them from a GstCaps, making it impossible to properly
    modify structures from caps in place

gst-editing-services

-   Fix frame position for sources with par < 1
-   Fix video position for sources with pixel-aspect-ratio > 1
-   Valgrind fixes
-   properties: add G_PARAM_STATIC_STRINGS where missing
-   Switch to GST_AUDIO_NE() to make things work properly on Big Endian systems

gst-devtools, gst-validate + gst-integration-testsuites

-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   validate: baseclasses: Reset Test timeouts between iterations
-   validate: scenario: Fix race condition when ignoring EOS

gst-examples

-   Fix new warnings on Fedora 42, various meson warnings, and other small meson build/wrap fixes
-   webrtc examples: Fix running against self-signed certs
-   webrtc/signalling: fix compatibility with python 3.13

gstreamer-docs

-   No changes

Development build environment

-   Various wrap updates
-   Add qt-method meson options to fix Qt detection in various places
-   pre-commit: Workaround broken shebang on Windows

Cerbero build tool and packaging changes in 1.26.2

-   directx-headers: Fix g-ir-scanner expecting MSVC naming convention for gst-plugins-bad introspection
-   m4: update recipe to fix hang in configure
-   pango: Fix introspection missing since 1.56.2 update

Contributors to 1.26.2

Adrian Perez de Castro, Alexander Slobodeniuk, Alicia Boya García, Andoni Morales Alastruey, Biswapriyo Nath, Brad Hards, Branko
Subasic, Christoph Reiter, Daniel Morin, Doug Nazar, Devon Sookhoo, Eva Pace, Guillaume Desmottes, Hou Qi, Jakub Adam, Jan
Schmidt, Jochen Henneberg, Jordan Petridis, L. E. Segovia, Mathieu Duponchelle, Matthew Waters, Nicolas Dufresne, Nirbheek
Chauhan, Olivier Crête, Pablo García, Piotr Brzeziński, Robert Mader, Sebastian Dröge, Seungha Yang, Thibault Saunier,
Tim-Philipp Müller, Vasiliy Doylov, Wim Taymans, Xavier Claessens, Zhao, Gang,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.2

-   List of Merge Requests applied in 1.26.2
-   List of Issues fixed in 1.26.2

1.26.3

The third 1.26 bug-fix release (1.26.3) was released on 26 June 2025.

This release only contains bugfixes including some important playback fixes, and it should be safe to update from 1.26.x.

Highlighted bugfixes in 1.26.3

-   Security fix for the H.266 video parser
-   Fix regression for WAV files with acid chunks
-   Fix high memory consumption caused by a text handling regression in uridecodebin3 and playbin3
-   Fix panic on late GOP in fragmented MP4 muxer
-   Closed caption conversion, rendering and muxing improvements
-   Decklink video sink preroll frame rendering and clock drift handling fixes
-   MPEG-TS demuxing and muxing fixes
-   MP4 muxer fixes for creating very large files with faststart support
-   New thread-sharing 1:N inter source and sink elements, and a ts-rtpdtmfsrc
-   New speech synthesis element around ElevenLabs API
-   RTP H.265 depayloader fixes and improvements, as well as TWCC and GCC congestion control fixes
-   Seeking improvements in DASH client for streams with gaps
-   WebRTC sink and source fixes and enhancements, including to LiveKit and WHIP signallers
-   The macOS osxvideosink now posts navigation messages
-   QtQML6GL video sink input event handling improvements
-   Overhaul detection of hardware-accelerated video codecs on Android
-   Video4Linux capture source fixes and support for BT.2100 PQ and 1:4:5:3 colorimetry
-   Vulkan buffer upload and memory handling regression fixes
-   gst-python: fix various regressions introduced in 1.26.2
-   cerbero: fix text relocation issues on 32-bit Android and fix broken VisualStudio VC templates
-   packages: ship pbtypes plugin and update openssl to 3.5.0 LTS
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   aggregator: Do not set event seqnum to INVALID
-   baseparse: test: Fix race on test start
-   pad: Only remove TAG events on STREAM_START if the stream-id actually changes
-   utils: Mark times array as static to avoid symbol conflict with the POSIX function
-   vecdeque: Use correct index type gst_vec_deque_drop_struct()

gst-plugins-base

-   GstAudioAggregator: fix structure unref in peek_next_sample()
-   audioconvert: Fix setting mix-matrix when input caps changes
-   encodebasebin: Duplicate encoding profile in property setter
-   gl: simplify private gst_gl_gst_meta_api_type_tags_contain_only()
-   osxvideosink: Use gst_pad_push_event() and post navigation messages
-   playsink: Fix race condition in stream synchronizer pad cleanup during state changes
-   python: Fix pulling events from appsink
-   streamsynchronizer: Consider streams having received stream-start as waiting
-   urisourcebin: Text tracks are no longer set as sparse stream in urisourcebin’s multiqueue

gst-plugins-good

-   aacparse: Fix counting audio channels in program_config_element
-   adaptivedemux2: free cancellable when freeing transfer task
-   dashdemux2: Fix seeking in a stream with gaps
-   decodebin wavparse cannot pull header
-   imagefreeze: fix not negotiate log when stop
-   osxvideosink: Use gst_pad_push_event() and post navigation messages
-   qml6glsink: Allow configuring if the item will consume input events
-   qtmux: Update chunk offsets when converting stco to co64 with faststart
-   splitmuxsink: Only send closed message once per open fragment
-   rtph265depay: CRA_NUT can also start an (open) GOP
-   rtph265depay: fix codec_data generation
-   rtspsrc: Don’t emit error during close if server is EOF
-   twcc: Fix reference timestamp wrapping (again)
-   v4l2: Fix possible internal pool leak
-   v4l2object: Add support for colorimetry bt2100-pq and 1:4:5:3
-   wavparse: Don’t error out always when parsing acid chunks

gst-plugins-bad

-   amc: Overhaul hw-accelerated video codecs detection
-   bayer2rgb: Fix RGB stride calculation
-   d3d12compositor: Fix critical warnings
-   dashsink: Fix failing test
-   decklink: calculate internal using values closer to the current clock times
-   decklinkvideosink: show preroll frame correctly
-   decklink: clock synchronization after pause
-   h266parser: Fix overflow when parsing subpic_level_info
-   lcevcdec: Check for errors after receiving all enhanced and base pictures
-   meson: fix building -bad tests with disabled soundtouch
-   mpegts: handle MPEG2-TS with KLV metadata safely by preventing out of bounds
-   mpegtsmux: Corrections around Teletext handling
-   srtsink: Fix header buffer filtering
-   transcoder: Fix uritranscodebin reference handling
-   tsdemux: Allow access unit parsing failures
-   tsdemux: Send new-segment before GAP
-   vulkanupload: fix regression for uploading VulkanBuffer
-   vulkanupload: fix regression when uploading to single memory multiplaned memory images.
-   webrtcbin: disconnect signal ICE handlers on dispose
-   {d3d12,d3d11}compositor: Fix negative position handling
-   {nv,d3d12,d3d11}decoder: Use interlace info in input caps

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   Add new speech synthesis element around ElevenLabs API
-   cea708mux: fix another WouldOverflow case
-   cea708mux: support configuring a limit to how much data will be pending
-   cea708overlay: also reset the output size on flush stop
-   gcc: handle out of order packets
-   fmp4mux: Fix panic on late GOP
-   livekit: expose a connection state property
-   mp4mux: add taic box
-   mp4mux: test the trak structure
-   pcap_writer: Make target-property and pad-path properties writable again
-   skia: Don’t build skia plugin by default for now
-   threadshare: cleanups & usability improvements
-   threadshare: sync runtime with latest async-io
-   threadshare: fix kqueue reactor
-   threadshare: Update to getifaddrs 0.2
-   threadshare: add new thread-sharing inter elements
-   threadshare: add a ts-rtpdtmfsrc element
-   transcriberbin: fix naming of subtitle pads
-   tttocea708: don’t panic if a new service would overflow
-   webrtc: android: Update Gradle and migrate to FindGStreamerMobile
-   webrtc: add new examples for stream selection over data channel
-   webrtcsrc: the webrtcbin get-transceiver index is not mlineindex
-   webrtcsrc: send CustomUpstream events over control channel ..
-   webrtcsink: Don’t require encoder element for pre-encoded streams
-   webrtcsink: Don’t reject caps events if the codec_data changes
-   whip: server: pick session-id from the endpoint if specified
-   cargo: add config file to force CARGO_NET_GIT_FETCH_WITH_CLI=true
-   Cargo.lock, deny: Update dependencies and log duplicated targo-lexicon
-   Update windows-sys dependency from “>=0.52, <=0.59” to “>=0.52, <=0.60”
-   deny: Add override for windows-sys 0.59
-   deny: Update lints
-   cargo_wrapper: Fix backslashes being parsed as escape codes on Windows
-   Fixes for Clock: non-optional return types
-   Rename relationmeta plugin to analytics

gst-libav

-   No changes

gst-rtsp-server

-   rtsp-server: tests: Fix a few memory leaks

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

This release includes some important regression fixes for the GStreamer Python bindings for regressions introduced in 1.26.2.

-   gst-python/tests: don’t depend on webrtc and rtsp-server
-   python: Fix pulling events from appsink and other fixes

gst-editing-services

-   No changes

gst-devtools, gst-validate + gst-integration-testsuites

-   validate: More memory leaks
-   validate: Valgrind fixes

gst-examples

-   No changes

gstreamer-docs

-   No changes

Development build environment

-   gst-env: Emit a warning about DYLD_LIBRARY_PATH on macOS

Cerbero build tool and packaging changes in 1.26.3

-   WiX: fix broken VC templates
-   android: Don’t ignore text relocation errors on 32-bit, and error out if any are found
-   build: source: handle existing .cargo/config.toml as in plugins-rs
-   ci: Detect text relocations when building android examples
-   gst-plugins-base: Ship pbtypes
-   gst-plugins-base: Fix category of pbtypes
-   gst-plugins-rs: Update for relationmeta -> analytics plugin rename
-   libsoup.recipe: XML-RPC support was removed before the 3.0 release
-   openssl: Update to 3.5.0 LTS

Contributors to 1.26.3

Albert Sjolund, Aleix Pol, Ben Butterworth, Brad Hards, César Alejandro Torrealba Vázquez, Changyong Ahn, Doug Nazar, Edward
Hervey, Elliot Chen, Enrique Ocaña González, François Laignel, Glyn Davies, He Junyan, Jakub Adam, James Cowgill, Jan Alexander
Steffens (heftig), Jan Schmidt, Jochen Henneberg, Johan Sternerup, Julian Bouzas, L. E. Segovia, Loïc Le Page, Mathieu
Duponchelle, Matthew Waters, Nicolas Dufresne, Nirbheek Chauhan, Philippe Normand, Pratik Pachange, Qian Hu (胡骞), Sebastian
Dröge, Seungha Yang, Taruntej Kanakamalla, Théo Maillart, Thibault Saunier, Tim-Philipp Müller, Víctor Manuel Jáquez Leal,
Xavier Claessens,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.3

-   List of Merge Requests applied in 1.26.3
-   List of Issues fixed in 1.26.3

1.26.4

The fourth 1.26 bug-fix release (1.26.4) was released on 16 July 2025.

This release only contains bugfixes including some important playback fixes, and it should be safe to update from 1.26.x.

Highlighted bugfixes in 1.26.4

-   adaptivedemux2: Fixed reverse playback
-   d3d12screencapture: Add support for monitor add/remove in device provider
-   rtmp2src: various fixes to make it play back AWS medialive streams
-   rtph265pay: add profile-id, tier-flag, and level-id to output rtp caps
-   vp9parse: Fix handling of spatial SVC decoding
-   vtenc: Fix negotiation failure with profile=main-422-10
-   gtk4paintablesink: Add YCbCr memory texture formats and other improvements
-   livekit: add room-timeout
-   mp4mux: add TAI timestamp muxing support
-   rtpbin2: fix various race conditions, plus other bug fixes and performance improvements
-   threadshare: add a ts-rtpdtmfsrc element, implement run-time input switching in ts-intersrc
-   webrtcsink: fix deadlock on error setting remote description and other fixes
-   cerbero: WiX installer: fix missing props files in the MSI packages
-   smaller macOS/iOS package sizes
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   tracers: Fix deadlock in latency tracer
-   Fix various valgrind/test errors when GST_DEBUG is enabled
-   More valgrind and test fixes
-   Various ASAN fixes

gst-plugins-base

-   Revert “streamsynchronizer: Consider streams having received stream-start as waiting”
-   alsa: free conf cache under valgrind
-   gst-device-monitor: Fix caps filter splitting
-   Fix various valgrind/test errors when GST_DEBUG is enabled
-   More valgrind and test fixes
-   Various ASAN fixes

gst-plugins-good

-   adaptivedemux2: Fixed reverse playback
-   matroskademux: Send tags after seeking
-   qtdemux: Fix incorrect FourCC used when iterating over sbgp atoms
-   qtdemux: Incorrect sibling type used in sbgp iteration loop
-   rtph265pay: add profile-id, tier-flag, and level-id to output rtp caps
-   rtpjpeg: fix copying of quant data if it spans memory segments
-   soup: Disable range requests when talking to Python’s http.server
-   v4l2videodec: need replace acquired_caps on set_format success
-   Fix various valgrind/test errors when GST_DEBUG is enabled
-   More valgrind and test fixes
-   Various ASAN fixes

gst-plugins-bad

-   avtp: crf: Setup socket during state change to ensure we handle failure
-   d3d12screencapture: Add support for monitor add/remove in device provider
-   mpegtsmux: fix double free caused by shared PMT descriptor
-   openh264: Ensure src_pic is initialized before use
-   rtmp2src: various fixes to make it play back AWS medialive streams
-   ssdobjectdetector: Use correct tensor data index for the scores
-   v4l2codecs: h265dec: Fix zero-copy of cropped window located at position 0,0
-   vp9parse: Fix handling of spatial SVC decoding
-   vp9parse: Revert “Always default to super-frame”
-   vtenc: Fix negotiation failure with profile=main-422-10
-   vulkan: Fix drawing too many triangles in fullscreenquad
-   vulkanfullscreenquad: add locks for synchronisation
-   Fix various valgrind/test errors when GST_DEBUG is enabled
-   More valgrind and test fixes
-   Various ASAN fixes

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   aws: s3hlssink: Write to S3 on OutputStream flush
-   cea708mux: fix clipping function
-   dav1ddec: Use video decoder base class latency reporting API
-   elevenlabssynthesizer: fix running time checks
-   gopbuffer: Push GOPs in order of time on EOS
-   gtk4: Improve color-state fallbacks for unknown values
-   gtk4: Add YCbCr memory texture formats
-   gtk4: Promote set_caps debug log to info
-   hlssink3: Fix a comment typo
-   hlssink3: Use closed fragment location in playlist generation
-   livekit: add room-timeout
-   mccparse: Convert “U” to the correct byte representation
-   mp4mux: add TAI timestamp element and muxing
-   threadshare: add a ts-rtpdtmfsrc element
-   rtp: Update to rtcp-types 0.2
-   rtpsend: Don’t configure a zero min RTCP interval for senders
-   rtpbin2: Fix handling of unknown PTs and don’t warn about incomplete RTP caps to allow for bundling
-   rtpbin2: Improve rtcp-mux support
-   rtpbin2: fix race condition on serialized Queries
-   rtpbin2: sync: fix race condition
-   rtprecv optimize src pad scheduling
-   rtprecv: fix SSRC collision event sent in wrong direction
-   skia: Add harfbuzz, freetype and fontconfig as dependencies in the meson build
-   tttocea{6,7}08: Disallow pango markup from input caps
-   ts-intersrc: handle dynamic inter-ctx changes
-   threadshare: src elements: don’t pause the task in downward state transitions
-   webrtc: sink: avoid recursive locking of the session
-   webrtcsink: fix deadlock on error setting remote description
-   webrtcsink: add mitigation modes parameter and signal
-   webrtc: fix Safari addIceCandidate crash
-   webrtc-api: Set default bundle policy to max-bundle
-   WHIP client: emit shutdown after DELETE request
-   Fix various new clippy 1.88 warnings
-   Update dependencies

gst-libav

-   Various ASAN fixes

gst-rtsp-server

-   No changes

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

-   No changes

gst-editing-services

-   Fix various valgrind/test errors when GST_DEBUG is enabled

gst-devtools, gst-validate + gst-integration-testsuites

-   Update various Rust dependencies

gst-examples

-   Update various Rust dependencies

gstreamer-docs

-   No changes

Development build environment

-   No changes

Cerbero build tool and packaging changes in 1.26.4

-   WiX: fix missing props files in the MSI
-   cmake: Do not rely on the CERBERO_PREFIX environment variable
-   osx: Update pkgbuild compression algorithms resulting in much smaller packages

Contributors to 1.26.4

Adrian Perez de Castro, Alicia Boya García, Arun Raghavan, Brad Hards, David Maseda Neira, David Monge, Doug Nazar, Enock Gomes
Neto, François Laignel, Haihua Hu, Hanna Weiß, Jerome Colle, Jochen Henneberg, L. E. Segovia, Mathieu Duponchelle, Matthew
Waters, Nicolas Dufresne, Nirbheek Chauhan, Philippe Normand, Piotr Brzeziński, Robert Ayrapetyan, Robert Mader, Sebastian
Dröge, Seungha Yang, Taruntej Kanakamalla, Thibault Saunier, Tim-Philipp Müller, Vivia Nikolaidou,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.4

-   List of Merge Requests applied in 1.26.4
-   List of Issues fixed in 1.26.4

1.26.5

The fifth 1.26 bug-fix release (1.26.5) was released on 7 August 2025.

This release only contains bugfixes including some important playback fixes, and it should be safe to update from 1.26.x.

Highlighted bugfixes in 1.26.5

-   audioconvert: Fix caps negotiation regression when using a mix matrix

-   aws: Add support for brevity in awstranslate and add option to partition speakers in the transcription output of
    awstranscriber2

-   speechmatics speech-to-text: Expose mask-profanities property

-   cea708mux: Add support for discarding select services on each input

-   cea608overlay, cea708overlay: Accept GPU memory buffers if downstream supports the overlay composition meta

-   d3d12screencapture source element and device provider fixes

-   decodebin3: Don’t error on an incoming ONVIF metadata stream

-   uridecodebin3: Fix potential crash when adding URIs to messages, e.g. if no decoder is available

-   v4l2: Fix memory leak for dynamic resolution change

-   VA encoder fixes

-   videorate, imagefreeze: Add support for JPEG XS

-   Vulkan integration fixes

-   wasapi2 audio device monitor improvements

-   webrtc: Add WHEP client signaller and add whepclientsrc element on top of webrtcsrc using that

-   threadshare: Many improvements and fixes to the generic threadshare and RTP threadshare elements

-   rtpbin2 improvements and fixes

-   gst-device-monitor-1.0 command line tool improvements

-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   aggregator: add sub_latency_min to pad queue size
-   build: Disable C5287 warning on MSVC

gst-plugins-base

-   audioconvert: Fix regression when using a mix matrix
-   audioconvert: mix-matrix causes caps negotiation failure
-   decodebin3: Don’t error on an incoming ONVIF metadata stream
-   gloverlay: Recompute geometry when caps change, and load texture after stopping and starting again
-   uridecodebin3: Add missing locking and NULL checks when adding URIs to messages
-   uridecodebin3: segfault in update_message_with_uri() if no decoder available
-   videorate, imagefreeze: add support for JPEG XS
-   gst-device-monitor-1.0: Add shell quoting for launch lines
-   gst-device-monitor-1.0: Fix criticals, and also accept utf8 in launch lines
-   gst-device-monitor-1.0: Use gst_print instead of g_print

gst-plugins-good

-   v4l2: fix memory leak for dynamic resolution change
-   videorate, imagefreeze: add support for JPEG XS

gst-plugins-bad

-   av1parse: Don’t error out on “currently” undefined seq-level indices
-   av1parse: fails to parse AV1 bitstreams generated by FFmpeg using the av1_nvenc hardware encoder
-   d3d12screencapturedevice: Avoid false device removal on monitor reconfiguration
-   d3d12screencapturesrc: Fix OS handle leaks/random crash in WGC mode
-   meson: d3d12: Add support for MinGW DirectXMath package
-   va: Re-negotiate after FLUSH
-   vaXXXenc: calculate latency with corrected framerate
-   vaXXXenc: fix potential race condition
-   vkphysicaldevice: enable sampler ycbcr conversion, synchronization2 and timeline semaphore features
-   vulkan: ycbcr conversion extension got promoted in 1.1.0
-   wasapi2: Port to IMMDevice based device selection

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   Note: This list has been updated, since it originally accidentally included some Merge Requests that only landed in the main
    branch, not in the 0.14 branch that ships with our GStreamer 1.26.5 packages.

-   awstranscriber2, awstranslate: Handle multiple stream-start event

-   awstranslate: expose property for turning brevity on)

-   awstranscriber2: add property for setting show_speaker_labels)

-   cea708mux: expose “discarded-services” property on sink pads)

-   ceaX08overlay: support ANY caps features, allowing e.g. memory:GLMemory if downstream supports the overlay composition meta

-   hlsmultivariantsink: Fix master playlist version

-   rtprecv: Drop state lock before chaining RTCP packets from the RTP chain function

-   Add rtpbin2 examples

-   rtpmp4apay2: fix payload size prefix

-   rtp: threadshare: fix some property ranges

-   mpegtslivesrc: Remove leftover debug message

-   speechmatics: expose mask-profanities property)

-   ts-audiotestsrc fixes

-   threadshare: fix flush for ts-queue ts-proxy & ts-intersrc

-   threadshare: fix regression in ts-proxysrc

-   threadshare: improvements to some elements

-   threadshare: udp: avoid getifaddrs in android)

-   threadshare: Enable windows Win32_Networking feature

-   threadshare: queue & proxy: fix race condition stopping

-   threadshare: Also enable windows Win32_Networking_WinSock feature

-   tracers: pipeline-snapshot: reduce WebSocket connection log level

-   tracers: queue-levels: add support for threadshare DataQueue related elements

-   tracers: Update to etherparse 0.19

-   transcriberbin: Fix handling of upstream latency query

-   webrtc: android example: fix media handling initialization sequence)

-   webrtcsink: Move videorate before videoconvert and videoscale to avoid processing frames that would be dropped

-   whep: add WHEP client signaller

-   Fix various new clippy 1.89 warnings

gst-libav

-   No changes

gst-rtsp-server

-   No changes

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

-   No changes

gst-editing-services

-   No changes

gst-devtools, gst-validate + gst-integration-testsuites

-   No changes

gst-examples

-   No changes

gstreamer-docs

-   No changes

Development build environment

-   gst-env: only-environment: only dump added and updated vars
-   gst-full: Fix detection of duplicate plugin entries
-   ci: Fix gst-full breakage due to a typo
-   build: Disable C5287 warning on MSVC

Cerbero build tool and packaging changes in 1.26.5

-   a52dec: update to 0.8.0 and port to Meson
-   build: Fix passing multiple steps
-   expat: update to 2.7.1
-   tar: Refactor in preparation for xcframework support

Contributors to 1.26.5

François Laignel, Jan Schmidt, Jaslo Ziska, L. E. Segovia, Marc-André Lureau, Mathieu Duponchelle, Matthew Waters, Nirbheek
Chauhan, Philippe Normand, Qian Hu (胡骞), Sanchayan Maity, Sebastian Dröge, Seungha Yang, Thibault Saunier, Tim-Philipp Müller,
Víctor Manuel Jáquez Leal, Xavier Claessens,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.5

-   List of Merge Requests applied in 1.26.5
-   List of Issues fixed in 1.26.5

1.26.6

The sixth 1.26 bug-fix release (1.26.6) was released on 14 September 2025.

This release only contains bugfixes including some important playback fixes, and it should be safe to update from 1.26.x.

Highlighted bugfixes in 1.26.6

-   analytics GstTensorMeta handling changes (see note below)
-   closed caption combiner and transcriberbin stability fixes
-   decklinkvideosrc: fix unrecoverable state after failing to start streaming because device is busy
-   decodebin3 tag handling improvements
-   fallbacksrc: Fix sources only being restarted once, as well as some deadlocks and race conditions on shutdown
-   gtk4paintablesink: Try importing dmabufs withouth DMA_DRM caps
-   hlsdemux2: Fix parsing of byterange and init map directives
-   rtpmp4gdepay2: allow only constantduration with neither constantsize nor sizelength set
-   spotifysrc: update to librespot 0.7 to make work after recent Spotify changes
-   threadshare: new blocking adapter element for use in front of block elements such as sinks that sync to the clock
-   threadshare: various other threadshare element fixes and improvements
-   v4l2: Add support for WVC1 and WMV3
-   videorate: possible performance improvements when operating in drop-only mode
-   GstBaseParse fixes
-   Vulkan video decoder fixes
-   Fix gst-device-monitor-1.0 tool device-path regression on Windows
-   Monorepo development environment builds fewer plugins using subprojects by default, those require explicit enablement now
-   Python bindings: Handle buffer PTS, DTS, duration, offset, and offset-end as unsigned long long (regression fix)
-   Cerbero: Reduce recipe parallelism in various cases and dump cerbero and recipe versions into datadir during packaging
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

Possibly breaking behavioural changes

-   Previously it was guaranteed that there is only ever up to one GstTensorMeta per buffer. This is no longer true and code
    working with GstTensorMeta must be able to handle multiple GstTensorMeta now (after this Merge Request).

gstreamer

-   baseparse: Try harder to fixate caps based on upstream in default negotiation
-   gst-discoverer reports 1x1 dimensions for “valid” MP4 files
-   baseparse: don’t clear most sticky events after a FLUSH_STOP event
-   gstreamer: Disable miniobject inline functions for gobject-introspection for non-subprojects too
-   gstreamer: Make sure to zero-initialize the GValue before G_VALUE_COLLECT_INIT
-   ptp: Fix a new Rust 1.89 compiler warning on Windows
-   ptp: Fix new compiler warning with Rust 1.89
-   Segmentation fault when compiled with “-ftrivial-auto-var-init=pattern”. Use of unitialized GValue.

gst-plugins-base

-   decodebin3: Update stream tags
-   rtpbasedepayload: Avoid potential use-after free
-   rtspconnection: Add get_url and get_ip return value annotation
-   gst_rtsp_connection_get_url return value transfer annotation missing
-   videometa: Fix valgrind warning when deserializing video meta
-   videorate: don’t hold the reference to the buffer in drop-only mode
-   gst-device-monitor-1.0: Fix device-path regression on Windows
-   gst-device-monitor-1.0: Add quoting for powershell and cmd
-   Monorepo: opengl, vorbis, plugins require explicit enablement now for a build using the Meson subproject fallback

gst-plugins-good

-   adaptivedemux2: fix crash due to log
-   adaptivedemux2: Crash in logging when “Dropping EOS before next period”
-   hlsdemux2: Fix parsing of byterange and init map directives
-   mpg123audiodec: Always break the decoding loop and relay downstream flow errors upstream
-   v4l2: Add support for WVC1 and WMV3
-   Monorepo: dv plugin requires explicit enablement now for a build using the Meson subproject fallback

gst-plugins-bad

-   analytics: always add GstTensorMeta
-   cccombiner: Crash fixes
-   curlsmtpsink: adapt to date formatting issue
-   decklinkvideosrc: fix decklinkvideosrc becomes unrecoverable if it fails to start streaming
-   decklinkvideosrc gets into unrecoverable state if device is busy
-   dwrite: Fix D3D12 critical warning
-   hlsdemux: Fix parsing of byterange and init map directives
-   mpegtsmux: Caps event fails with stream type change error
-   vulkanh24xdec: couple of fixes
-   vulkanh26xdec: fix discont state handling
-   waylandsink: add some error handler for event dispatch
-   zbar: tests: Handle symbol-bytes as not null-terminated
-   Monorepo: avtp, codec2json, iqa, microdns, openjpeg, qroverlay, soundtouch, tinyalsa plugins require explicit enablement now
    for a build using the Meson subproject fallback

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   analyticscombiner: Use NULL caps instead of EMPTY caps in the array for streams with no caps
-   aws: Ensure task stopping on paused-to-ready state change
-   fallbacksrc: Don’t panic during retries if the element was shut down in parallel
-   fallbacksrc: Don’t restart source if the element is just being shut down
-   fallbacksrc: Fix some custom source deadlocks
-   fallbacksrc: Fix sources only being restarted once
-   gtk4: Try importing dmabufs withouth DMA_DRM caps
-   inter: Give the appsrc/appsink a name that has the parent element as prefix
-   mp4: Skip tests using x264enc if it does not exist
-   rtpgccbwe: avoid clamp() panic when min_bitrate > max_bitrate
-   rtpmp4gdepay2: allow only constantduration with neither constantsize nor sizelength set
-   rtprecv: fix race condition on first buffer
-   speechmatics: Specify rustls as an explicit dependency
-   spotify: update to librespot 0.7
-   threadshare: add a blocking adapter element
-   threadshare: always use block_on_or_add_subtask
-   threadshare: audiotestsrc: fix setting samples-per-buffer…
-   threadshare: blocking_adapter: fix Since marker in docs
-   threadshare: fix resources not available when preparing asynchronously
-   threadshare: fix ts-inter test one_to_one_up_first
-   threadshare: have Task log its obj
-   threadshare: intersink: return from blocking tasks when stopping
-   threadshare: inter: update doc example
-   threadshare: runtime/pad: lower log level pushing Buffer to flushing pad
-   threadshare: separate blocking & throttling schedulers
-   threadshare: update examples
-   threadshare: Update to getifaddrs 0.5
-   threadshare: Fix macOS build post getifaddrs 0.5 update
-   threadshare: Bump up getiffaddrs to 0.1.5 and revert “udp: avoid getifaddrs in android”
-   threadshare: Reapply “udp: avoid getifaddrs in android”
-   transcriberbin: Fix some deadlocks
-   Update dependencies
-   webrtc: Migrate to warp 0.4 and switch to tokio-rustls
-   webrtc/signalling: Fix setting of host address
-   ci: add script to check readme against plugins list
-   Fix various new clippy 1.89 warnings
-   Don’t suggest running cargo cinstall after cargo cbuild
-   meson: Isolate built plugins from cargo target directory

gst-libav

-   No changes

gst-rtsp-server

-   rtsp-server: tests: Switch to fixtures to ensure pool shutdown

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

-   python: Handle buffer PTS/DTS/duration/offset/offset-end as unsigned long long

gst-editing-services

-   gstreamer: Make sure to zero-initialize the GValue before G_VALUE_COLLECT_INIT
-   Fix various memory leaks

gst-devtools, gst-validate + gst-integration-testsuites

-   validate: http-actions: Replace GUri with GstURI for GLib 2.64 compatibility
-   Fix memory leak and use of incorrect context

gst-examples

-   No changes

gstreamer-docs

-   No changes

Development build environment

-   gobject-introspection: Fix introspection failing on Linux with subproject GLib
-   glib: update to 2.82.5 and backport shebangs patch
-   ci: Work around PowerShell broken argument parsing
-   Disable more plugins on Windows by default by not pulling in fallback subprojects automatically, only if plugins are enabled
    explicitly
-   Fix build on windows due to proxy-libintl not being invoked
-   python: Reapply fixes to enable running Python bindings on Windows

Cerbero build tool and packaging changes in 1.26.6

-   ffmpeg: enable filters needed by avvideocompare
-   cache: Fix detection of build tools prefix when using a cache
-   cerbero/package: fix –tarball –compress-method=none
-   cerbero: Reduce recipe parallelism in various cases
-   ci: remove unpacked apk dir on completion
-   package: Dump cerbero and recipe versions into datadir

Contributors to 1.26.6

Andrey Khamukhin, Daniel Morin, Doug Nazar, François Laignel, Guillaume Desmottes, Hou Qi, Ian Napier, Jan Alexander Steffens
(heftig), Jan Schmidt, Jordan Petridis, L. E. Segovia, Marko Kohtala, Matthew Waters, Monty C, Nirbheek Chauhan, Ola Fornander,
Olivier Crête, Piotr Brzeziński, Qian Hu (胡骞), r4v3n6101, Robert Mader, Ruben Gonzalez, Sanchayan Maity, Sebastian Dröge,
Seungha Yang, Taruntej Kanakamalla, Thibault Saunier, Tim-Philipp Müller, Víctor Manuel Jáquez Leal, Vivian LEE, Vivienne
Watermeier, Xavier Claessens,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.6

-   List of Merge Requests applied in 1.26.6
-   List of Issues fixed in 1.26.6

1.26.7

The seventh 1.26 bug-fix release (1.26.7) was released on 14 October 2025.

This release only contains bugfixes including some important playback fixes, and it should be safe to update from 1.26.x.

Highlighted bugfixes in 1.26.7

-   cea608overlay: improve handling of non-system memory
-   cuda: Fix runtime kernel compile with CUDA 13.0
-   d3d12: Fix crop meta support in converter and passthrough handling in deinterlacer
-   fallbacksrc: source handling improvements; no-more-pads signal for streams-unaware parents
-   inter: add properties to fine tune the inner elements
-   qtdemux: surround sound channel layout handling fixes and performance improvements for GoPro videos
-   rtp: Add linear audio (L8, L16, L24) RTP payloaders / depayloaders
-   rtspsrc: Send RTSP keepalives in TCP/interleaved modes
-   rtpamrpay2: frame quality indicator flag related fixes
-   rtpbasepay2: reuse last PTS when possible, to work around problems with NVIDIA Jetson AV1 encoder
-   mpegtsmux, tsdemux: Opus audio handling fixes
-   threadshare: latency related improvements and many other fixes
-   matroskamux, tsmux, flvmux, cea608mux: Best pad determination fixes at EOS
-   unixfd: support buffers with a big payload
-   videorate unknown buffer duration assertion failure with variable framerates
-   editing services: Make GESTimeline respect SELECT_ELEMENT_TRACK signal discard decision; memory leak fixes
-   gobject-introspection annotation fixes
-   cerbero: Update meson to 1.9.0 to enable Xcode 26 compatibility
-   Various bug fixes, build fixes, memory leak fixes, and other stability and reliability improvements

gstreamer

-   controller: Fix get_all() return type annotation
-   gst-launch: Do not assume error messages have a src element
-   multiqueue: Fix object reference handling in signal callbacks
-   netclientclock: Fix memory leak in error paths

gst-plugins-base

-   discoverer: Mark gst_discoverer_stream_info_list_free() as transfer full
-   qt: Fix building examples on macOS
-   riff: Add channel reorder maps for 3 and 7 channel audio
-   sdp: proper usage of gst_buffer_append
-   videorate: fix assert fail due to invalid buffer duration
-   Fix build error with glib < 2.68

gst-plugins-good

-   matroskamux: Properly check if pads are EOS in find_best_pad
-   qt: Fix building examples on macOS
-   qtdemux: bad performance with GoPro videos containing FDSC metadata tracks
-   qtdemux: fix open/seek perf for GoPro files with SOS track
-   qtdemux: handle unsupported channel layout tags gracefully
-   qtdemux: set channel-mask to 0 for unknown layout tags
-   rtspsrc: Send RTSP keepalives in TCP/interleaved modes
-   v4l2: Add GstV4l2Error handling in gst_v4l2_get_capabilities
-   v4l2: Fix memory leak for DRM caps negotiation
-   v4l2transform: reconfigure v4l2object only if respective caps changed
-   Fix issues with G_DISABLE_CHECKS & G_DISABLE_ASSERT

gst-plugins-bad

-   cuda: Fix runtime kernel compile with CUDA 13.0
-   d3d12convert: Fix crop meta support
-   d3d12deinterlace: Fix passthrough handling
-   gst: Fix a few small leaks
-   matroskamux: Properly check if pads are EOS in find_best_pad
-   tsdemux: Directly forward Opus AUs without opus_control_header
-   tsmux: Write a full Opus channel configuration if no matching Vorbis one is found
-   unixfd: Fix case of buffer with big payload
-   vacompositor: Correct scale-method properties
-   webrtc: nice: Fix a use-after-free and a mem leak
-   Fix all compiler warnings on Fedora
-   Fix issues with G_DISABLE_CHECKS & G_DISABLE_ASSERT

gst-plugins-ugly

-   No changes

GStreamer Rust plugins

-   cea608overlay: Support non-system memory correctly
-   fallbacksrc: Fix custom source reuse case
-   fallbacksrc: Fix sources only being restarted once
-   fallbacksrc: Post no-more-pads signal for streams-unaware parent
-   inter: add properties to fine tune the inner elements
-   onvifmetadatapay: copy metadata from source buffer
-   rtp: Add linear audio (L8, L16, L24) RTP payloaders / depayloaders
-   rtpamrpay2: Actually forward the frame quality indicator
-   rtpamrpay2: Set frame quality indicator flag
-   rtp: basedepay: reuse last PTS, when possible to work around problems with NVIDIA Jetson AV1 encoder
-   rtpsend/recv: fix property type for stats
-   threadshare: audiotestsrc: support more Audio formats
-   threadshare: backpressure: abort pending items on flush start
-   threadshare: fixes & improvements
-   threadshare: latency related improvements and fixes
-   threadshare: runtime task: execute action in downward transition
-   threadshare: udpsink: fix panic recalculating latency from certain executors
-   uriplaylistbin: Ignore all tests for now
-   webrtc: livekit: Drop connection lock after take()
-   Update dependencies
-   Update dependencies
-   ci: use image and GST_RS_MSRV / GST_RS_STABLE variables from gstreamer-rs 0.24 in gst-plugins-rs 0.14 branch
-   Add rust-tls-native-roots feature to the reqwest dep
-   Fix some new clippy 1.90 warnings
-   meson: Fix .pc files installation and simplify build output handling

gst-libav

-   Fix all compiler warnings on Fedora

gst-rtsp-server

-   Fix issues with G_DISABLE_CHECKS & G_DISABLE_ASSERT

gstreamer-vaapi

-   No changes

gstreamer-sharp

-   No changes

gst-python

-   No changes

gst-editing-services

-   ges: timeline: Respect SELECT_ELEMENT_TRACK signal discard decision
-   gst: Fix a few small leaks

gst-devtools, gst-validate + gst-integration-testsuites

-   Fix issues with G_DISABLE_CHECKS & G_DISABLE_ASSERT

gst-examples

-   No changes

gstreamer-docs

-   No changes

Development build environment

-   libsoup.wrap: Apply upstream fix for GModule deadlock

Cerbero build tool and packaging changes in 1.26.7

-   meson: Update to 1.9.0 to enable Xcode 26 compatibility
-   osxrelocator: Add .so to the allowed dylib extensions
-   ci: update macos image to 26-tahoe
-   EndeavourOS support

Contributors to 1.26.7

Andoni Morales Alastruey, Branko Subasic, Vincent Beng Keat Cheah, Doug Nazar, Ekwang Lee, François Laignel, Inbok Kim, Jakub
Adam, Jan Schmidt, Jochen Henneberg, L. E. Segovia, Mark Nauwelaerts, Markus Hofstaetter, Matthew Waters, Nirbheek Chauhan,
Norbert Hańderek, Philippe Normand, Razvan Grigore, Sebastian Dröge, Seungha Yang, Taruntej Kanakamalla, Thibault Saunier,
Tim-Philipp Müller, Xavier Claessens,

… and many others who have contributed bug reports, translations, sent suggestions or helped testing. Thank you all!

List of merge requests and issues fixed in 1.26.7

-   List of Merge Requests applied in 1.26.7
-   List of Issues fixed in 1.26.7

Schedule for 1.28

Our next major feature release will be 1.28, and 1.27 will be the unstable development version leading up to the stable 1.28
release. The development of 1.27/1.28 will happen in the git main branch of the GStreamer mono repository.

The schedule for 1.28 is yet to be decided, but we’re aiming for a release towards the end of 2025.

1.28 will be backwards-compatible to the stable 1.26, 1.24, 1.22, 1.20, 1.18, 1.16, 1.14, 1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0
release series.

--------------------------------------------------------------------------------------------------------------------------------

These release notes have been prepared by Tim-Philipp Müller with contributions from Arun Raghavan, Daniel Morin, Nirbheek
Chauhan, Olivier Crête, Philippe Normand, Sebastian Dröge, Seungha Yang, Thibault Saunier, and Víctor Manuel Jáquez Leal.

License: CC BY-SA 4.0
07070100000005000081A400000000000000000000000168EE8797000000B6000000000000000000000000000000000000001E00000000gst-rtsp-server-1.26.7/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.
  07070100000006000081A400000000000000000000000168EE879700000FAB000000000000000000000000000000000000001F00000000gst-rtsp-server-1.26.7/RELEASE    This is GStreamer gst-rtsp-server 1.26.7.

The GStreamer team is thrilled to announce a new major feature release
of your favourite cross-platform multimedia framework!

As always, this release is again packed with new features, bug fixes and
other improvements.
 
The 1.26 release series adds new features on top of the 1.24 series and is
part of the API and ABI-stable 1.x release series.

Full release notes can be found at:

  https://gstreamer.freedesktop.org/releases/1.26/

Binaries for Android, iOS, Mac OS X and Windows will usually be provided
shortly after the release.

This module will not be very useful by itself and should be used in conjunction
with other GStreamer modules for a complete multimedia experience.

 - gstreamer: provides the core GStreamer libraries and some generic plugins

 - gst-plugins-base: a basic set of well-supported plugins and additional
                     media-specific GStreamer helper libraries for audio,
                     video, rtsp, rtp, tags, OpenGL, etc.

 - gst-plugins-good: a set of well-supported plugins under our preferred
                     license

 - gst-plugins-ugly: a set of well-supported plugins which might pose
                     problems for distributors

 - gst-plugins-bad: a set of plugins of varying quality that have not made
                    their way into one of core/base/good/ugly yet, for one
                    reason or another. Many of these are are production quality
                    elements, but may still be missing documentation or unit
                    tests; others haven't passed the rigorous quality testing
                    we expect yet.

 - gst-libav: a set of codecs plugins based on the ffmpeg library. This is
                    where you can find audio and video decoders and encoders
                    for a wide variety of formats including H.264, AAC, etc.

 - gstreamer-vaapi: hardware-accelerated video decoding and encoding using
                    VA-API on Linux. Primarily for Intel graphics hardware.
                    (Deprecated, use the new "va" plugin instead)

 - gst-rtsp-server: library to serve files or streaming pipelines via RTSP

 - gst-editing-services: library an plugins for non-linear editing

 - gst-plugins-rs: an exciting collection of well-maintained plugins written
                   in the Rust programming language (usable from any language)

==== Download ====

You can find source releases of gstreamer in the download
directory: https://gstreamer.freedesktop.org/src/gstreamer/

The git repository and details how to clone it can be found at
https://gitlab.freedesktop.org/gstreamer/gstreamer/

==== Homepage ====

The project's website is https://gstreamer.freedesktop.org/

==== Support and Bugs ====

We track bugs and feature requests in GitLab:

  https://gitlab.freedesktop.org/gstreamer/gstreamer/

Please submit patches via GitLab as well, in form of Merge Requests. See

  https://gstreamer.freedesktop.org/documentation/contribute/

for more details.

For help and support, please head over to our Discourse forum at

  https://discourse.gstreamer.org/

or pop into one of our Matrix chat rooms, see

  https://discourse.gstreamer.org/t/new-gstreamer-matrix-chat-space/675

for more details.

Please do not submit support requests in GitLab, we only use it for
bug tracking and merge requests review. Use the Discourse forum instead.

==== Developers ====

The GStreamer source code repository can be found on GitLab on freedesktop.org:

  https://gitlab.freedesktop.org/gstreamer/gstreamer/

and can also be cloned from there and this is also where you can submit
Merge Requests or file issues for bugs or feature requests.

Interested developers of the core library, plugins, and applications should
join us on Matrix for chat and the Discourse forum for announcements, help
and discussions.

There is also a gstreamer-devel mailing list, but Discourse is preferred:

  https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel
 07070100000007000081A400000000000000000000000168EE879700000062000000000000000000000000000000000000002400000000gst-rtsp-server-1.26.7/REQUIREMENTS   You need to have GStreamer. You can use an installed version of
GStreamer or from its build dir.

  07070100000008000081A400000000000000000000000168EE879700000040000000000000000000000000000000000000001C00000000gst-rtsp-server-1.26.7/TODO   
 - use a config file to configure the server
 - error recovery
07070100000009000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000001C00000000gst-rtsp-server-1.26.7/docs   0707010000000A000081A400000000000000000000000168EE879700004EEA000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/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.

  0707010000000B000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/docs/design    0707010000000C000081A400000000000000000000000168EE8797000005E5000000000000000000000000000000000000003900000000gst-rtsp-server-1.26.7/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.
   0707010000000D000081A400000000000000000000000168EE879700000018000000000000000000000000000000000000002500000000gst-rtsp-server-1.26.7/docs/index.md  # GStreamer RTSP Server
0707010000000E000081A400000000000000000000000168EE879700000F7F000000000000000000000000000000000000002800000000gst-rtsp-server-1.26.7/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',
)
 0707010000000F000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002400000000gst-rtsp-server-1.26.7/docs/plugins   07070100000010000081A400000000000000000000000168EE87970000573C000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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"
    }
}07070100000011000081A400000000000000000000000168EE879700000011000000000000000000000000000000000000002D00000000gst-rtsp-server-1.26.7/docs/plugins/index.md  # rtspclientsink
   07070100000012000081A400000000000000000000000168EE87970000000A000000000000000000000000000000000000003000000000gst-rtsp-server-1.26.7/docs/plugins/sitemap.txt   gst-index
  07070100000013000081A400000000000000000000000168EE879700000017000000000000000000000000000000000000002700000000gst-rtsp-server-1.26.7/docs/sitemap.md    gi-index
    gst-index
 07070100000014000081A400000000000000000000000168EE879700000009000000000000000000000000000000000000002800000000gst-rtsp-server-1.26.7/docs/sitemap.txt   gi-index
   07070100000015000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002000000000gst-rtsp-server-1.26.7/examples   07070100000016000081A400000000000000000000000168EE87970000043E000000000000000000000000000000000000002C00000000gst-rtsp-server-1.26.7/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
  07070100000017000081A400000000000000000000000168EE879700001215000000000000000000000000000000000000002E00000000gst-rtsp-server-1.26.7/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;
}
   07070100000018000081A400000000000000000000000168EE879700001BA4000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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;
}
07070100000019000081A400000000000000000000000168EE879700001E1D000000000000000000000000000000000000003300000000gst-rtsp-server-1.26.7/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;

  g_print ("removing all sessions\n");
  pool = gst_rtsp_server_get_session_pool (server);
  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;
  }
}
   0707010000001A000081A400000000000000000000000168EE879700001967000000000000000000000000000000000000002C00000000gst-rtsp-server-1.26.7/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;

  g_print ("removing all sessions\n");
  pool = gst_rtsp_server_get_session_pool (server);
  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;
  }
}
 0707010000001B000081A400000000000000000000000168EE8797000022AE000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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;
  }
}
  0707010000001C000081A400000000000000000000000168EE8797000010A5000000000000000000000000000000000000002E00000000gst-rtsp-server-1.26.7/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;
}
   0707010000001D000081A400000000000000000000000168EE8797000014E9000000000000000000000000000000000000002B00000000gst-rtsp-server-1.26.7/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;
}
   0707010000001E000081A400000000000000000000000168EE879700000C6F000000000000000000000000000000000000003100000000gst-rtsp-server-1.26.7/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;
  }
}
 0707010000001F000081A400000000000000000000000168EE879700000E25000000000000000000000000000000000000003200000000gst-rtsp-server-1.26.7/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;
  }
}
   07070100000020000081A400000000000000000000000168EE879700000FC4000000000000000000000000000000000000003700000000gst-rtsp-server-1.26.7/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;
}
07070100000021000081A400000000000000000000000168EE879700000ECD000000000000000000000000000000000000003000000000gst-rtsp-server-1.26.7/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;

  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 ();
  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);

  return 0;
}
   07070100000022000081A400000000000000000000000168EE879700000C2D000000000000000000000000000000000000002B00000000gst-rtsp-server-1.26.7/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;
}
   07070100000023000081A400000000000000000000000168EE879700000A91000000000000000000000000000000000000003900000000gst-rtsp-server-1.26.7/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;
}
   07070100000024000081A400000000000000000000000168EE879700005823000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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;
}
 07070100000025000081A400000000000000000000000168EE8797000043DD000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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;
}
   07070100000026000081A400000000000000000000000168EE879700000441000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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
   07070100000027000081A400000000000000000000000168EE8797000008C4000000000000000000000000000000000000002E00000000gst-rtsp-server-1.26.7/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;
}
07070100000028000081A400000000000000000000000168EE879700001A94000000000000000000000000000000000000003300000000gst-rtsp-server-1.26.7/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;
}
07070100000029000081A400000000000000000000000168EE879700000DAD000000000000000000000000000000000000002E00000000gst-rtsp-server-1.26.7/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;
}
   0707010000002A000081A400000000000000000000000168EE87970000677F000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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_element_call_async (GST_ELEMENT_CAST (self->uridecodebin),
          (GstElementCallAsyncFunc) gst_replay_bin_do_segment_seek, self, NULL);
    } 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_element_call_async (GST_ELEMENT_CAST (self->uridecodebin),
      (GstElementCallAsyncFunc) gst_replay_bin_do_initial_segment_seek,
      self, NULL);
}

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, };

  /* get the feature list using the filter */
  gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter)
      payloader_filter, FALSE, &data);

  /* 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;
}
 0707010000002B000081A400000000000000000000000168EE879700000545000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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
   0707010000002C000081A400000000000000000000000168EE879700000B11000000000000000000000000000000000000002B00000000gst-rtsp-server-1.26.7/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;
}
   0707010000002D000081A400000000000000000000000168EE879700001220000000000000000000000000000000000000002B00000000gst-rtsp-server-1.26.7/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;
  }
}
0707010000002E000081A400000000000000000000000168EE879700001E62000000000000000000000000000000000000003800000000gst-rtsp-server-1.26.7/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;
  }
}
  0707010000002F000081A400000000000000000000000168EE879700000C7F000000000000000000000000000000000000003100000000gst-rtsp-server-1.26.7/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;
  }
}
 07070100000030000081A400000000000000000000000168EE8797000018D3000000000000000000000000000000000000002D00000000gst-rtsp-server-1.26.7/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;
  }
}
 07070100000031000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000001B00000000gst-rtsp-server-1.26.7/gst    07070100000032000081A400000000000000000000000168EE879700004FAB000000000000000000000000000000000000002C00000000gst-rtsp-server-1.26.7/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.26.7</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-10-14</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.7.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.6</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-09-14</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.6.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.5</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-08-07</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.5.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.4</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-07-16</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.4.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.3</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-06-26</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.3.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.2</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-05-29</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.2.tar.xz" />
  </Version>
 </release>

 <release>
  <Version>
   <revision>1.26.1</revision>
   <branch>1.26</branch>
   <name></name>
   <created>2025-04-24</created>
   <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.26.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>
 07070100000033000081A400000000000000000000000168EE87970000084E000000000000000000000000000000000000003100000000gst-rtsp-server-1.26.7/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
  07070100000034000081A400000000000000000000000168EE87970000002A000000000000000000000000000000000000002700000000gst-rtsp-server-1.26.7/gst/meson.build    subdir('rtsp-server')
subdir('rtsp-sink')
  07070100000035000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002700000000gst-rtsp-server-1.26.7/gst/rtsp-server    07070100000036000081A400000000000000000000000168EE879700000CDA000000000000000000000000000000000000003300000000gst-rtsp-server-1.26.7/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)
  07070100000037000081A400000000000000000000000168EE87970000502F000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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;
}
 07070100000038000081A400000000000000000000000168EE879700001B59000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);

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);

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);

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__ */
   07070100000039000081A400000000000000000000000168EE879700007F5B000000000000000000000000000000000000003300000000gst-rtsp-server-1.26.7/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: (transfer full): 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);
}
 0707010000003A000081A400000000000000000000000168EE879700002045000000000000000000000000000000000000003300000000gst-rtsp-server-1.26.7/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);

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);

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);

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);

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);

/* 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__ */
   0707010000003B000081A400000000000000000000000168EE879700027426000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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_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));

  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);

  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);
    return GST_RTSP_FILTER_REMOVE;
  } else {
    *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) {
    /* unlink all media managed in this session */
    gst_rtsp_session_filter (session, filter_session_media, client);
  }

  /* 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) {
    /* unlink all media managed in this session. This needs to happen
     * without the client lock, so we really want to do it here. */
    gst_rtsp_session_filter (sess, filter_session_media, user_data);
  }

  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);

  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;

  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);
  gst_rtsp_client_session_filter (client, cleanup_session, &closed);

  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;
}
  0707010000003C000081A400000000000000000000000168EE8797000037F5000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);

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);

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);

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);

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);

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);

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__ */
   0707010000003D000081A400000000000000000000000168EE879700000B43000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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;
}
 0707010000003E000081A400000000000000000000000168EE879700000C28000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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__ */
0707010000003F000081A400000000000000000000000168EE8797000027EF000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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;
}
 07070100000040000081A400000000000000000000000168EE879700000947000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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__ */
 07070100000041000081A400000000000000000000000168EE8797000047BE000000000000000000000000000000000000004000000000gst-rtsp-server-1.26.7/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 };

  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 */
  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);
}

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;
  }
}
  07070100000042000081A400000000000000000000000168EE879700000D4F000000000000000000000000000000000000004000000000gst-rtsp-server-1.26.7/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);

/* 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);

#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__ */
 07070100000043000081A400000000000000000000000168EE87970000F875000000000000000000000000000000000000003C00000000gst-rtsp-server-1.26.7/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;
}
   07070100000044000081A400000000000000000000000168EE879700003583000000000000000000000000000000000000003C00000000gst-rtsp-server-1.26.7/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);

/* 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);

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);

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);

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);

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);

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);

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__ */
 07070100000045000081A400000000000000000000000168EE879700025696000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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_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_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_element_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_element_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_element_state_get_name (old), gst_element_state_get_name (new),
          gst_element_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_element_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_element_state_get_name (state), media,
      gst_element_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;
}
  07070100000046000081A400000000000000000000000168EE87970000462D000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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);

GST_RTSP_SERVER_API
GstElement *          gst_rtsp_media_get_element      (GstRTSPMedia *media);

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);

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);

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);

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);

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);

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);

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__ */
   07070100000047000081A400000000000000000000000168EE87970000292A000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);
}
  07070100000048000081A400000000000000000000000168EE879700000FA0000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);

GST_RTSP_SERVER_API
gchar *               gst_rtsp_mount_points_make_path      (GstRTSPMountPoints *mounts,
                                                            const GstRTSPUrl * url);
/* finding a media factory */

GST_RTSP_SERVER_API
GstRTSPMediaFactory * gst_rtsp_mount_points_match          (GstRTSPMountPoints *mounts,
                                                            const gchar *path,
                                                            gint * matched);
/* 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__ */
07070100000049000081A400000000000000000000000168EE8797000018FA000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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;
}
  0707010000004A000081A400000000000000000000000168EE879700000971000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);

#endif /* __GST_RTSP_ONVIF_CLIENT_H__ */
   0707010000004B000081A400000000000000000000000168EE879700004134000000000000000000000000000000000000004200000000gst-rtsp-server-1.26.7/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);
}
0707010000004C000081A400000000000000000000000168EE8797000012F4000000000000000000000000000000000000004200000000gst-rtsp-server-1.26.7/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);

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);

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__ */
0707010000004D000081A400000000000000000000000168EE879700002BED000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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;
}
   0707010000004E000081A400000000000000000000000168EE879700000AAD000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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__ */
   0707010000004F000081A400000000000000000000000168EE879700000CCF000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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)
{
}
 07070100000050000081A400000000000000000000000168EE879700000A65000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);

#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__ */
   07070100000051000081A400000000000000000000000168EE8797000008D1000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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;
}
   07070100000052000081A400000000000000000000000168EE879700000522000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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__ */
  07070100000053000081A400000000000000000000000168EE8797000029DF000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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;
}
 07070100000054000081A400000000000000000000000168EE879700001232000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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);

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__ */
  07070100000055000081A400000000000000000000000168EE879700004606000000000000000000000000000000000000003200000000gst-rtsp-server-1.26.7/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;
}
  07070100000056000081A400000000000000000000000168EE879700000601000000000000000000000000000000000000003200000000gst-rtsp-server-1.26.7/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__ */
   07070100000057000081A400000000000000000000000168EE879700000E81000000000000000000000000000000000000003E00000000gst-rtsp-server-1.26.7/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__ */
   07070100000058000081A400000000000000000000000168EE879700001FC1000000000000000000000000000000000000003C00000000gst-rtsp-server-1.26.7/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);

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);

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);

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);

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);

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);

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);

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);

GST_RTSP_SERVER_API
GSource *             gst_rtsp_server_create_source        (GstRTSPServer *server,
                                                            GCancellable * cancellable,
                                                            GError **error);

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);

#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__ */
   07070100000059000081A400000000000000000000000168EE879700000668000000000000000000000000000000000000003D00000000gst-rtsp-server-1.26.7/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__ */
0707010000005A000081A400000000000000000000000168EE87970000A25A000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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;
}
  0707010000005B000081A400000000000000000000000168EE8797000006A3000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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__ */
 0707010000005C000081A400000000000000000000000168EE8797000038E1000000000000000000000000000000000000003C00000000gst-rtsp-server-1.26.7/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;

  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);

  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);

  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;
}
   0707010000005D000081A400000000000000000000000168EE87970000132C000000000000000000000000000000000000003C00000000gst-rtsp-server-1.26.7/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);

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);

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);

#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__ */
0707010000005E000081A400000000000000000000000168EE879700004E9E000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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;

  gst_rtsp_session_pool_filter (pool, remove_sessions_func, 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;
}
  0707010000005F000081A400000000000000000000000168EE8797000018FA000000000000000000000000000000000000003B00000000gst-rtsp-server-1.26.7/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);

/* 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);

GST_RTSP_SERVER_API
GstRTSPSession *      gst_rtsp_session_pool_find              (GstRTSPSessionPool *pool,
                                                               const gchar *sessionid);

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);

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);

#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__ */
  07070100000060000081A400000000000000000000000168EE879700005ADD000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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;

  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);
  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);

  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
   07070100000061000081A400000000000000000000000168EE879700001B63000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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);

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);

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);
/**
 * 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);


#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPSession, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_SESSION_H__ */
 07070100000062000081A400000000000000000000000168EE879700006A2D000000000000000000000000000000000000003F00000000gst-rtsp-server-1.26.7/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_LAST
};

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_class_init (GstRTSPStreamTransportClass * klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gst_rtsp_stream_transport_finalize;

  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;
}

/**
 * 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);
}
   07070100000063000081A400000000000000000000000168EE879700002637000000000000000000000000000000000000003F00000000gst-rtsp-server-1.26.7/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);

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);

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__ */
 07070100000064000081A400000000000000000000000168EE87970002B673000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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;

    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;
    }
  }

  if (priv->send_thread == NULL) {
    priv->send_thread = g_thread_new (NULL, (GThreadFunc) send_func, user_data);
  }

  g_mutex_unlock (&priv->lock);

  g_mutex_lock (&priv->send_lock);
  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 G_GNUC_UNUSED;      /* G_DISABLE_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;

  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++;
  g_cond_signal (&priv->send_cond);
  g_mutex_unlock (&priv->send_lock);

  if (priv->send_thread) {
    g_thread_join (priv->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);
}
 07070100000065000081A400000000000000000000000168EE879700003E17000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);

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);

GST_RTSP_SERVER_API
GstPad *          gst_rtsp_stream_get_sinkpad      (GstRTSPStream *stream);

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);

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);

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);

GST_RTSP_SERVER_API
GstRTSPAddress *  gst_rtsp_stream_reserve_address  (GstRTSPStream *stream,
                                                    const gchar * address,
                                                    guint port,
                                                    guint n_ports,
                                                    guint ttl);

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);

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);


GST_RTSP_SERVER_API
GObject *         gst_rtsp_stream_get_rtpsession   (GstRTSPStream *stream);

GST_RTSP_SERVER_API
GstElement *      gst_rtsp_stream_get_srtp_encoder (GstRTSPStream *stream);

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);

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);

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtcp_socket  (GstRTSPStream *stream,
                                                    GSocketFamily family);

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtp_multicast_socket (GstRTSPStream *stream,
                                                            GSocketFamily family);

GST_RTSP_SERVER_API
GSocket *         gst_rtsp_stream_get_rtcp_multicast_socket (GstRTSPStream *stream,
                                                             GSocketFamily family);

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);

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);

GST_RTSP_SERVER_API
GstElement *      gst_rtsp_stream_request_aux_receiver       (GstRTSPStream * stream, guint sessid);

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);

GST_RTSP_SERVER_API
GstElement *       gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream *stream, guint sessid);

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);

#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPStream, gst_object_unref)
#endif

G_END_DECLS

#endif /* __GST_RTSP_STREAM_H__ */
 07070100000066000081A400000000000000000000000168EE879700003DD8000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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);
}
07070100000067000081A400000000000000000000000168EE879700001928000000000000000000000000000000000000003A00000000gst-rtsp-server-1.26.7/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);

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);

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);

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__ */
07070100000068000081A400000000000000000000000168EE879700001FAE000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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;
}
  07070100000069000081A400000000000000000000000168EE879700000E6F000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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);

GST_RTSP_SERVER_API
GstRTSPToken *       gst_rtsp_token_new                (const gchar * firstfield, ...);

GST_RTSP_SERVER_API
GstRTSPToken *       gst_rtsp_token_new_valist         (const gchar * firstfield, va_list var_args);

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__ */
 0707010000006A000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002500000000gst-rtsp-server-1.26.7/gst/rtsp-sink  0707010000006B000081A400000000000000000000000168EE87970002794D000000000000000000000000000000000000003900000000gst-rtsp-server-1.26.7/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)
{
  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 = (gchar *) 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_element_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 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);
        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_element_state_get_name (newstate),
            gst_element_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;
}
   0707010000006C000081A400000000000000000000000168EE879700002027000000000000000000000000000000000000003900000000gst-rtsp-server-1.26.7/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__ */
 0707010000006D000081A400000000000000000000000168EE8797000002B2000000000000000000000000000000000000003100000000gst-rtsp-server-1.26.7/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]
  0707010000006E000081A400000000000000000000000168EE879700000263000000000000000000000000000000000000002E00000000gst-rtsp-server-1.26.7/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)
 0707010000006F000081A400000000000000000000000168EE8797000020F0000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/meson.build    project('gst-rtsp-server', 'c',
  version : '1.26.7',
  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.24.0', meson.project_version())

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: ', ')
07070100000070000081A400000000000000000000000168EE879700000885000000000000000000000000000000000000002900000000gst-rtsp-server-1.26.7/meson_options.txt  # 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')
   07070100000071000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000001F00000000gst-rtsp-server-1.26.7/scripts    07070100000072000081ED00000000000000000000000168EE879700000653000000000000000000000000000000000000004600000000gst-rtsp-server-1.26.7/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))
 07070100000073000081ED00000000000000000000000168EE879700002119000000000000000000000000000000000000003000000000gst-rtsp-server-1.26.7/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',
    'gstreamer-vaapi': 'c89e9afc5d43837c498a55f8f13ddf235442b83b',
    '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')
   07070100000074000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000001D00000000gst-rtsp-server-1.26.7/tests  07070100000075000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/tests/check    07070100000076000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002700000000gst-rtsp-server-1.26.7/tests/check/gst    07070100000077000081A400000000000000000000000168EE87970000284E000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);
  07070100000078000081A400000000000000000000000168EE87970001326C000000000000000000000000000000000000003000000000gst-rtsp-server-1.26.7/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 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);

  return s;
}

GST_CHECK_MAIN (rtspclient);
07070100000079000081A400000000000000000000000168EE879700007330000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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);
0707010000007A000081A400000000000000000000000168EE87970000383D000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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;

  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 (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);
   0707010000007B000081A400000000000000000000000168EE8797000012BC000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);
0707010000007C000081A400000000000000000000000168EE87970000A9E2000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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);
  0707010000007D000081A400000000000000000000000168EE879700001847000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);
 0707010000007E000081A400000000000000000000000168EE879700001FD8000000000000000000000000000000000000003800000000gst-rtsp-server-1.26.7/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);

    gst_rtsp_stream_transport_filter (stream,
        (GstRTSPStreamTransportFilterFunc) check_transport, user_data);
  }
}

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);
0707010000007F000081A400000000000000000000000168EE879700014A5A000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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);
  07070100000080000081A400000000000000000000000168EE879700003223000000000000000000000000000000000000003600000000gst-rtsp-server-1.26.7/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);
 07070100000081000081A400000000000000000000000168EE879700001989000000000000000000000000000000000000003500000000gst-rtsp-server-1.26.7/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);
   07070100000082000081A400000000000000000000000168EE879700005A04000000000000000000000000000000000000003000000000gst-rtsp-server-1.26.7/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);
07070100000083000081A400000000000000000000000168EE879700001A04000000000000000000000000000000000000003400000000gst-rtsp-server-1.26.7/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);
07070100000084000081A400000000000000000000000168EE8797000010CB000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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);
 07070100000085000081A400000000000000000000000168EE8797000008A0000000000000000000000000000000000000002F00000000gst-rtsp-server-1.26.7/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
07070100000086000041ED00000000000000000000000268EE879700000000000000000000000000000000000000000000002300000000gst-rtsp-server-1.26.7/tests/files    07070100000087000081A400000000000000000000000168EE87970005DFEE000000000000000000000000000000000000002C00000000gst-rtsp-server-1.26.7/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      07070100000088000081A400000000000000000000000168EE8797000001D4000000000000000000000000000000000000002900000000gst-rtsp-server-1.26.7/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)
07070100000089000081A400000000000000000000000168EE8797000006EE000000000000000000000000000000000000002C00000000gst-rtsp-server-1.26.7/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;
  }
}
  0707010000008A000081A400000000000000000000000168EE8797000008FF000000000000000000000000000000000000002A00000000gst-rtsp-server-1.26.7/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!!!                                                                                                                                                                        4700 blocks
