diff options
Diffstat (limited to 'subprojects/gst-rtsp-server')
128 files changed, 73730 insertions, 0 deletions
diff --git a/subprojects/gst-rtsp-server/.gitignore b/subprojects/gst-rtsp-server/.gitignore new file mode 100644 index 0000000000..7fa61a72f3 --- /dev/null +++ b/subprojects/gst-rtsp-server/.gitignore @@ -0,0 +1,4 @@ +*~ +/build +/_build +/b/ diff --git a/subprojects/gst-rtsp-server/.gitlab-ci.yml b/subprojects/gst-rtsp-server/.gitlab-ci.yml new file mode 100644 index 0000000000..c61aa7a529 --- /dev/null +++ b/subprojects/gst-rtsp-server/.gitlab-ci.yml @@ -0,0 +1 @@ +include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml" diff --git a/subprojects/gst-rtsp-server/AUTHORS b/subprojects/gst-rtsp-server/AUTHORS new file mode 100644 index 0000000000..ecef28eff3 --- /dev/null +++ b/subprojects/gst-rtsp-server/AUTHORS @@ -0,0 +1 @@ +Wim Taymans <wim.taymans@collabora.co.uk> diff --git a/subprojects/gst-rtsp-server/COPYING b/subprojects/gst-rtsp-server/COPYING new file mode 100644 index 0000000000..efce2a87c3 --- /dev/null +++ b/subprojects/gst-rtsp-server/COPYING @@ -0,0 +1,503 @@ + 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! + diff --git a/subprojects/gst-rtsp-server/COPYING.LIB b/subprojects/gst-rtsp-server/COPYING.LIB new file mode 100644 index 0000000000..efce2a87c3 --- /dev/null +++ b/subprojects/gst-rtsp-server/COPYING.LIB @@ -0,0 +1,503 @@ + 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! + diff --git a/subprojects/gst-rtsp-server/ChangeLog b/subprojects/gst-rtsp-server/ChangeLog new file mode 100644 index 0000000000..aca54deba1 --- /dev/null +++ b/subprojects/gst-rtsp-server/ChangeLog @@ -0,0 +1,14309 @@ +=== release 1.19.2 === + +2021-09-23 01:35:27 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * gst-rtsp-server.doap: + * meson.build: + Release 1.19.2 + +2021-07-05 11:54:18 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-sink/gstrtspclientsink.c: + Protection against early RTCP packets. + When receiving RTCP packets early the funnel is not ready yet and + GST_FLOW_FLUSHING will be returned when pushing data to it's srcpad. + This causes the thread that handle RTCP packets to go to pause mode. + Since this thread is in pause mode there will be no further callbacks to + handle keep-alive for incoming RTCP packets. This will make the session + time out if the client is not using another keep-alive mechanism. + Change-Id: Idb29db05f59c06423fa693a2aeeacbe3a1883fc5 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/211> + +2021-06-21 08:34:35 +0000 Corentin Damman <c.damman@intopix.com> + + * COPYING: + * COPYING.LIB: + Update COPYING.LIB, COPYING files + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/210> + +2021-06-01 15:29:07 +0100 Tim-Philipp Müller <tim@centricular.com> + + * docs/gst_plugins_cache.json: + * meson.build: + Back to development + +=== release 1.19.1 === + +2021-06-01 00:15:08 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * docs/gst_plugins_cache.json: + * gst-rtsp-server.doap: + * meson.build: + Release 1.19.1 + +2021-05-24 18:58:00 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: use new gst_buffer_new_memdup() + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/208> + +2021-05-04 20:47:18 -0400 Doug Nazar <nazard@nazar.ca> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-media: fix leak when adding converter + Free the previous caps before reusing the variable for the converter caps. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/204> + +2021-05-04 20:45:19 -0400 Doug Nazar <nazard@nazar.ca> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: fix leak adding headers + gst_rtsp_message_add_header() makes a copy of the header, instead + of taking ownership. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/204> + +2021-04-21 10:43:41 +0200 François Laignel <fengalin@free.fr> + + * gst/rtsp-server/rtsp-stream.c: + Use gst_element_request_pad_simple... + Instead of the deprecated gst_element_get_request_pad. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/195> + +2021-04-29 03:07:42 -0400 Doug Nazar <nazard@nazar.ca> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Ensure the bus watch is removed during unprepare + It's possible for the destruction of the source to be delayed. + Instead of relying on the dispose() to remove the bus watch, do + it ourselves. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/202> + +2021-04-27 09:22:21 +0200 Marc Leeman <m.leeman@televic.com> + + * docs/README: + docs: minor spelling correction in README + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/200> + +2021-04-27 09:05:39 +0200 Marc Leeman <m.leeman@televic.com> + + * examples/test-replay-server.c: + test-replay-server: minor spelling corrections + Bumped on these while investigating the example code. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/200> + +2021-04-22 23:26:02 -0400 Doug Nazar <nazard@nazar.ca> + + * tests/check/gst/stream.c: + tests: Don't fail tests if IPv6 not available. + On computers with IPv6 disabled it shouldn't result in a test failure. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/196> + +2021-04-23 07:18:48 +0200 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Add one more case to seek avoidance + This is an extension to the previous commit. There can also be cases where the + start position is not specified, in those cases we should also avoid doing + seeking unless it's forced. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/197> + +2021-04-16 14:35:02 -0400 Doug Nazar <nazard@nazar.ca> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Improve skipping trickmode seek. + We can also skip the seek if the end range is already + correct. + Avoids initial seek on play start if playing full stream. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/194> + +2021-03-19 10:36:01 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Don't run signal class handlers during the CLEANUP stage + It's sufficient to run them during the FIRST stage instead of in both. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/193> + +2021-02-15 12:07:15 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspclientsink.c: + tests: rtspclientsink: fix some leaks + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/190> + +2021-02-15 12:26:30 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: mark cached caps as maybe-leaked to make leaks tracer happy + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/190> + +2021-02-15 12:07:45 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspclientsink.c: + rtspclientsink: add unit test for potential shutdown deadlock + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/189> + +2021-02-15 12:01:34 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: fix deadlock on shutdown before preroll + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/130 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/189> + +2021-02-01 12:16:46 +0100 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: avoid deadlock in send_func + Currently the send_func() runs in a thread of its own which is started + the first time we enter handle_new_sample(). It runs in an outer loop + until priv->continue_sending is FALSE, which happens when a TEARDOWN + request is received. We use a local variable, cont, which is initialized + to TRUE, meaning that we will always enter the outer loop, and at the + end of the outer loop we assign it the value of priv->continue_sending. + Within the outer loop there is an inner loop, where we wait to be + signaled when there is more data to send. The inner loop is exited when + priv->send_cookie has changed value, which it does when more data is + available or when a TEARDOWN has been received. + But if we get a TEARDOWN before send_func() is entered we will get stuck + in the inner loop because no one will increase priv->session_cookie + anymore. + By not entering the outer loop in send_func() if priv->continue_sending + is FALSE we make sure that we do not get stuck in send_func()'s inner + loop should we receive a TEARDOWN before the send thread has started. + Change-Id: I7338a0ea60ea435bb685f875965f5165839afa20 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/187> + +2021-01-22 08:58:23 +0100 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: cleanup transports during TEARDOWN + When tunneling RTP over RTSP the stream transports are stored in a hash + table in the GstRTSPClientPrivate struct. They are used for, among other + things, mapping channel id to stream transports when receiving data from + the client. The stream tranports are created and added to the hash table + in handle_setup_request(), but unfortuately they are not removed in + handle_teardown_request(). This means that if the client sends data on + the RTSP connection after it has sent the TEARDOWN, which is often the + case when audio backchannel is enabled, handle_data() will still be able + to map the channel to a session transport and pass the data along to it. + Which eventually leads to a failing assert in gst_rtsp_stream_recv_rtp() + because the stream is no longer joined to a bin. + We avoid this by removing the stream transports from the hash table when + we handle the TEARDOWN request. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/184> + +2020-12-15 11:07:01 +0200 Sebastian Dröge <sebastian@centricular.com> + + * docs/gst_plugins_cache.json: + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Add "update-sdp" signal that allows updating the SDP before sending it to the server + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/178> + +2020-12-23 13:54:54 -0500 John Lindgren <john.lindgren@avasure.com> + + * tests/check/gst/client.c: + Add test cases for mountpoint of '/' + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/168> + +2020-11-05 16:02:49 -0500 John Lindgren <john.lindgren@avasure.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-session-media.c: + Make a mount point of "/" work correctly. + As far as I can tell, this is neither explicitly allowed nor + forbidden by RFC 7826. + Meanwhile, URLs such as rtsp://<IP>:554 or rtsp://<IP>:554/ are in + use in the wild (presumably with non-GStreamer servers). + GStreamer's prior behavior was confusing, in that + gst_rtsp_mount_points_add_factory() would appear to accept a mount + path of "" or "/", but later connection attempts would fail with a + "media not found" error. + This commit makes a mount path of "/" work for either form of URL, + while an empty mount path ("") is rejected and logs a warning. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/168> + +2020-12-15 10:18:16 +0200 Sebastian Dröge <sebastian@centricular.com> + + * docs/gst_plugins_cache.json: + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Use proper types instead of G_TYPE_POINTER for the RTSP messages in the "handle-request" signal + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/177> + +2020-12-17 15:27:27 +0100 Tobias Ronge <tobiasr@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Only count senders when counting blocked streams + Only sender streams sends the GstRTSPStreamBlocking message, so only + these should be counted before setting media status to prepared. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/180> + +2020-10-21 15:38:43 +0200 Jimmi Holst Christensen <jimmi.christensen@aivero.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink add proper support for uri queries + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/166> + +2020-12-14 14:12:38 +1300 Lawrence Troup <lawrence.troup@teknique.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Only unref client watch context on finalize, to avoid deadlock + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/127 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/176> + +2020-11-18 20:36:50 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: collect a clock_rate when blocking + This lets us provide a clock_rate in a fashion similar to the + other code paths in get_rtpinfo() + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/174> + +2020-11-16 10:34:41 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Use guint64 for setting the size-time property on rtpstorage + Otherwise this will cause memory corruption as the property expects a 64 + bit integer. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/169> + +2020-11-03 16:56:28 +0100 David Phung <davidph@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-media: Ignore GstRTSPStreamBlocking from incomplete streams + To prevent cases with prerolling when the inactive stream prerolls first + and the server proceeds without waiting for the active stream, we will + ignore GstRTSPStreamBlocking messages from incomplete streams. When + there are no complete streams (during DESCRIBE), we will listen to all + streams. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/167> + +2020-10-28 21:48:06 +0100 Kristofer Björkström <kristofb@axis.com> + + * tests/check/gst/media.c: + * tests/check/meson.build: + * tests/files/test.avi: + media test: Add test for seeking one active stream with a demuxer + Add another seek_one_active_stream test but with a demuxer. The demuxer + will flush both streams in opposed to the existing test which only + flushes the active stream. This will help exposing problems with the + prerolling process after a flushing seek. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/167> + +2018-10-29 09:19:33 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/meson.build: + * meson.build: + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + * pkgconfig/gstreamer-rtsp-server.pc.in: + * pkgconfig/meson.build: + Meson: Use pkg-config generator + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/1> + +2020-10-19 11:25:25 +0300 Sebastian Dröge <sebastian@centricular.com> + + * meson.build: + meson: update glib minimum version to 2.56 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/164> + +2020-09-04 21:14:35 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-launch.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + rtsp-media-factory: expose API to disable RTCP + This is supported by the RFC, and can be useful on systems where + allocating two consecutive ports is problematic, and RTCP is not + necessary. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/159> + +2020-10-08 23:45:24 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * hooks/pre-commit.hook: + * meson.build: + git: use our standard pre commit hook + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/162> + +2020-10-08 22:17:16 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: make use of blocked_running_time in query_position + When blocking, the sink element will not have received a buffer + yet and the position query will fail. Instead, we make use of + the running time of the buffer we blocked on. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/160> + +2020-10-06 00:04:17 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: collect rtp info when blocking + We don't unblock the stream anymore before replying to the + play request (883ddc72bb5bc57c95a9e167814d1ac53fe1b443), + so the sinks don't have a last-sample after potentially flush + seeking. seek_trickmode waits for preroll however, which means + the stream will block and wait for a first buffer. Subsequent + calls to get_rtpinfo() can thus make use of the information. + See https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/115 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/160> + +2020-09-27 20:09:22 +0900 Seungha Yang <seungha@centricular.com> + + * examples/meson.build: + * examples/test-replay-server.c: + * examples/test-replay-server.h: + examples: Add an example for loop playback + This demo example shows a way of file loop playback of a given source. + Note that client seek request is not properly implemented yet. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/154> + +2020-09-28 22:03:47 +0200 David Phung <davidph@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Plug memory leak + The get-storage signal of rtpbin increases the ref count of the storage. + So we have to unref it after usage. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/155> + +2020-09-11 15:46:41 +0200 Guiqin Zou <guiqinzu@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Get rates only on sender streams + When play a media with both sender and receiver stream, like ONVIF + back channel audio in, gst_rtsp_media_get_rates call + gst_rtsp_stream_get_rates for each stream to set the rates. But + gst_rtsp_stream_get_rates return false for the receiver steam, which + lead a g_assert crash. + Instead to get rates on all streams, now just get rates on sender + streams. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/150> + +2020-09-05 00:30:42 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream.c: + rtsp-media: set a 0 storage size for TCP receivers + ulpfec correction is obviously useless when receiving a stream + over TCP, and in TCP modes the rtp storage receives non + timestamped buffers, causing it to queue buffers indefinitely, + until the queue grows so large that sanity checks kick in and + warnings start to get emitted. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/149> + +2020-08-21 03:02:40 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: preroll on gap events + This allows negotiating a SDP with all streams present, but only + start sending packets at some later point in time + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/146> + +2020-08-25 16:10:36 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: do not unblock on unsuspend + rtsp_media_unsuspend() is called from handle_play_request() + before sending the play response. Unblocking the streams here + was causing data to be sent out before the client was ready + to handle it, with obvious side effects such as initial packets + getting discarded, causing decoding errors. + Instead we can simply let the media streams be unblocked when + the state of the media is set to PLAYING, which occurs after + sending the play response. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/147> + +2020-09-08 17:30:49 +0100 Tim-Philipp Müller <tim@centricular.com> + + * .gitlab-ci.yml: + ci: include template from gst-ci master branch again + +2020-09-08 16:58:58 +0100 Tim-Philipp Müller <tim@centricular.com> + + * docs/gst_plugins_cache.json: + * meson.build: + Back to development + +=== release 1.18.0 === + +2020-09-08 00:08:29 +0100 Tim-Philipp Müller <tim@centricular.com> + + * .gitlab-ci.yml: + * ChangeLog: + * NEWS: + * RELEASE: + * docs/gst_plugins_cache.json: + * gst-rtsp-server.doap: + * meson.build: + Release 1.18.0 + +=== release 1.17.90 === + +2020-08-20 16:15:06 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * docs/gst_plugins_cache.json: + * gst-rtsp-server.doap: + * meson.build: + Release 1.17.90 + +2020-08-03 19:34:30 +0300 Jordan Petridis <jordan@centricular.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + rtsp-thread-pool.c: fix clang 10 warning + clang 10 is complaining about incompatible types due to the + glib typesystem. + ``` + ../subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.c:534:10: error: incompatible pointer types passing 'typeof ((((void *)0))) *' (aka 'void **') to parameter of type 'GThreadPool **' (aka 'struct _GThreadPool **') [-Werror,-Wincompatible-pointer-types] + ``` + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/145> + +2020-08-03 19:34:30 +0300 Jordan Petridis <jordan@centricular.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + rtsp-thread-pool.c: fix clang 10 warning + clang 10 is complaining about incompatible types due to the + glib typesystem. + ``` + ../subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.c:534:10: error: incompatible pointer types passing 'typeof ((((void *)0))) *' (aka 'void **') to parameter of type 'GThreadPool **' (aka 'struct _GThreadPool **') [-Werror,-Wincompatible-pointer-types] + ``` + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/145> + +2020-07-15 11:19:40 +0200 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Fix resource leak in mikey messsage + Fixed a resource leak for mikey message while adding crypto session + failed. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/144> + +2020-07-08 17:28:57 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + * scripts/extract-release-date-from-doap-file.py: + meson: set release date from .doap file for releases + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/143> + +2020-07-02 23:52:47 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: explicitly set caps on udpsrc elements + This causes them to send caps events before data flow, which is + usually a pretty correct thing to do! + Not doing so manifested in a bug where ssrcdemux wouldn't forward + the caps it had received with an extra ssrc field, as it hadn't + received any caps event. + Fixes #85 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/141> + +2020-07-03 02:04:04 +0100 Tim-Philipp Müller <tim@centricular.com> + + * docs/gst_plugins_cache.json: + * meson.build: + Back to development + +=== release 1.17.2 === + +2020-07-03 00:33:54 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * docs/gst_plugins_cache.json: + * gst-rtsp-server.doap: + * meson.build: + Release 1.17.2 + +2020-06-19 22:55:54 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/gst_plugins_cache.json: + doc: Stop documenting properties from parents + +2020-06-22 20:04:45 +0300 Sebastian Dröge <sebastian@centricular.com> + + * docs/gst_plugins_cache.json: + docs: Fix version in the plugins cache + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/138> + +2020-06-22 12:33:32 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Don't call gst_ghost_pad_construct() anymore + It's deprecated, unneeded and doesn't do anything anymore. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/138> + +2020-06-20 00:28:28 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + Back to development + +=== release 1.17.1 === + +2020-06-19 19:24:38 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * docs/gst_plugins_cache.json: + * gst-rtsp-server.doap: + * meson.build: + Release 1.17.1 + +2020-06-15 19:45:38 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Add/configure transports when completing the pipeline + Otherwise the transports are not set up yet during the PLAY request + handling when unsuspending (and thus unblocking) the media. + In case of live pipelines this then causes the first few packets to go + to the sinks before they know what to do with them, and they simply + discard them which is rather suboptimal in case of keyframes. + For non-live pipelines this is not a problem because the sink will still + be PAUSED and as such not send out the data yet but wait until it goes + to PLAYING, which is late enough. + Adding the transports multiple times is not a problem: if the transport + is already added it won't be added another time and TRUE will be + returned. + This fixes a regression introduced by a7732a68e8bc6b4ba15629c652056c240c624ff0 + before 1.14.0. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/107 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/135> + +2020-06-15 19:45:21 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix misleading comment + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/135> + +2020-06-15 18:29:13 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Make sure to also unblock pads when going to PLAYING while buffering + The pad probes are not needed anymore at this point and later when + reaching buffering 100% only the state is changed, no unblocking + happens. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/135> + +2020-06-15 18:17:40 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Remove duplicated media_unblock() function + It does literally the same as media_streams_set_blocked(FALSE). + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/135> + +2020-06-12 15:38:45 +0200 Lenny Jorissen <lennyjorissen@gmail.com> + + * examples/test-onvif-server.c: + test-onvif-server: cast ntp-offset property value to 64 bit + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/134> + +2020-06-09 15:21:24 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/gst_plugins_cache.json: + docs: Update plugins cache + +2020-06-10 13:45:04 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-onvif-server.c: + * examples/test-onvif-server.h: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + onvif-media-factory: define autoptr cleanup function + And have the factory in the onvif-server example inherit from + GstRTSPOnvifMediaFactory. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/133> + +2020-06-08 10:59:34 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/gst_plugins_cache.json: + docs: Update plugins cache + +2020-06-08 09:45:15 +0200 Guillaume Desmottes <guillaume.desmottes@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: enforce I420 format + Test was not enforcing a video format on videotestsrc. I420 was picked as it + was the first format in GST_VIDEO_FORMATS_ALL which will no longer be + true (gst-plugins-base!689). + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/129> + +2020-06-06 00:41:51 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + plugins: uddate gst_type_mark_as_plugin_api() calls + +2020-06-03 18:36:25 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/meson.build: + doc: Require hotdoc >= 0.11.0 + +2020-05-27 17:00:05 +0300 Sebastian Dröge <sebastian@centricular.com> + + * docs/gst_plugins_cache.json: + docs: Update gst_plugins_cache.json + +2020-05-30 23:23:51 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + plugins: Use gst_type_mark_as_plugin_api() for all non-element plugin types + +2020-05-27 23:38:06 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/meson.build: + meson: gir: remove bogus sources_top_dir kwarg + Doesn't actually exist. Was fixed differently in Meson + so that the user doesn't have to specify it. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/127> + +2020-05-27 17:43:43 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/meson.build: + tests: put registry into tests/check not the gst/ subdir + Underscorify the test name before setting GST_REGISTRY, + so the registry actually ends up in the current build dir + and not some subdir. + For consistency with the other modules, but should also + avoid problems on windows. + Also fix indentation of environment block. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/126> + +2020-05-27 17:33:24 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/meson.build: + tests: fix meson test env setup to make sure we use the right gst-plugin-scanner + If core is built as a subproject (e.g. as in gst-build), make sure to use + the gst-plugin-scanner from the built subproject. Without this, gstreamer + might accidentally use the gst-plugin-scanner from the install prefix if + that exists, which in turn might drag in gst library versions we didn't + mean to drag in. Those gst library versions might then be older than + what our current build needs, and might cause our newly-built plugins + to get blacklisted in the test registry because they rely on a symbol + that the wrongly-pulled in gst lib doesn't have. + This should fix running of unit tests in gst-build when invoking + meson test or ninja test from outside the devenv for the case where + there is an older or different-version gst-plugin-scanner installed + in the install prefix. + In case no gst-plugin-scanner is installed in the install prefix, this + will fix "GStreamer-WARNING: External plugin loader failed. This most + likely means that the plugin loader helper binary was not found or + could not be run. You might need to set the GST_PLUGIN_SCANNER + environment variable if your setup is unusual." warnings when running + the unit tests. + In the case where we find GStreamer core via pkg-config we use + a newly-added pkg-config var "pluginscannerdir" to get the right + directory. This has the benefit of working transparently for both + installed and uninstalled pkg-config files/setups. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/126> + +2020-05-27 17:32:02 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/meson.build: + tests: gst-plugins-base and -bad plugins are required for the unit tests + Make hard requirement until we have more fine-grained control + in the unit tests. Of course the presence of the .pc file doesn't + imply that the plugins we need are actually there, but it's at + least a step in the right direction. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/126> + +2020-05-27 17:29:18 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/meson.build: + tests: pick up rtsp-server plugins from build directory only + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/126> + +2020-05-26 15:31:22 +0200 Ludvig Rappe <ludvigr@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: wait for all GstRTSPStreamBlocking messages + Make sure rtsp-media have received a GstRTSPStreamBlocking message from + each active stream when checking if all streams are blocked. + Without this change there will be a race condition when using two or + more streams and rtsp-media receives a GstRTSPStreamBlocking message + from one of the streams. This is because rtsp-media then checks if all + streams are blocked by calling gst_rtsp_stream_is_blocking() for each + stream. This function call returns TRUE if the stream has sent a + GstRTSPStreamBlocking message, however, rtsp-media may have yet to + receive this message. This would then result in that rtsp-media + erroneously thinks it is blocking all streams which could result in + rtsp-media changing state, from PREPARING to PREPARED. In the case of a + preroll, this could result in that rtsp-media thinks that the pipeline + is prerolled even though that might not be the case. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/124> + +2020-05-04 13:43:00 +0200 Ludvig Rappe <ludvigr@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: update expected_async_done during suspend + Set expected_async_done to FALSE in default_suspend() if a state change + occurs and the return value from set_target_state() is something other + than GST_STATE_CHANGE_ASYNC. + Without this change there is a risk that expected_async_done will be + TRUE even though no asynchronous state change is taking place. This + could happen if the pipeline is set to PAUSED using + media_set_pipeline_state_locked(), an asynchronous state change starts + and then the media is suspended (which could result in a state change, + aborting the asynchronous state change). If the media is suspended + before the asynchronous state change ends then expected_async_done will + be TRUE but no asynchronous state change is taking place. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/123> + +2020-05-25 13:49:45 +0200 Kristofer Björkström <kristofb@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix race condition in rtsp ctrl timeout by WeakRef client + There was a race condition where client was being finalized and + concurrently in some other thread the rtsp ctrl timout was relying on + client data that was being freed. + When rtsp ctrl timeout is setup, a WeakRef on Client is set. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/121> + +2015-03-03 14:42:07 +0100 Gregor Boirie <gregor.boirie@parrot.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media-factory: complete DSCP QoS setting support + add dscp_qos setting support at factory and media level to setup IP DSCP + field of bounded UDP sinks. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/6 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/120> + +2020-05-14 10:08:32 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix some race conditions around timeout source removal + We always need to take the lock while accessing it as otherwise another + thread might've removed it in the meantime. Also when destroying and + creating a new one, ensure that the mutex is not shortly unlocked in + between as during that time another one might potentially be created + already. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/119> + +2020-05-03 16:29:31 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-media: Mark out parameters accordingly in gst_rtsp_media_get_rates() + And the same for gst_rtsp_stream_get_rates(). + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/118> + +2020-05-03 10:17:41 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-onvif-server.c: + examples: test-onvif-server: fix compiler warnings on raspbian + Fix printf format for 64-bit variables. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/117> + +2020-05-01 10:42:17 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream-transport: Fix accidental API/ABI breakage with message_sent callbacks + The old API is preserved now and new API was added that provides the + additional parameter to the callback. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/104 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/116> + +2020-04-28 23:33:49 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Store the timeout source by pointer instead of id + That way we don't have to retrieve it again from the main context when + destroying it but can directly do so. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/115> + +2020-04-28 23:16:18 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Clean up watch/watch context and related state consistently + And assert that it was cleaned up properly before the client is + finalized. If something is still around when the client is shut down + then something went very wrong before. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/115> + +2020-04-27 23:25:22 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/rtspserver.c: + rtsp-client: Combine the pre-session and post-session timeout + They previously used the same state but different mechanisms and + functions, which was difficult to follow, error prone and simply + confusing. + Also adjust the test for the post-session timeout a bit to be less racy + now that the timing has slightly changed. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/115> + +2020-04-27 19:47:15 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Don't ever close the client connection directly when a session is torn down + There might be other sessions that are running over the same RTSP + connection and we should not simply close the client directly if one of + them is torn down. + By default the connection will be closed once the client closes it or + the OS does. This behaviour can be adjusted with the + post-session-timeout property, which allows to close it automatically + from the server side after all sessions are gone and the given timeout + is reached. + This reverts the previous commit. + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/115> + +2020-04-27 13:49:55 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: If the TEARDOWN response can be sent directly, directly close the client + Instead of closing it never at all. Previously there was only code that + closed the client asynchronously if sending the response happened + asynchrously at a later time. + Thanks to Christian M for debugging this issue. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/issues/102 + Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/-/merge_requests/114> + +2020-03-23 14:51:28 +0100 Michael Olbrich <m.olbrich@pengutronix.de> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: use mcast_udpsink[0] last-sample if available for rtpinfo + Otherwise no sink is found for multicast sreams and the less accurate + fallback is used to determine the current sequence number and timestamp. + +2020-03-23 16:06:43 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + rtsp-auth: Fix NULL pointer dereference when handling an invalid basic Authorization header + When using the basic authentication scheme, we wouldn't validate that + the authorization field of the credentials is not NULL and pass it on + to g_hash_table_lookup(). g_str_hash() however is not NULL-safe and will + dereference the NULL pointer and crash. + A specially crafted (read: invalid) RTSP header can cause this to + happen. + As a solution, check for the authorization to be not NULL before + continuing processing it and if it is simply fail authentication. + This fixes CVE-2020-6095 and TALOS-2020-1018. + Discovered by Peter Wang of Cisco ASIG. + +2020-03-09 14:17:34 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Use watch_context before unref + Move the usage of priv->watch_context to beginning of function + gst_rtsp_client_finalize. Instead of use it after + g_main_context_unref (priv->watch_context). + +2020-02-14 14:59:43 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fix deadlock on transport removal + We cannot take the RTSPStream lock while holding a transport backlog + lock, as remove_transport may be called externally, which will + take first the RTSPStream lock then the transport backlog lock. + +2020-02-14 14:59:25 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: clear backlog when removing transport + This ensures we don't end up calling any of transports' callbacks + with a potentially unreffed user_data (in practice, a client that + may have been removed) + +2020-02-06 22:46:18 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: marshal calls to send_tcp_message to a single thread + In order to address the race condition pointed out at + https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/merge_requests/108#note_403579 + we get rid of the send thread pool, and instead spawn and manage + a single thread to pull samples from app sinks and add them to + the transport's backlogs. + Additionally, we now also always go through the backlogs in order + to simplify the logic. + +2020-02-05 20:28:19 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: properly protect TCP backlog access + Fixes #97 + We cannot hold stream->lock while pushing data, but need + to consistently check the state of the backlog both from + the send_tcp_message function and the on_message_sent function, + which may or may not be called from the same thread. + This commit introduces internal API to allow for potentially + recursive locking of transport streams, addressing a race + condition where the RTSP stream could push items out of order + when popping them from the backlog. + +2020-02-22 00:41:32 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Sink pipeline in gst_rtsp_media_take_pipeline() + It's taken ownership of by the media, and returned with `transfer none` + from the GstRTSPMedia::create_pipeline() vfunc. If we don't sink it + first then any bindings will wrongly take ownership of the pipeline once + it arrives in bindings code. + +2020-02-05 16:51:14 +0100 Bastian Bouchardon <bastian.bouchardon@gmail.com> + + * examples/test-onvif-client.c: + Add initialization for context and params (gchar *) Insert define (DEFAULT_*) into help to have to modify only the constants + +2020-02-03 12:30:14 +0000 Marc Leeman <marc.leeman@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: fix default latency + +2020-01-15 17:06:41 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: make closing more thread safe + + Take the watch lock prior to using priv->watch + + Flush both the watch and connection before closing / unreffing + gst_rtsp_connection_close() is not threadsafe on its own, this is + a workaround at the client level, where we control both the watch + and the connection + +2020-01-23 16:41:26 +0200 Jordan Petridis <jordan@centricular.com> + + * gst/rtsp-server/rtsp-latency-bin.c: + rtsp-latency-bin: replace G_TYPE_INSTANCE_GET_PRIVATE as it's been deprecated + from glib + ``` + Deprecated: 2.58: Use %G_ADD_PRIVATE and the generated + `your_type_get_instance_private()` function instead + ``` + +2019-12-17 16:08:19 +0100 Zoltán Imets <zoltani@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/rtspserver.c: + rtsp-client: add property post-session-timeout + This is a TCP connection timeout for client connections, in seconds. + If a positive value is set for this property, the client connection + will be kept alive for this amount of seconds after the last session + timeout. For negative values of this property the connection timeout + handling is delegated to the system (just as it was before). + Fixes #83 + +2020-01-11 22:58:48 +0100 Mark Nauwelaerts <mnauw@users.sourceforge.net> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: check for NULL transports prior to ref'ing + +2020-01-09 14:10:44 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fix checking of TCP backpressure + The internal index of our appsinks, while it can be used to + determine whether a message is RTP or RTCP, is not necessarily + the same as the interleaved channel. Let the stream-transport + determine the channel to check backpressure for, the same way + it determines the channel according to whether it is sending + RTP or RTCP. + +2019-12-10 19:16:51 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: Butcher the file to please gst-indent in the CI + This should be reverted once the CI has an updated gst-indent. + +2019-12-10 18:39:32 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + rtsp-session & client: Remove deprecated GTimeVal + GTimeVal won't work past 2038 + +2019-12-12 17:56:18 +0100 Nicola Murino <nicola.murino@gmail.com> + + * gst/rtsp-server/rtsp-auth.c: + rtsp-auth: fix default token leak + +2019-12-09 14:17:05 +0100 Adam x Nilsson <adamni@axis.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + gstrtspclientsink: unref transports when closing bin + Fixes #91 + +2019-12-06 10:44:35 +0100 Kristofer Bjorkstrom <kristofb@pc36402-1937.se.axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Force seek when flush flag is set + The commit "rtsp-client: define all seek accuracy flags from + setup_play_mode" changed the behaviour of when doing a seek. + Before that commit, having the flush flag set would result in a seek + (forced seek). + Even if no seek was needed. One reason to force seek is to flush old buffers + created in Describe requests. + Thus adding force seek also for flush flag will result in play request + with fresh buffers. + +2019-11-21 17:12:45 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Revitalize dead code + Leftover from 65d9aa327cd1844934836249cd4463edf09c725d + CID: 1455379 + +2019-11-27 15:22:35 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Don't try to use non-initialized values + Only attempt to use the various timing values iif gst_rtsp_stream_get_info() + returns TRUE. Also avoid the whole clock signalling block if we're not + dealing with senders. + CID: 1439524 + CID: 1439536 + CID: 1439520 + +2019-11-01 12:01:41 +0100 Adam x Nilsson <adamni@axis.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/stream.c: + rtsp-stream: Removing invalid transports returns false + When removing transports an assertion was that the transports passed in + for removal are present in the list, however that can't be assumed. + As an example if a transport was removed from a thread running + send_tcp_message, the main thread can try to remove the same transport + again if it gets a handle_pause_request. This will not effect the + transport list but it will effect n_tcp_transports as it will be + decrement and then have the wrong value. + +2019-11-06 14:17:48 +0100 Zoltán Imets <zoltani@axis.com> + + * tests/check/gst/client.c: + client test: add scale and speed negative tests + Negative tests for scale and speed should be done as well, verify that + the response code is "400 Bad request" when a bad request is done. + +2019-08-29 07:34:26 +0200 Niels De Graef <nielsdegraef@gmail.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-sink/gstrtspclientsink.c: + Don't pass default GLib marshallers for signals + By passing NULL to `g_signal_new` instead of a marshaller, GLib will + actually internally optimize the signal (if the marshaller is available + in GLib itself) by also setting the valist marshaller. This makes the + signal emission a bit more performant than the regular marshalling, + which still needs to box into `GValue` and call libffi in case of a + generic marshaller. + Note that for custom marshallers, one would use + `g_signal_set_va_marshaller()` with the valist marshaller instead. + +2019-09-05 19:51:06 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-mount-points.c: + GstRTSPMountPoints: Remove any existing factory before adding a new one + The documentation of gst_rtsp_mount_points_add_factory() says "Any + previous mount point will be freed" which was true when it was + implemented using a GHashTable. But in 2012 it got rewrote using a + GSequence and since then it could have 2 factories for the same path. + Which one gets used is random, depending on the sorting order of 2 + identical items. + +2019-10-15 19:08:32 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server-internal.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + stream: refactor TCP backpressure handling + The previous implementation stopped sending TCP messages to + all clients when a single one stopped consuming them, which + obviously created problems for shared media. + Instead, we now manage a backlog in stream-transport, and slow + clients are removed once this backlog exceeds a maximum duration, + currently hardcoded. + Fixes #80 + +2019-10-18 00:42:12 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: build gir even when cross-compiling if introspection was enabled explicitly + This can be made to work in certain circumstances when + cross-compiling, so default to not building g-i stuff + when cross-compiling, but allow it if introspection was + enabled explicitly via -Dintrospection=enabled. + See gstreamer/gstreamer#454 and gstreamer/gstreamer#381. + +2019-10-18 09:19:59 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: clean up comment extra-timeout + +2019-10-17 12:15:42 +0200 Muhammet Ilendemli <mi@tailored-apps.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Generate correct URI for MIKEY in ANNOUNCE responses + Instead of hardcoding the URI, take the actual URI (and especially the correct port) + from the RTSP context. + Fixes #84 + +2019-10-16 13:20:54 +0000 Kristofer <kristofer.bjorkstrom@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp-client: Lock shared media + For shared media we got race conditions. Concurrently rtsp clients might + suspend or unsuspend the shared media and thus change the state without + the clients expecting that. + By introducing a lock that can be taken by callers such as rtsp_client + one can force rtsp clients calling, eg. PLAY, SETUP and that uses shared media, + to handle the media sequentially thus allowing one client to finish its + rtsp call before another client calls on the same media. + https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/86 + Fixes #86 + +2019-10-15 07:33:29 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: add property 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. + +2019-10-07 12:13:47 +0200 Adam x Nilsson <adamni@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream : fix race condition in send_tcp_message + If one thread is inside the send_tcp_message function and are done + sending rtp or rtcp messages so the n_outstanding variable is zero + however have not exit the loop sending the messages. While sending its + messages, transports have been added or removed to the transport list, + so the cache should be updated. If now an additional thread comes to + the function send_tcp_message and trying to send rtp messages it will + first destroy the rtp cache that is still being iterated trough by the + first thread. + Fixes #81 + +2019-05-24 14:32:50 +0200 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + * .gitmodules: + * Makefile.am: + * autogen.sh: + * common: + * configure.ac: + * docs/.gitignore: + * examples/.gitignore: + * examples/Makefile.am: + * gst/Makefile.am: + * gst/rtsp-server/.gitignore: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-sink/Makefile.am: + * pkgconfig/.gitignore: + * pkgconfig/Makefile.am: + * tests/.gitignore: + * tests/Makefile.am: + * tests/check/Makefile.am: + Remove autotools build + Replaced by Meson. + Maybe we can now use the meson pkgconfig module + for .pc files? (Does it support uninstalled now?) + +2019-10-07 10:27:36 +0200 Göran Jönsson <goranjn@axis.com> + + * tests/check/gst/client.c: + client: fix test mem leak in attach_rate_tweaking_probe + +2019-10-07 10:14:52 +0200 Göran Jönsson <goranjn@axis.com> + + * tests/check/gst/media.c: + media: remove memleak in test test_media_seek + +2019-10-07 10:07:54 +0200 Göran Jönsson <goranjn@axis.com> + + * tests/check/gst/rtspserver.c: + rtspserver: Remove memleak in test test_double_play + +2019-09-17 13:45:57 +0200 Adam x Nilsson <adamni@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Use lock in gst_rtsp_media_is_receive_only + +2018-10-29 17:02:41 +0100 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/rtspserver.c: + rtsp-media: Unblock all streams + When unsuspending and going to PLAYING, unblock all streams instead of + only those that are linked (the linked streams are the ones for which + SETUP has been called). GST_FLOW_NOT_LINKED will be returned when + pushing buffers on unlinked streams. + This change is because playback using single-threaded demuxers like + matroska-demux could be blocked if SETUP was not called for all media. + Demuxers that use GstFlowCombiner (including gstoggdemux, gstavidemux, + gstflvdemux, qtdemux, and matroska-demux) will handle + GST_FLOW_NOT_LINKED automatically. + Fixes #39 + +2019-09-11 07:08:37 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/rtspserver.c: + rtsp-media: Wait on async when needed. + Wait on asyn-done when needed in gst_rtsp_media_seek_trickmode. + In the unit test the pause from adjust_play_mode will cause a preroll + and after that async-done will be produced. + Without this patch there are no one consuming this async-done and when + later when seek fluch is done in gst_rtsp_media_seek_trickmode then it + wait for async-done. But then it wrongly find the async-done prodused by + adjus_play_mode and continue executing without waiting for the preroll + to finish. + +2019-09-30 15:13:15 +0200 Kristofer Bjorkstrom <kristofb@pc36402-1937.se.axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: RTP Info when completed_sender + Change condition that should be fulfilled regarding RTPInfo. + Replace !gst_rtsp_media_is_receive_only with + gst_rtsp_media_has_completed_sender. It is more correct to actually look + for a sender pipeline that is complete. Only then a RTPInfo should + exist. + gst_rtsp_media_is_receive_only gives different answears depending on + state of server. + If Describe is called wth URL+options for backchannel SDP will give only + audio and only backchannel a=sendonly + If Describe is called on URL+options that gives both audio and video + direction from server to client, pipelines are created. Thus + receive_only will return false, even though Setup only would setup + backchannel. + RTP-Info is only for outgoing streams. Thus one should look if outgoing + streams are complete. + +2019-09-25 09:14:08 +0000 Kristofer <kristofer.bjorkstrom@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + rtsp-client: RTP Info exists conditionally in PLAY + If RTP Info is missing and it is not a receiver only, eg. audio + backchannel. Then return GST_RTSP_STS_INTERNAL_SERVER_ERROR. + In rfc2326 it says RTP-info is req. but in RFC7826 it is conditional. + Since 1.14 there is audio backchannel support. Thus RTP-info is + conditional now. When audio backchannel only mode, there is no RTP-info. + Fixes #82 + +2019-09-05 16:23:26 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-onvif-client.c: + test-onvif-client: remove unused query + +2019-08-30 14:00:52 +0200 Kristofer Björkström <kristofb@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: RTP Info must exist in PLAY response + If RTP Info is missing. Then return GST_RTSP_STS_INTERNAL_SERVER_ERROR + Fixes #76 + +2019-08-29 21:37:24 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-onvif-client.c: + test-onvif-client: perform accurate seeks + See https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/merge_requests/336 + Also, modify how we compute the position: position queries in + PAUSED mode fail to account for the newly-prerolled frame, leading + to frame skips when performing seeks in that state. Instead, + compute the current position from the last sample. + +2019-08-21 14:57:25 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * tests/check/gst/rtspserver.c: + Use complete streams for scale and speed. + Without this patch it's always stream0 that is used to get segment event + that is used to set scale and speed. This even if client not doing SETUP + for stream0. At least in suspend mode reset this not working since then + it's just random if send_rtp_sink have got any segment event. There are + no check if send_rtp_sink for stream0 got any data before media is + prerolled after PLAY request. + +2019-08-26 22:24:12 +1000 Matthew Waters <matthew@centricular.com> + + * examples/test-onvif-server.c: + * examples/test-onvif-server.h: + examples/onvif-server: fix werror build with clang + ../subprojects/gst-rtsp-server/examples/test-onvif-server.c:346:65: warning: implicit conversion from enumeration type 'const GstSegmentFlags' to different enumeration type 'GstSeekFlags' [-Wenum-conversion] + self->incoming_segment->format, self->incoming_segment->flags, + ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~ + ../subprojects/gst-rtsp-server/examples/test-onvif-server.c:53:1: warning: unused function 'REPLAY_IS_BIN' [-Wunused-function] + G_DECLARE_FINAL_TYPE (ReplayBin, replay_bin, REPLAY, BIN, GstBin); + ^ + /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \ + ^ + <scratch space>:77:1: note: expanded from here + REPLAY_IS_BIN + ^ + ../subprojects/gst-rtsp-server/examples/test-onvif-server.c:525:1: warning: unused function 'ONVIF_FACTORY' [-Wunused-function] + G_DECLARE_FINAL_TYPE (OnvifFactory, onvif_factory, ONVIF, FACTORY, + ^ + /usr/include/glib-2.0/gobject/gtype.h:1405:33: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) { \ + ^ + <scratch space>:9:1: note: expanded from here + ONVIF_FACTORY + ^ + ../subprojects/gst-rtsp-server/examples/test-onvif-server.c:525:1: warning: unused function 'ONVIF_IS_FACTORY' [-Wunused-function] + /usr/include/glib-2.0/gobject/gtype.h:1407:26: note: expanded from macro 'G_DECLARE_FINAL_TYPE' + static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \ + ^ + <scratch space>:12:1: note: expanded from here + ONVIF_IS_FACTORY + ^ + +2019-08-23 16:21:36 +1000 Matthew Waters <matthew@centricular.com> + + * docs/meson.build: + meson: Don't generate doc cache when no plugins are enabled + Fixes gst-build with -Dauto-features=disabled -Drtsp_server=enabled + +2019-08-16 13:38:01 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * examples/test-onvif-client.c: + test-onvif-client: stdin is not defined in MSVC + +2019-08-12 18:03:36 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: add missing Since tag + +2019-08-08 15:52:53 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-onvif-client.c: + test-onvif-client: STDIN_FILENO is not portable + If not defined, define it to _fileno(stdin) on Windows, 0 + everywhere else + +2019-08-07 21:04:33 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/test-onvif-server.c: + test-onvif-server: downgrade logging + +2019-07-27 05:14:49 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/meson.build: + * examples/test-onvif-client.c: + * examples/test-onvif-server.c: + examples: add ONVIF client / server example + +2019-07-27 05:14:28 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-client: define all seek accuracy flags from setup_play_mode + We then pass those to adjust_play_mode, which needs to operate + on the "final" seek flags, as previously the code in rtsp-media + was assuming that accuracy seek flags (accurate / key_unit) should + not be set if the flags passed to the seek method were already set. + +2019-07-22 19:32:43 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Try to get dynamic payloaders by name from their bin first + First try "pay", then "pay_%s" (where %s == pad name). And only then + fall back to the code that simply takes the first payloader that is + found. + The current code usually works (but is racy) because it will always take + the payloader that was last added (due to g_list_prepend() when adding + elements) in pad-added and that's usually the correct one. But if a new + payloader is added between pad-added and us trying to get it, we would + get the wrong payloader. + +2019-07-17 15:51:08 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * tests/check/gst/client.c: + client test: expect any port in transport + setup_multicast_client sets a 5000-5010 range for the client + ports, it is incorrect to expect the transport to always use + 5000-5001 + Fixes #73 + +2019-07-15 17:06:42 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * tests/check/gst/onvif.c: + onvif tests: use g_cond_wait() correctly + g_cond_wait() has to be called in a loop until required conditions + are met + Fixes #71 + +2019-06-28 12:28:41 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Not wait on receiver streams when pre-rolling + Without this patch there are problem pre-rolling when using audio back + channel. + Without this patch a probe will be created for all streams including + the stream for audio backchannel. To pre-roll all this pads have to + receive data. Since the stream for audio backchannel is a receiver this + will never happen. + The solution is to never create any probes for streams that are for + incomming data and instead set them as blocking already from beginning. + +2019-06-25 13:19:44 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-onvif-media-factory.c: + * gst/rtsp-server/rtsp-onvif-media.c: + onvif-media: fix "void function returning a value" compiler warning + +2019-06-12 22:19:27 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: make sure streams are blocked when sending seek + The recent ONVIF work exposed a race condition when dealing with + multiple streams: one of the sinks may preroll before other streams + have started flushing. This led to the pipeline posting async-done + prematurely, when some streams were actually still in the middle + of performing a flushing seek. The newly-added code looks up a + sticky segment event on the first stream in order to respond to + the PLAY request with accurate Scale and Speed headers. In the + failure condition, the first stream was flushing, and thus had + no sticky segment event, leading to the PLAY request failing, + and in turn the test. + +2019-06-07 10:51:19 +0200 Michael Bunk <bunk@iat.uni-leipzig.de> + + * docs/README: + * gst/rtsp-server/rtsp-media-factory-uri.h: + Fix typos + +2019-04-05 00:48:07 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-onvif-client.c: + * gst/rtsp-server/rtsp-onvif-client.h: + * gst/rtsp-server/rtsp-onvif-media-factory.c: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + * gst/rtsp-server/rtsp-onvif-media.c: + * gst/rtsp-server/rtsp-onvif-server.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/media.c: + * tests/check/gst/onvif.c: + * tests/check/meson.build: + onvif: Implement and test the Streaming Specification + https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf + +2018-11-05 15:34:20 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: add gst_rtsp_client_get_stream_transport() + This will be used in the onvif tests in order to validate the + data transmitted over TCP: for streaming to continue after a + data message has been provided to client->send_func, the client + is responsible for marking the message as sent on the relevant + stream transport. + +2018-11-07 00:33:01 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + client: Scale implies TRICK_MODE + +2018-11-07 00:32:29 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + client: compare booleans, not pointers to them + +2018-11-13 21:28:45 +0100 Nikita Bobkov <NikitaDBobkov@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/media.c: + Reverse playback support + GStreamer plays segment from stop to start when doing reverse playback. + RTSP implies that media should be played from start of Range header to + its stop. Hence we swap start and stop times before passing them to + gst_element_seek. + Also make gst_rtsp_stream_query_stop always return value that can be + used as stop time of Range header. + +2018-10-12 08:53:04 +0200 Branko Subasic <branko@subasic.net> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * tests/check/gst/client.c: + rtsp-client: add support for Scale and Speed header + Add support for the RTSP Scale and Speed headers by setting the rate in + the seek to (scale*speed). We then check the resulting segment for rate + and applied rate, and use them as values for the Speed and Scale headers + respectively. + https://bugzilla.gnome.org/show_bug.cgi?id=754575 + +2018-10-01 18:51:49 +0200 Branko Subasic <branko@subasic.net> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: allow sub classes to adjust the seek + Adds a new virtual function, adjust_play_mode(), that allows + sub classes to adjust the seek done on the media. The sub class can + modify the values of the the seek flags and the rate. + https://bugzilla.gnome.org/show_bug.cgi?id=754575 + +2018-09-27 19:09:01 +0200 Branko Subasic <branko@subasic.net> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/media.c: + rtsp-media: allow specifying rate when seeking + Add new function gst_rtsp_media_seek_full_with_rate() which allows the + caller to specify the rate for the seek. Also added functions in + rtsp-stream and rtsp-media for retreiving current rate and applied rate. + https://bugzilla.gnome.org/show_bug.cgi?id=754575 + +2019-06-02 21:39:33 +0200 Niels De Graef <niels.degraef@barco.com> + + * configure.ac: + * meson.build: + meson: Bump minimal GLib version to 2.44 + This means we can use some newer features and get rid of some + boilerplate code using the G_DECLARE_* macros. + As discussed on IRC, 2.44 is old enough by now to start depending on it. + +2019-05-31 18:53:36 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/.gitignore: + * docs/libs/Makefile.am: + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * docs/libs/gst-rtsp-server.types: + docs: remove obsolete gtk-doc related files + +2019-05-29 23:20:09 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + doc: remove xml from comments + +2019-05-16 09:23:53 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/gst_plugins_cache.json: + * docs/meson.build: + docs: Stop building the doc cache by default + And update the cache + Fixes https://gitlab.freedesktop.org/gstreamer/gst-docs/issues/36 + +2019-05-13 22:59:57 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/gst_plugins_cache.json: + docs: Update plugins documentation cache + +2019-04-23 12:30:02 -0400 Thibault Saunier <tsaunier@igalia.com> + + * docs/meson.build: + * gst/rtsp-server/rtsp-context.c: + * gst/rtsp-server/rtsp-session-pool.c: + doc: Fix some docstrings + +2018-10-22 11:29:24 +0200 Thibault Saunier <tsaunier@igalia.com> + + * .gitignore: + * Makefile.am: + * configure.ac: + * docs/Makefile.am: + * docs/gst_plugins_cache.json: + * docs/index.md: + * docs/meson.build: + * docs/plugin-index.md: + * docs/plugin-sitemap.txt: + * docs/sitemap.md: + * docs/sitemap.txt: + * docs/version.entities.in: + * gst/rtsp-server/meson.build: + * gst/rtsp-sink/meson.build: + * meson.build: + * meson_options.txt: + docs: Port to hotdoc + +2019-04-23 15:09:34 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-server: Fix various Since markers + +2019-04-23 15:01:32 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: Add various Since: 1.14 markers + +2019-04-23 14:38:05 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: Add various missing Since: 1.16 markers + +2019-04-15 20:54:24 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Set async-handling=false for the internal bins + Without this we can easily run into a race condition with async state changes: + - the pipeline is doing an async state change + - we set the internal bins to PLAYING but that's ignored because an + async state change is currently pending + - the async state change finishes but does not change the state of the + internal bins because of locked_state==TRUE + - the internal bins stay in PAUSED forever + +2019-04-15 20:51:30 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Use write_messages() API to send buffer lists in one go + And to write messages with multiple memories also via writev(). + +2019-03-27 16:21:03 +0100 Kristofer Bjorkstrom <kristofb@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server-object.h: + * gst/rtsp-server/rtsp-server.c: + rtsp-client: Handle Content-Length limitation + Add functionality to limit the Content-Length. + API addition, Enhancement. + Define an appropriate request size limit and reject requests + exceeding the limit with response status 413 Request Entity Too Large + Related to !182 + +2019-04-19 10:40:29 +0100 Tim-Philipp Müller <tim@centricular.com> + + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +=== release 1.16.0 === + +2019-04-19 00:34:54 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.16.0 + +2019-04-15 20:33:01 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Notify the stream transport about each written message + Otherwise it will never try to send us the next one: it tries to keep + exactly one message in-flight all the time. + In gst-rtsp-server this is done asynchronously via the GstRTSPWatch but + in the client sink we always write data out synchronously. + +2019-04-02 08:05:03 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp_server: Free thread pool before clean transport cache + If not waiting for free thread pool before clean transport caches, there + can be a crash if a thread is executing in transport list loop in + function send_tcp_message. + Also add a check if priv->send_pool in on_message_sent to avoid that a + new thread is pushed during wait of free thread pool. This is possible + since when waiting for free thread pool mutex have to be unlocked. + +=== release 1.15.90 === + +2019-04-11 00:35:55 +0100 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.15.90 + +2019-04-10 10:32:53 +0200 Ulf Olsson <ulfo@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Add support for GCM (RFC 7714) + Follow-up to !198 + +2019-03-28 00:27:37 +0100 Erlend Eriksen <erlend_ne@hotmail.com> + + * gst/rtsp-server/rtsp-session-pool.c: + session pool: fix missing klass-> in klass->create_session + +2019-03-23 19:16:17 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + g-i: pass --quiet to g-ir-scanner + This suppresses the annoying 'g-ir-scanner: link: cc ..' output + that we get even if everything works just fine. + We still get g-ir-scanner warnings and compiler warnings if + we pass this option. + +2019-03-23 19:15:48 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + g-i: silence 'nested extern' compiler warnings when building scanner binary + We need a nested extern in our init section for the scanner binary + so we can call gst_init to make sure GStreamer types are initialised + (they are not all lazy init via get_type functions, but some are in + exported variables). There doesn't seem to be any other mechanism to + achieve this, so just remove that warning, it's not important at all. + +2019-03-21 11:49:10 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: pass -Wno-unused to compiler if gstreamer debug system is disabled + +2019-03-14 07:37:26 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + rtsp-media: Handle set state when preparing. + Handle the situation when a call to gst_rtsp_media_set_state is done + when media status is preparing. + Also add unit test for this scenario. + The unit test simulate on a media level when two clients share a (live) + media. + Both clients have done SETUP and got responses. Now client 1 is doing + play and client 2 is just closing the connection. + Then without patch there are a problem when + client1 is calling gst_rtsp_media_unsuspend in handle_play_request. + And client2 is doing closing connection we can end up in a call + to gst_rtsp_media_set_state when + priv->status == GST_RTSP_MEDIA_STATUS_PREPARING and all the logic for + shut down media is jumped over . + With this patch and this scenario we wait until + priv->status == GST_RTSP_MEDIA_STATUS_PREPARED and then continue to + execute after that and now we will execute the logic for + shut down media. + +2019-03-04 09:13:30 +0000 Tim-Philipp Müller <tim@centricular.com> + + * NEWS: + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +=== release 1.15.2 === + +2019-02-26 11:58:53 +0000 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.15.2 + +2019-02-19 09:45:08 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/client.c: + rtsp-media: Fix multicast use case with common media + Use case + client 1: SETUP + client 1: PLAY + client 2: SETUP + client 1: TEARDOWN + client 2: PLAY + client 2: TEARDOWN + +2019-01-16 12:59:11 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-server: remove recursive behavior + Introduce a threadpool to send rtp and rtcp to avoid recursive behavior. + +2019-01-25 14:22:42 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Only allow to set either a send_func or send_messages_func but not both + And route all messages through the send_func if no send_messages_func + was provided. + We otherwise break backwards compatibility. + +2018-09-17 22:18:46 +0300 Sebastian Dröge <sebastian@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-stream.c: + rtsp-client: Add support for sending buffer lists directly + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/29 + +2018-06-27 12:17:07 +0200 Sebastian Dröge <sebastian@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-sink/gstrtspclientsink.c: + rtsp-server: Add support for buffer lists + This adds new functions for passing buffer lists through the different + layers without breaking API/ABI, and enables the appsink to actually + provide buffer lists. + This should already reduce CPU usage and potentially context switches a + bit by passing a whole buffer list from the appsink instead of + individual buffers. As a next step it would be necessary to + a) Add support for a vector of data for the GstRTSPMessage body + b) Add support for sending multiple messages at once to the + GstRTSPWatch and let it be handled internally + c) Adding API to GOutputStream that works like writev() + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/29 + +2018-12-04 14:12:04 +0100 Benjamin Berg <bberg@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: Fix crash in close handler + The close handler could trigger a crash because it invalidated the + watch_context while still leaving a source attached to it which would be + cleaned up at a later point. + +2019-01-29 14:42:35 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Use cached address when allocating sockets + If an address/port was previously decided upon (ex: multicast in the + SDP), then use that instead of re-creating another one + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/57 + +2018-12-27 11:28:17 +0100 Lars Wiréen <larswi@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix race codition in finish_unprepare + The previous fix for race condition around finish_unprepare where the + function could be called twice assumed that the status wouldn't change + during execution of the function. This assumption is incorrect as the + state may change, for example if an error message arrives from the + pipeline bus. + Instead a flag keeping track on whether the finish_unprepare function + is currently executing is introduced and checked. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/59 + +=== release 1.15.1 === + +2019-01-17 02:26:48 +0000 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.15.1 + +2018-12-05 15:07:25 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + Add source elements to the pipeline before activation + In plug_src we changed the element state before adding it to + the owner container. This prevented the pipeline from intercepting + a GST_STREAM_STATUS_TYPE_CREATE message from the pad in order + to assign a custom task pool. + Fixes https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/53 + +2018-12-05 17:24:59 -0300 Thibault Saunier <tsaunier@igalia.com> + + * common: + Automatic update of common submodule + From ed78bee to 59cb678 + +2018-11-20 19:12:09 +0100 Ingo Randolf <ingo.randolf@servus.at> + + * examples/test-appsrc.c: + examples: test-appsrc: fix coding style error + +2018-11-20 11:07:48 +0100 Ingo Randolf <ingo.randolf@servus.at> + + * examples/test-appsrc.c: + examples: test-appsrc: fix buffer leak + +2018-11-17 19:19:54 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Update priv->blocked when linked streams are unblocked. + Media is considered to be blocked when all streams that belong to + that media are blocked. + This patch solves the problem of inconsistent updates of + priv->blocked that are not synchronized with the media state. + +2018-11-17 18:18:27 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't block streams before seeking + Before the seek operation is performed on media, it's required that + its pipeline is prepared <=> the pipeline is in the PAUSED state. + At this stage, all transport parts (transport sinks) have been successfully + added to the pipeline and there is no need for blocking the streams. + +2018-11-17 16:11:53 +0100 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/rtspserver.c: + tests: rtspserver: Add shared media test case for TCP + +2018-11-06 18:21:54 +0100 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Use seqnum-offset for rtpinfo + The sequence number in the rtpinfo is supposed to be the first RTP + sequence number. The "seqnum" property on a payloader is supposed to be + the number from the last processed RTP packet. The sequence number for + payloaders that inherit gstrtpbasepayload will not be correct in case of + buffer lists. In order to fix the seqnum property on the payloaders + gst-rtsp-server must get the sequence number for rtpinfo elsewhere and + "seqnum-offset" from the "stats" property contains the value of the + very first RTP packet in a stream. The server will, however, try to look + at the last simple in the sink element and only use properties on the + payloader in case there no sink elements yet, and by looking at the last + sample of the sink gives the server full control of which RTP packet it + looks at. If the payloader does not have the "stats" property, "seqnum" + is still used since "seqnum-offset" is only present in as part of + "stats" and this is still an issue not solved with this patch. + Needed for gst-plugins-base!17 + +2018-11-06 18:10:56 +0100 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Plug memory leak + Attaching a GSource to a context will increase the refcount. The idle + source will never be free'd since the initial reference is never + dropped. + +2018-11-12 16:06:39 +0200 Jordan Petridis <jordan@centricular.com> + + * .gitlab-ci.yml: + Add Gitlab CI configuration + This commit adds a .gitlab-ci.yml file, which uses a feature + to fetch the config from a centralized repository. The intent is + to have all the gstreamer modules use the same configuration. + The configuration is currently hosted at the gst-ci repository + under the gitlab/ci_template.yml path. + Part of https://gitlab.freedesktop.org/gstreamer/gstreamer-project/issues/29 + +2018-11-05 05:56:35 +0000 Matthew Waters <matthew@centricular.com> + + * .gitmodules: + * gst-rtsp-server.doap: + Update git locations to gitlab + +2018-11-01 14:20:16 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/meson.build: + meson: add new onvif types + +2018-11-01 12:49:51 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/meson.build: + Add ONVIF subclass headers to the installed headers in meson.build too + +2018-11-01 11:29:01 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-server-object.h: + * gst/rtsp-server/rtsp-server.h: + rtsp-server: Declare GstRTSPServer struct before anything else + It's needed by all kinds of other headers, including the ones that are + required for defining the GstRTSPServer struct itself and its API. + +2018-11-01 10:23:02 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-onvif-client.h: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + * gst/rtsp-server/rtsp-onvif-media.h: + * gst/rtsp-server/rtsp-onvif-server.h: + Mark all ONVIF-specific subclasses as Since 1.14 + +2018-11-01 10:18:22 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/meson.build: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-onvif-server.c: + * gst/rtsp-server/rtsp-onvif-server.h: + * gst/rtsp-server/rtsp-server-object.h: + * gst/rtsp-server/rtsp-server-prelude.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session.h: + Include ONVIF types from single-include rtsp-server.h + ... by actually making it a single-include header and moving everything + related to the GstRTSPServer type to rtsp-server-object.h instead. + Otherwise there are too many circular includes. + https://bugzilla.gnome.org/show_bug.cgi?id=797361 + +2018-10-18 07:25:05 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-latency-bin.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: use idle source in on_message_sent + When the underlying layers are running on_message_sent, this sometimes + causes the underlying layer to send more data, which will cause the + underlying layer to run callback on_message_sent again. This can go on + and on. + To break this chain, we introduce an idle source that takes care of + sending data if there are more to send when running callback + https://bugzilla.gnome.org/show_bug.cgi?id=797289 + +2018-10-20 16:14:53 +0200 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Remove timeout GSource on cleanup + Avoids ending up with races where a timeout would still be around + *after* a client was gone. This could happen rather easily in + RTSP-over-HTTP mode on a local connection, where each RTSP message + would be sent as a different HTTP connection with the same tunnelid. + If not properly removed, that timeout would then try to free again + a client (and its contents). + +2018-10-04 14:31:59 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/Makefile.am: + autotools: fix distcheck + +2018-09-12 11:55:15 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/meson.build: + * gst/rtsp-server/rtsp-latency-bin.c: + * gst/rtsp-server/rtsp-latency-bin.h: + * gst/rtsp-server/rtsp-onvif-media.c: + onvif: encapsulate onvif part into a bin + ...and thus do not let onvif affect pipelines latency + https://bugzilla.gnome.org/show_bug.cgi?id=797174 + +2018-09-27 19:57:13 +0200 Patricia Muscalu <patricia@dovakhiin.com> + + * tests/check/gst/client.c: + tests: client: Avoid bind() failures in tests + https://bugzilla.gnome.org/show_bug.cgi?id=797059 + +2018-09-06 16:17:33 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/mediafactory.c: + New property for socket binding to mcast addresses + By default the multicast sockets are bound to INADDR_ANY, + as it's not allowed to bind sockets to multicast addresses + in Windows. This default behaviour can be changed by setting + bind-mcast-address property on the media-factory object. + https://bugzilla.gnome.org/show_bug.cgi?id=797059 + +2018-09-24 09:36:21 +0100 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/meson.build: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-context.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-server-prelude.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-token.c: + * meson.build: + libs: fix API export/import and 'inconsistent linkage' on MSVC + Export rtsp-server library API in headers when we're building the + library itself, otherwise import the API from the headers. + This fixes linker warnings on Windows when building with MSVC. + Fix up some missing config.h includes when building the lib which + is needed to get the export api define from config.h + https://bugzilla.gnome.org/show_bug.cgi?id=797185 + +2018-09-19 14:31:56 +0200 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: Add missing break statements + This resulted in warnings/assertions whenever one accessed the + max-mcast-ttl property. + CID #1439515 + CID #1439523 + +2018-09-19 12:21:30 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + * meson_options.txt: + meson: add gobject-cast-checks, glib-asserts, glib-checks options + +2018-09-19 12:17:57 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/meson.build: + * meson_options.txt: + * tests/check/meson.build: + meson: add option to disable build of rtspclientsink plugin + +2018-09-19 12:10:14 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson_options.txt: + meson: re-arrange options + +2018-09-01 11:21:15 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * meson.build: + * meson_options.txt: + * tests/check/meson.build: + * tests/meson.build: + meson: Use feature option for tests option + This was somehow missed the last time around. + +2018-08-31 14:42:15 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * gst/rtsp-server/meson.build: + * meson.build: + meson: Maintain macOS ABI through dylib versioning + Requires Meson 0.48, but the feature will be ignored on older versions + so it's safe to add it without bumping the requirement. + Documentation: + https://github.com/mesonbuild/meson/blob/master/docs/markdown/Reference-manual.md#shared_library + +2018-08-31 17:20:47 +1000 Matthew Waters <matthew@centricular.com> + + * gst/rtsp-sink/meson.build: + * meson.build: + meson: add pkg-config file for the rtspclientsink plugin + +2018-08-17 09:54:27 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + rtsp-client: Avoid reuse of channel numbers for interleaved + If a (strange) client would reuse interleaved channel numbers in + multiple SETUP requests, we should not accept them. The channel + numbers are used for looking up stream transports in the + priv->transports hash table, and transports disappear from the table + if channel numbers are reused. + RFC 7826 (RTSP 2.0), Section 18.54, clarifies that it is OK for the + server to change the channel numbers suggested by the client. + https://bugzilla.gnome.org/show_bug.cgi?id=796988 + +2018-08-17 09:54:27 +0200 David Svensson Fors <davidsf@axis.com> + + * tests/check/gst/client.c: + rtsp-client: Add unit test of SETUP for RTSP/RTP/TCP + Allow regex for matching transport header against expected pattern. + https://bugzilla.gnome.org/show_bug.cgi?id=796988 + +2018-08-15 18:57:27 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * tests/check/meson.build: + meson: There is no gstreamer-plugins-good-1.0.pc + There is no installed version of that, only an uninstalled version. + +2018-08-14 14:31:55 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/stream.c: + Fix indentation again + +2018-07-26 12:01:16 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/stream.c: + stream: Added a list of multicast client addresses + When media is shared, the same media stream can be sent + to multiple multicast groups. Currently, there is no API + to retrieve multicast addresses from the stream. + When calling gst_rtsp_stream_get_multicast_address() function, + only the first multicast address is returned. + With this patch, each multicast destination requested in SETUP + will be stored in an internal list (call to + gst_rtsp_stream_add_multicast_client_address()). + The list of multicast groups requested by the clients can be + retrieved by calling gst_rtsp_stream_get_multicast_client_addresses(). + There still exist some problems with the current implementation + in the multicast case: + 1) The receiving part is currently only configured with + regard to the first multicast client (see + https://bugzilla.gnome.org/show_bug.cgi?id=796917). + 2) Secondly, of security reasons, some constraints should be + put on the requested multicast destinations (see + https://bugzilla.gnome.org/show_bug.cgi?id=796916). + Change-Id: I6b060746e472a0734cc2fd828ffe4ea2956733ea + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-25 15:33:18 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + stream: Choose the maximum ttl value provided by multicast clients + The maximum ttl value provided so far by the multicast clients + will be chosen and reported in the response to the current + client request. + Change-Id: I5408646e3b5a0a224d907ae215bdea60c4f1905f + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-02-23 14:34:32 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + rtsp-stream: Don't require address pool in the transport specific case + If "transport.client-settings" parameter is set to true, the client is + allowed to specify destination, ports and ttl. + There is no need for pre-configured address pool. + Change-Id: I6ae578fb5164d78e8ec1e2ee82dc4eaacd0912d1 + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-24 14:02:40 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + client: Don't reserve multicast address in the client setting case + When two multicast clients request specific transport + configurations, and "transport.client-settings" parameter is + set to true, it's wrong to actually require that these two + clients request the same multicast group. + Removed test_client_multicast_invalid_transport_specific test + cases as they wrongly require that the requested destination + address is supposed to be present in the address pool, also in + the case when "transport.client-settings" parameter is set to true. + Change-Id: I4580182ef35996caf644686d6139f72ec599c9fa + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-24 09:35:46 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/mediafactory.c: + Add new API for setting/getting maximum multicast ttl value + Change-Id: I5ef4758188c14785e17fb8fbf42a3dc0cb054233 + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-31 21:17:41 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: avoid duplicating the first multicast client + In dcb4533fedae3ac62bc25a916eb95927b7d69aec , we made it so + clients were dynamically added and removed to the multicast + udp sinks, as such we should no longer add a first client in + set_multicast_socket_for_udpsink + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-08-14 14:25:53 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + Revert "rtsp-stream: avoid duplicating the first multicast client" + This reverts commit 33570944401747f44d8ebfec535350651413fb92. + Commits where accidentially squashed together + +2018-08-14 14:25:42 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/mediafactory.c: + Revert "Add new API for setting/getting maximum multicast ttl value" + This reverts commit 7f0ae77e400fb8a0462a76a5dd2e63e12c4a2e52. + Commits where accidentially squashed together + +2018-08-14 14:25:37 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + Revert "rtsp-stream: Don't require address pool in the transport specific case" + This reverts commit a9db3e7f092cfeb5475e9aa24b1e91906c141d52. + Commits where accidentially squashed together + +2018-08-14 14:25:14 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/stream.c: + Revert "stream: Choose the maximum ttl value provided by multicast clients" + This reverts commit 499e437e501215849d24cdaa157e0edf4de097d0. + Commits where accidentially squashed together + +2018-08-14 14:10:56 +0300 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-auth-digest.c: + examples: Fix indentation + +2018-07-25 15:33:18 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/stream.c: + stream: Choose the maximum ttl value provided by multicast clients + The maximum ttl value provided so far by the multicast clients + will be chosen and reported in the response to the current + client request. + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-02-23 14:34:32 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + rtsp-stream: Don't require address pool in the transport specific case + If "transport.client-settings" parameter is set to true, the client is + allowed to specify destination, ports and ttl. + There is no need for pre-configured address pool. + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-24 09:35:46 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/mediafactory.c: + Add new API for setting/getting maximum multicast ttl value + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-07-31 21:17:41 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: avoid duplicating the first multicast client + In dcb4533fedae3ac62bc25a916eb95927b7d69aec , we made it so + clients were dynamically added and removed to the multicast + udp sinks, as such we should no longer add a first client in + set_multicast_socket_for_udpsink + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-08-06 15:33:04 -0400 Thibault Saunier <tsaunier@igalia.com> + + * gst/rtsp-server/Makefile.am: + rtsp-server: Add gstreamer-base gir dir in autotools + +2018-07-25 19:54:55 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-client: always allocate both IPV4 and IPV6 sockets + multiudpsink does not support setting the socket* properties + after it has started, which meant that rtsp-server could no + longer serve on both IPV4 and IPV6 sockets since the patches + from https://bugzilla.gnome.org/show_bug.cgi?id=757488 were + merged. + When first connecting an IPV6 client then an IPV4 client, + multiudpsink fell back to using the IPV6 socket. + When first connecting an IPV4 client, then an IPV6 client, + multiudpsink errored out, released the IPV4 socket, then + crashed when trying to send a message on NULL nevertheless, + that is however a separate issue. + This could probably be fixed by handling the setting of + sockets in multiudpsink after it has started, that will + however be a much more significant effort. + For now, this commit simply partially reverts the behaviour + of rtsp-stream: it will continue to only create the udpsinks + when needed, as was the case since the patches were merged, + it will however when creating them, always allocate both + sockets and set them on the sink before it starts, as was + the case prior to the patches. + Transport configuration will only error out if the allocation + of UDP sockets fails for the actual client's family, this + also downgrades the GST_ERRORs in alloc_ports_one_family + to GST_WARNINGs, as failing to allocate is no longer + necessarily fatal. + https://bugzilla.gnome.org/show_bug.cgi?id=796875 + +2018-07-25 17:22:20 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * meson.build: + * meson_options.txt: + meson: Convert common options to feature options + These are necessary for gst-build to set options correctly. The + remaining automagic option is cgroup support in examples. + https://bugzilla.gnome.org/show_bug.cgi?id=795107 + +2018-07-23 18:03:51 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Slightly simplify locking + +2018-06-28 11:22:21 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + Limit queued TCP data messages to one per stream + Before, the watch backlog size in GstRTSPClient was changed + dynamically between unlimited and a fixed size, trying to avoid both + unlimited memory usage and deadlocks while waiting for place in the + queue. (Some of the deadlocks were described in a long comment in + handle_request().) + In the previous commit, we changed to a fixed backlog size of 100. + This is possible, because we now handle RTP/RTCP data messages differently + from RTSP request/response messages. + The data messages are messages tunneled over TCP. We allow at most one + queued data message per stream in GstRTSPClient at a time, and + successfully sent data messages are acked by sending a "message-sent" + callback from the GstStreamTransport. Until that ack comes, the + GstRTSPStream does not call pull_sample() on its appsink, and + therefore the streaming thread in the pipeline will not be blocked + inside GstRTSPClient, waiting for a place in the queue. + pull_sample() is called when we have both an ack and a "new-sample" + signal from the appsink. Then, we know there is a buffer to write. + RTSP request/response messages are not acked in the same way as data + messages. The rest of the 100 places in the queue are used for + them. If the queue becomes full of request/response messages, we + return an error and close the connection to the client. + Change-Id: I275310bc90a219ceb2473c098261acc78be84c97 + +2018-06-28 11:22:13 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Use fixed backlog size + Change to using a fixed backlog size WATCH_BACKLOG_SIZE. + Preparation for the next commit, which changes to a different way of + avoiding both deadlocks and unlimited memory usage with the watch + backlog. + +2018-07-16 21:57:08 +0200 Carlos Rafael Giani <dv@pseudoterminal.org> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: unref clock (if set) when finalizing + https://bugzilla.gnome.org/show_bug.cgi?id=796814 + +2018-07-16 21:56:44 +0200 Carlos Rafael Giani <dv@pseudoterminal.org> + + * docs/libs/gst-rtsp-server-sections.txt: + rtsp-media: add gst_rtsp_media_*_set_clock to docs + https://bugzilla.gnome.org/show_bug.cgi?id=796814 + +2018-07-12 19:01:54 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: unref old clock when setting new clock + https://bugzilla.gnome.org/show_bug.cgi?id=796724 + +2018-06-29 15:20:57 -0700 Brendan Shanks <brendan.shanks@teradek.com> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: unref clock in finalize + https://bugzilla.gnome.org/show_bug.cgi?id=796724 + +2018-07-12 18:57:21 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-onvif-media.c: + rtsp-onvif-media: fix g-ir-scanner warnings + +2018-07-10 23:56:23 +0100 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + .gitignore: add another example binary + +2018-07-10 23:55:20 +0100 Tim-Philipp Müller <tim@centricular.com> + + * examples/meson.build: + meson: add new test-appsrc2 example to meson build + +2018-07-10 23:53:41 +0100 Tim-Philipp Müller <tim@centricular.com> + + * examples/Makefile.am: + examples: fix build of new test-appsrc2 example + Need to link against libgstapp-1.0. + +2018-07-11 01:25:51 +1000 Jan Schmidt <jan@centricular.com> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-appsrc2.c: + examples: Add test-appsrc2 + Add an example of feeding both audio and video into an RTSP + pipeline via appsrc. + +2016-01-08 18:12:14 -0500 Louis-Francis Ratté-Boulianne <lfrb@collabora.com> + + * gst/rtsp-server/rtsp-client.c: + client: Strip transport parts as whitespaces could be around commas + https://bugzilla.gnome.org/show_bug.cgi?id=758428 + +2018-06-27 08:30:42 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: avoid pushing data on unlinked udpsrc pad during setup + Fix race when setting up source elements. + Since we set the source element(s) to PLAYING state before hooking + them up to the downstream funnel, it's possible for the source element + to receive packets before we actually get to linking it to the funnel, + in which case buffers would be pushed out on an unlinked pad, causing + it to error out and stop receiving more data. + We fix this by blocking the source's srcpad until we have linked it. + https://bugzilla.gnome.org/show_bug.cgi?id=796160 + +2018-03-21 10:56:51 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix mismatch between allowed and configured protocols + https://bugzilla.gnome.org/show_bug.cgi?id=796679 + +2017-02-01 09:44:50 +0100 Ulf Olsson <ulfo@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Emit a signal when the SRTP decoder is created + https://bugzilla.gnome.org/show_bug.cgi?id=778080 + +2018-03-13 11:10:35 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Don't require presence of sinks in _get_*_socket() + Transport specific sink elements are added to the pipeline + in PLAY request and sockets are already created in SETUP so + it's actually wrong to require the presence of sinks in + _get_*_socket() functions. + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-02-14 10:41:02 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Update transport for multicast clients as well + If a multicast client requests different transport settings + than the existing one make sure that this new transport + configuruation is propagated to the multicast udp sink. + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-02-13 11:04:36 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Set the multicast TTL parameter on multicast udp sinks + And not on unicast udp sinks + https://bugzilla.gnome.org/show_bug.cgi?id=793441 + +2018-06-24 12:44:26 +0200 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-thread-pool.c: + Update for g_type_class_add_private() deprecation in recent GLib + +2018-06-24 12:45:49 +0200 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-stream.c: + Fix indentation + +2018-06-22 23:17:08 +1000 Jan Schmidt <jan@centricular.com> + + * examples/Makefile.am: + * examples/test-video-disconnect.c: + examples: Add test-video-disconnect example + Simple example which cuts off all clients 10 seconds + after the first one connects. + +2018-06-20 04:37:11 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * examples/test-auth-digest.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + rtsp-auth: Add support for parsing .htdigest files + Passwords are usually not stored in clear text, but instead + stored already hashed in a .htdigest file. + Add support for parsing such files, add API to allow setting + a custom realm in RTSPAuth, and update the digest example. + https://bugzilla.gnome.org/show_bug.cgi?id=796637 + +2018-06-19 14:53:02 +1000 Matthew Waters <matthew@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + rtspclientsink: fix waiting for multiple streams + We were previously only ever waiting for a single stream to notify it's + blocked status through GstRTSPStreamBlocking. Actually count streams to + wait for. + Fixes rtspclientsink sending SDP's without out some of the input + streams. + https://bugzilla.gnome.org/show_bug.cgi?id=796624 + +2018-06-20 04:30:04 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + docs: add missing auth methods + +2018-06-20 00:10:18 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: only create funnel if it didn't exist already. + This precented using multiple protocols for the same stream. + https://bugzilla.gnome.org/show_bug.cgi?id=796634 + +2018-06-20 01:35:47 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * examples/meson.build: + meson: build auth-digest example + +2018-06-05 08:44:44 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + Get payloader stats only for the sending streams + Get/set payloader properties only for streams that actually + contain a payloader element. + https://bugzilla.gnome.org/show_bug.cgi?id=796523 + +2018-05-18 14:53:49 +0200 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/Makefile.am: + Makefile: Don't hardcode libtool for g-i build + Similar to the other commits in core/base/bad + +2018-05-08 14:13:31 +0200 Johan Bjäreholt <johanbj@axis.com> + + * gst/rtsp-server/rtsp-onvif-media-factory.h: + rtsp-onvif-media-factory: export gst_rtsp_onvif_media_factory_requires_backchannel + https://bugzilla.gnome.org/show_bug.cgi?id=796229 + +2018-05-09 04:09:02 +1000 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Don't deadlock in preroll on early close + If the connection is closed very early, the flushing + marker might not get set and rtspclientsink can get + deadlocked waiting for preroll forever. + https://bugzilla.gnome.org/show_bug.cgi?id=786961 + +2018-05-05 19:51:52 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * meson.build: + * meson_options.txt: + meson: Update option names to omit disable_ and with- prefixes + Also yield common options to the outer project (gst-build in our case) + so that they don't have to be set manually. + +2018-04-25 11:00:32 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: use -Wl,-Bsymbolic-functions where supported + Just like the autotools build. + +2018-04-22 20:09:01 +0100 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + * tests/check/Makefile.am: + configure: check for -good and -bad plugins only in uninstalled setup + Avoids confusing configure messages looking or a -good .pc file + that doesn't exist. + Also use plugindir variables that common macros set while at it. + https://bugzilla.gnome.org/show_bug.cgi?id=795466 + +2018-04-17 11:03:11 +0200 Joakim Johansson <joakimj@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix session timeout + When streaming data over TCP then is not the keep-alive + functionality working. + The reason is that the function do_send_data have changed + to boolean but the code is still checking the received result + from send_func with GST_RTSP_OK. + The result is that a successful send_func will always lead to + that do_send_data is returning false and the keep-alive will + not be updated. + https://bugzilla.gnome.org/show_bug.cgi?id=795321 + +2018-04-02 22:49:35 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + Implement support for ULP Forward Error Correction + In this initial commit, interface is only exposed for RECORD, + further work will be needed in rtspsrc to support this for + PLAY. + https://bugzilla.gnome.org/show_bug.cgi?id=794911 + +2018-04-17 17:47:30 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-onvif-media.c: + Revert "rtsp-server: Switch around sendonly/recvonly attributes" + This reverts commit 3d275b1345b76151418e3f56ed014d9089ac1a57. + While RFC 3264 (SDP) says that sendonly/recvonly are from the point of view of + the requester, the actual RTSP RFCs (RFC 2326 / 7826) disagree and say + the opposite, just like the ONVIF standard. + Let's follow those RFCs as we're doing RTSP here, and add a property at + a later time if needed to switch to the SDP RFC behaviour. + https://bugzilla.gnome.org/show_bug.cgi?id=793964 + +2018-04-16 10:53:52 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From 3fa2c9e to ed78bee + +2018-04-04 10:06:06 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/rtspclientsink.c: + gst: Run everything through gst-indent again + +2018-04-03 08:57:47 +0200 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + rtsp-media: query the position on active streams if media is complete + 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 incomplete stream may return a position that originates from + an earlier preroll. + https://bugzilla.gnome.org/show_bug.cgi?id=794964 + +2018-04-02 12:35:04 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: make sure not to use freed string + Set transport string to NULL after freeing it, so that + at worst we get a NULL pointer if constructing a new + transport string fails (which shouldn't really fail here). + Also check return value of that, just in case. + CID 1433768. + +2018-03-30 23:34:01 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: do not free string passed to take_header + +2018-03-30 23:10:10 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: do not take lock in request_aux_receiver + Added it right before pushing the previous commit, it is + incorrect and deadlocks because this function gets called + from the join_bin thread, which already holds the lock, + that's the reason why request_aux_sender didn't take the + lock either. + +2018-03-29 22:49:26 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-server: add API to enable retransmission requests + "do-retransmission" was previously set when rtx-time != 0, + which made no sense as do-retransmission is used to enable + the sending of retransmission requests, where as rtx-time + is used by the peer to enable storing of buffers in order + to respond to retransmission requests. + rtsp-media now also provides a callback for the + request-aux-receiver signal. + https://bugzilla.gnome.org/show_bug.cgi?id=794822 + +2018-03-29 16:18:42 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: add rtx ssrc to mikey's crypto sessions + https://bugzilla.gnome.org/show_bug.cgi?id=794813 + +2018-03-29 16:15:45 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Handle the KeyMgmt header in ANNOUNCE response + This in order to be able to decrypt the RTCP backchannel + https://bugzilla.gnome.org/show_bug.cgi?id=794813 + +2018-03-29 16:12:26 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Send KeyMgmt header in ANNOUNCE response + When sending back an encrypted RTCP back channel, it is useful + for the client to know the encryption key. + https://bugzilla.gnome.org/show_bug.cgi?id=794813 + +2018-03-29 16:06:31 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: extract handle_keymgmt from rtsp-client + rtspclientsink will also need to parse KeyMgmt headers + sent by the server to decrypt the RTCP backchannel stream + https://bugzilla.gnome.org/show_bug.cgi?id=794813 + +2018-03-29 02:51:02 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * tests/check/gst/rtspclientsink.c: + rtspclientsink: Fix client ports for the RTCP backchannel + This was broken since the work for delayed transport creation + was merged: 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. + https://bugzilla.gnome.org/show_bug.cgi?id=794789 + +2018-02-15 13:26:16 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client:Error handling when equal http session cookie + There are some clients that are sending same session cookie on random + basis. + https://bugzilla.gnome.org/show_bug.cgi?id=753616 + +2018-03-20 16:21:37 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-media-factory-uri: Fix compilation with latest GLib + rtsp-media-factory-uri.c: In function ‘rtsp_media_factory_uri_create_element’: + rtsp-media-factory-uri.c:621:17: error: assignment from incompatible pointer type [-Werror=incompatible-pointer-types] + data->factory = g_object_ref (factory); + ^ + +2018-03-20 10:21:36 +0000 Tim-Philipp Müller <tim@centricular.com> + + * NEWS: + * RELEASE: + * configure.ac: + * meson.build: + Back to development + +=== release 1.14.0 === + +2018-03-19 20:27:04 +0000 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.14.0 + +=== release 1.13.91 === + +2018-03-13 19:28:33 +0000 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.13.91 + +2018-03-13 13:30:41 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/meson.build: + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-onvif-client.h: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + * gst/rtsp-server/rtsp-onvif-media.h: + * gst/rtsp-server/rtsp-onvif-server.h: + * gst/rtsp-server/rtsp-params.h: + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-server-prelude.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-server/rtsp-thread-pool.h: + * gst/rtsp-server/rtsp-token.h: + rtsp-server: GST_EXPORT -> GST_RTSP_SERVER_API + We need different export decorators for the different libs. + For now no actual change though, just rename before the release, + and add prelude headers to define the new decorator to GST_EXPORT. + +2018-03-07 12:20:05 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-onvif-media-factory.c: + rtsp-onvif-media-factory: Document that backchannel pipelines must end with async=false sinks + https://bugzilla.gnome.org/show_bug.cgi?id=794143 + +=== release 1.13.90 === + +2018-03-03 22:49:34 +0000 Tim-Philipp Müller <tim@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.13.90 + +2018-03-02 16:24:23 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-permissions.c: + permissions: add Since tags and example for new API + +2018-03-02 01:36:23 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + * tests/check/gst/permissions.c: + permissions: more bindings-friendly API + https://bugzilla.gnome.org/show_bug.cgi?id=793975 + +2018-03-01 19:28:16 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * meson.build: + meson: enable more warnings + +2018-02-28 21:12:43 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Place netaddress meta on packets received via TCP + This allows us to later map signals from rtpbin/rtpsource back to the + corresponding stream transport, and allows to do keep-alive based on + RTCP packets in case of TCP media transport. + https://bugzilla.gnome.org/show_bug.cgi?id=789646 + +2018-02-27 20:34:49 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: if OPEN failed, unqueue next command + As READY_TO_PAUSED can no longer return async, the RECORD + command will be queued before the OPEN command fails + (for example in case the server could not be connected), + and record then waits for ever. + https://bugzilla.gnome.org/show_bug.cgi?id=793896 + +2018-02-26 22:59:17 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: fix retrieval of custom payloader caps + If a bin is passed as the custom payloader, the caps of + its factory will be empty, the correct way to obtain the caps + is to query its sinkpad. + +2018-02-26 22:59:00 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: fix extra unref of custom payloader + +2018-02-26 22:57:39 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rspclientsink: fix recent code indentation + +2018-02-26 20:27:57 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: add missing get_type prototype + +2018-02-24 03:52:15 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: allow setting payloader as pad property + This was a FIXME item, and can be quite useful, also + allowing to specify payloader properties from the command + line, which is always nice. + https://bugzilla.gnome.org/show_bug.cgi?id=793776 + +2018-02-26 14:16:54 +0100 Carlos Rafael Giani <dv@pseudoterminal.org> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Replace g_print() log line + https://bugzilla.gnome.org/show_bug.cgi?id=793838 + +2018-02-22 20:17:33 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/rtspclientsink.c: + rtsp-media: fix RECORD getting stuck + The test_record case was working because async=false had + been added in https://bugzilla.gnome.org/show_bug.cgi?id=757488 + but that was incorrect, as it should not be needed. + Removing async=false made the test fail as expected, this is + fixed by not trying to preroll when preparing the media for + RECORD, as start_prepare is called upon receiving ANNOUNCE, + and our peer will not start sending media until it has received + a response to that request, and sent and received a response + to RECORD as well, thus obviously preventing preroll. + https://bugzilla.gnome.org/show_bug.cgi?id=793738 + +2018-02-23 03:26:21 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + rtsp-auth: fix set_tls_authentication_mode annotation + +2018-02-19 11:57:29 +0100 VÃctor Manuel Jáquez Leal <vjaquez@igalia.com> + + * gst/rtsp-server/rtsp-onvif-media.c: + rtp-server: remove redefined variable + res is a boolean variable which is defined in the function scope and + redefined, with no reason, in the loop scope. This patch removes the + redefinition. + https://bugzilla.gnome.org/show_bug.cgi?id=793592 + +2018-02-05 11:49:07 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: Add functions for checking if stream is receiver or sender + ...and replace all checks for RECORD in GstRTSPMedia which are really + for "sender-only". This way the code becomes more generic and introducing + support for onvif-backchannel later on will require no changes in + GstRTSPMedia. + +2017-10-21 14:06:30 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-onvif-media-factory.c: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + onvif: Make requires_backchannel() public + ...in order to let subclasses building the onvif part of the pipeline + check whether backchannel shall be included or not. + +2018-01-22 12:46:34 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-onvif-media.c: + rtsp-server: Switch around sendonly/recvonly attributes + They are wrong in the ONVIF streaming spec. The backchannel should be + recvonly and the normal media should be sendonly: direction is always + from the point of view of the SDP offerer (the server) according to + RFC 3264. + +2017-09-25 19:41:05 +0300 Sebastian Dröge <sebastian@centricular.com> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-onvif-backchannel.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-onvif-client.c: + * gst/rtsp-server/rtsp-onvif-client.h: + * gst/rtsp-server/rtsp-onvif-media-factory.c: + * gst/rtsp-server/rtsp-onvif-media-factory.h: + * gst/rtsp-server/rtsp-onvif-media.c: + * gst/rtsp-server/rtsp-onvif-media.h: + * gst/rtsp-server/rtsp-onvif-server.c: + * gst/rtsp-server/rtsp-onvif-server.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + rtsp: Add support for ONVIF backchannel + This adds a new RTSP server, client, media-factory and media subclass + for handling the specifics of the backchannel. Ideally this later can be + extended with other ONVIF specific features. + +2017-10-12 21:00:16 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Add support for sending+receiving medias + We need to add an appsrc/appsink in that case because otherwise the + media bin will be a sink and a source for rtpbin, causing a pipeline + loop. + https://bugzilla.gnome.org/show_bug.cgi?id=788950 + +2018-02-15 19:44:28 +0000 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + * meson.build: + Back to development + +=== release 1.13.1 === + +2018-02-15 17:15:40 +0000 Tim-Philipp Müller <tim@centricular.com> + + * NEWS: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.13.1 + +2018-02-14 17:11:19 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-session-pool.c: + session-pool: remove nullable return annotation + create_watch can only return NULL from the API guards, no + need for nullable. + +2018-02-13 18:59:16 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + set_clock functions: Add nullable annotations + +2018-02-10 00:07:25 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-thread-pool.c: + All around: add annotations and API guards + +2018-02-12 19:12:35 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * tests/test-cleanup.c: + test-cleanup: bind any port + The meson test suite runs tests in parallel, trying to bind + a single port made the test fail. + +2018-02-08 19:15:10 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: make version numbers ints and fix int/string comparison + WARNING: Trying to compare values of different types (str, int). + The result of this is undefined and will become a hard error + in a future Meson release. + +2018-02-06 18:00:33 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-context.c: + gst_rtsp_context_get_current: add (skip) annotation + The return value type is defined with G_DEFINE_POINTER_TYPE, + and gi emits the following warning: + Invalid non-constant return of bare structure or union; register as + boxed type or (skip) + +2018-02-06 17:58:49 +0100 Mathieu Duponchelle <mathieu@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: add type annotations + gi doesn't seem to be able to figure out the type of the + signal parameters when defined with G_DEFINE_POINTER_TYPE + +2018-02-04 12:24:09 +0100 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + autotools: use -fno-strict-aliasing where supported + https://bugzilla.gnome.org/show_bug.cgi?id=769183 + +2018-01-30 20:35:21 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: use -fno-strict-aliasing where supported + https://bugzilla.gnome.org/show_bug.cgi?id=769183 + +2018-01-25 12:09:03 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-mount-points.c: + mount-points: bail out of loop again when matching mount points + Previous patch led to us iterating the entire sequence. Bail out + of the loop again if we have a match but are moving away from it. + https://bugzilla.gnome.org/show_bug.cgi?id=771555 + +2018-01-25 12:06:57 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/mountpoints.c: + tests: mountpoints: add more checks for mount point path matching + https://bugzilla.gnome.org/show_bug.cgi?id=771555 + +2016-09-16 20:41:19 +0000 Andrew Bott <andrew.bott@blackmoth.com> + + * gst/rtsp-server/rtsp-mount-points.c: + mount-points: fix matching of paths where there's also an entry with a common prefix + e.g. with the following mount points + /raw + /raw/snapshot + /raw/video + _match() would not match /raw/video and /raw/snapshot correctly. + https://bugzilla.gnome.org/show_bug.cgi?id=771555 + +2018-01-18 23:53:20 +0000 Tim-Philipp Müller <tim@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + * tests/check/gst/permissions.c: + permissions: add some new API to make this usable from bindings + https://bugzilla.gnome.org/show_bug.cgi?id=787073 + +2018-01-18 11:32:32 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-token.c: + rtsp-token: annotate constructors for bindings + This maps _new_empty() to _new(), which also makes RTSPToken() + work properly now. Since this API wasn't usable from bindings + before, this should hopefully be fine. + https://bugzilla.gnome.org/show_bug.cgi?id=787073 + +2018-01-18 11:07:45 +0000 Tim-Philipp Müller <tim@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-token.c: + * gst/rtsp-server/rtsp-token.h: + * tests/check/gst/token.c: + rtsp-token: add some API to set fields from bindings + The existing functions are all vararg-based and as such + not usable from bindings. + https://bugzilla.gnome.org/show_bug.cgi?id=787073 + +2018-01-13 15:02:28 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspclientsink.c: + * tests/check/gst/rtspserver.c: + * tests/check/gst/sessionpool.c: + * tests/check/gst/stream.c: + tests: fix indentation + Fix and "fix". + +2018-01-13 14:58:55 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspserver.c: + tests: rtspserver: fix another ref leak + Even if this didn't show up in valgrind. + +2018-01-13 14:58:00 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspclientsink.c: + tests: rtspclientsink: fix leak + +2018-01-02 14:19:31 +0100 Branko Subasic <branko@axis.com> + + * tests/check/gst/rtspserver.c: + test: rtspserver: plug memory leak in test_no_session_timeout + In test_no_session_timeout, unref the rtsp session object when the + test is done. + https://bugzilla.gnome.org/show_bug.cgi?id=792127 + +2017-12-20 14:17:02 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtpsclientsink: Initialize and clear newly added mutex and cond + While it *did* work, glib would automatically create new mutex and cond + ... which never got freed + +2017-12-19 11:34:37 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Set multicast TTL on the multicast sockets + And not if we do unicast UDP. + https://bugzilla.gnome.org/show_bug.cgi?id=791743 + +2017-12-19 11:14:48 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Decide based on the sockets, not the addresses if we already allocated a socket + In the multicast case (as in test-multicast, not test-multicast2), the + address could be allocated/reserved (and thus set) already without + allocating the actual socket. We need to allocate the socket here still + instead of just claiming that it was already allocated. + See https://bugzilla.gnome.org/show_bug.cgi?id=791743#c2 + +2017-12-16 21:46:53 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + rtspclientsink: Use the new rtsp-stream API + https://bugzilla.gnome.org/show_bug.cgi?id=790412 + +2017-12-16 21:01:43 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + rtspclientsink: Wait until OPEN has been scheduled + Make sure that the sink thread has started opening connection + to the server before continuing. + https://bugzilla.gnome.org/show_bug.cgi?id=790412 + +2017-12-14 14:53:35 +1100 Matthew Waters <matthew@centricular.com> + + * common: + Automatic update of common submodule + From e8c7a71 to 3fa2c9e + +2017-12-07 16:08:29 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: Minor doc fixes + Mostly for g-i + +2017-12-06 20:47:22 +0000 Tim-Philipp Müller <tim@centricular.com> + + * Makefile.am: + * tests/Makefile.am: + tests: disable all tests when --disable-tests is used + Move conditional subdir include into top level. + Based on patch by: Joel Holdsworth + https://bugzilla.gnome.org/show_bug.cgi?id=757703 + +2017-12-06 20:42:39 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + * meson_options.txt: + * tests/meson.build: + meson: build more tests and add options to disable tests and examples + +2017-11-26 13:26:39 -0300 Thibault Saunier <tsaunier@gnome.org> + + * gst/rtsp-server/rtsp-session.c: + Fix build when -Werror=deprecated-declarations is on + As gst_rtsp_session_next_timeout is deprecated. + ``` + ../subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.c:760:3: error: ‘gst_rtsp_session_next_timeout’ is deprecated: Use 'gst_rtsp_session_next_timeout_usec' instead [-Werror=deprecated-declarations] + res = (gst_rtsp_session_next_timeout (session, now) == 0); + ^~~ + ../subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.c:685:1: note: declared here + gst_rtsp_session_next_timeout (GstRTSPSession * session, GTimeVal * now) + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ``` + +2017-11-27 20:18:24 +1100 Matthew Waters <matthew@centricular.com> + + * common: + Automatic update of common submodule + From 3f4aa96 to e8c7a71 + +2017-11-25 20:34:16 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * tests/check/gst/media.c: + check/media: Add seekability test case: not all streams are active + Media contains two streams but only one is complete and prepared + for playing. + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-25 20:32:02 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Do not reset 'blocking' if stream is already blocked + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-25 20:45:44 +0100 Patricia Muscalu <patricia@dovakhiin.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix missing lock in gst_rtsp_media_seekable() + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-26 16:29:49 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: remove vs_module_defs_dir variable which is no longer needed + +2017-11-26 14:46:05 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-session.h: + rtsp: fix distcheck + +2017-11-26 12:53:42 +0000 Tim-Philipp Müller <tim@centricular.com> + + * Makefile.am: + * gst/rtsp-server/meson.build: + * win32/MANIFEST: + * win32/common/libgstrtspserver.def: + win32: remove .def file with exports + They're no longer needed, symbol exporting is now explicit + via GST_EXPORT in all cases (autotools, meson, incl. MSVC). + +2017-11-26 12:28:40 +0000 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + autotools: stop controlling symbol visibility with -export-symbols-regex + Instead, use -fvisibility=hidden and explicit exports via GST_EXPORT. + This should result in consistent behaviour for the autotools and + Meson builds. + +2017-11-26 12:47:08 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp-server: add missing GST_EXPORT and export deprecated funcs + +2017-11-25 07:53:30 +0100 Edward Hervey <edward@centricular.com> + + * tests/check/gst/media.c: + check: Add seekability testing on medias + Make sure that once GstRTSPMedia are prepared they returned + the expected seekability results + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-24 17:34:31 +0100 Edward Hervey <edward@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * win32/common/libgstrtspserver.def: + rtsp-media: Enable seeking query before pipeline is complete + SDP are now provided *before* the pipeline is fully complete. In order + to know whether a media is seekable or not therefore requires asking + the invididual streams. + API: gst_rtsp_stream_seekable + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-23 20:34:03 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix handling in default_unsuspend() + Handle the case when streams are not blocked and media + is suspended from PAUSED. + Change-Id: I2f3d222ea7b9b20a0732ea5dc81a32d17ab75040 + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-23 18:51:21 +0100 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/media.c: + check/media: Fix thread pool leak. + Change-Id: I0f92b1caca0ee518ae64a7dacfbd28a214c3eea1 + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-23 18:39:44 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Removed fakesink elements + There is not need of adding fakesink elements to the media + pipeline in the dynamic-payloader case. + The media pipeline itself is dynamically updated with + the receiver and sender parts that are based on the client + transport information known after SETUP has been received. + Change-Id: I4e88c9b500c04030669822f0d03b1842913f6cb9 + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-23 09:10:54 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Corrected ASYNC_DONE handling + Media is complete when all the transport based parts are + added to the media pipeline. At this point ASYNC_DONE is + posted by the media pipeline and media is ready to enter + the PREPARED state. + Change-Id: I50fb8dfed88ebaf057d9a35fca2d7f0a70e9d1fa + https://bugzilla.gnome.org/show_bug.cgi?id=790674 + +2017-11-22 12:24:38 +0100 Edward Hervey <bilboed@bilboed.com> + + * tests/check/gst/media.c: + check/media: Check that prepared media can provide a SDP + Whenever a RTSPMedia is prepared, it should be able to provide a SDP + +2017-11-21 09:53:19 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Don't leak addr + CID #1422260 + +2017-11-21 09:53:08 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream.c: + Run gst-indent + +2017-11-20 18:30:19 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't unblock with remaining dynamic payloaders + If we still have some dynamic paylaoders which haven't posted + no-more-pads yet, don't go to PREPARED if one of the streams + blocked. + The risk was that we would end up not exposing/using all specified + streams. + The downside is that if you have _multiple_ _live_ _dynamic_ payloaders + then it will take a bit more time to start. But only if those 3 + conditions are present. + https://bugzilla.gnome.org/show_bug.cgi?id=769521 + +2017-11-20 16:49:29 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix doc + +2017-11-20 16:48:55 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't set float on a gint64 variable + Just use 0. Fixes 'undefined' behaviour from clang + +2017-11-20 18:29:02 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix previous commit + We only want to count dynamic payloaders + +2017-11-20 09:32:07 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + rtsp-media: Handle multiple dynamic elements + If we have more than one dynamic payloader in the pipeline, we need + to wait until the *last* one emits 'no-more-pads' before switching + to PREPARED. + Failure to do so would result in a race where some of the streams + wouldn't properly be prepared + https://bugzilla.gnome.org/show_bug.cgi?id=769521 + +2017-11-16 12:18:20 +0200 Sebastian Dröge <sebastian@centricular.com> + + * win32/common/libgstrtspserver.def: + win32: Fix exported symbols list + +2017-11-15 19:52:29 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Only update the RTP udpsink if it actually exists + For send-only streams it does not exist, but the RTCP udpsink might. + +2017-11-15 18:15:53 +0200 Sebastian Dröge <sebastian@centricular.com> + + * win32/common/libgstrtspserver.def: + win32: Update exports + +2017-10-23 09:49:09 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-media: seek on media pipelines that are complete + Make sure that a seek is performed on pipelines that + contain at least one sink element. + Change-Id: Icf398e10add3191d104b1289de612412da326819 + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-17 10:44:33 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/client.c: + * tests/check/gst/media.c: + * tests/check/gst/rtspserver.c: + * tests/check/gst/stream.c: + Dynamically reconfigure pipeline in PLAY based on transports + The initial pipeline does not contain specific transport + elements. The receiver and the sender parts are added + after PLAY. + If the media is shared, the streams are dynamically + reconfigured after each PLAY. + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-16 12:40:57 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: obtain stream position from pad + If no sinks have been added yet, obtain the current and + the stop position of the stream from the send_src pad. + Change-Id: Iacd4ab4bdc69f6b49370d06012880ce48a7d595a + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-16 11:35:10 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + rtsp-session-media: add function to get a list of transports + Change-Id: I817e10624da0f3200f24d1b232cff481099278e3 + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-16 11:15:55 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: add functions to get rtp and rtcp multicast sockets + Change-Id: Iddfe6e0bd250cb0159096d5eba9e4202d22b56db + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-20 12:21:48 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: set async=sync=false only for RTCP appsink + Change-Id: I929a218a9adf4759f61322b6f2063aacc5595f90 + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-10-16 10:10:17 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: return minimum value in query position case + The minimum position should be returned as we are interested + in the whole interval. + Change-Id: I30e297fc040c995ae40c25dee8ff56321612fe2b + https://bugzilla.gnome.org/show_bug.cgi?id=788340 + +2017-08-09 11:52:38 +0200 Jonathan Karlsson <jonakn@axis.com> + + * gst/rtsp-server/rtsp-session.c: + * tests/check/gst/rtspserver.c: + rtsp-session: Handle the case when timeout=0 + According to the documentation, a timeout of value 0 means + that the session never timeouts. This adds handling of that. + If timeout=0 we just return with a -1 from + gst_rtsp_session_next_timeout_usec (). + https://bugzilla.gnome.org/show_bug.cgi?id=785058 + +2017-07-17 17:15:22 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Add "accept-certificate" signal for manually checking a TLS certificate for validity + https://bugzilla.gnome.org/show_bug.cgi?id=785024 + +2017-10-26 14:43:19 +0200 Mathieu Duponchelle <mathieu@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media-factory.c: + docs: add media factory transport mode accessors + and fix the documentation for the return value of the getter + +2017-10-09 12:43:01 +0200 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: unref 'pipelined_requests' in finalize + The hash table priv->pipelined_requests is not unref:ed in the + finalize funktion. Make sure it is. + https://bugzilla.gnome.org/show_bug.cgi?id=788704 + +2017-10-09 14:44:40 +0200 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Initialize scalar variable + CID 1418985 + +2017-10-06 10:27:34 +0200 Edward Hervey <edward@centricular.com> + + * win32/common/libgstrtspserver.def: + win32: Update export file + +2017-04-22 09:26:07 -0300 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Start support for RTSP 2.0 + This adds basic support for new 2.0 features, though the protocol is + subposdely backward incompatible, most semantics are the sames. + This commit adds: + - features: + * version negotiation + * pipelined requests support + * Media-Properties support + * Accept-Ranges support + - APIs: + * gst_rtsp_media_seekable + The RTSP methods that have been removed when using 2.0 now return + BAD_REQUEST. + https://bugzilla.gnome.org/show_bug.cgi?id=781446 + +2017-06-02 15:37:54 -0400 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Use stream duration as stream-stop if segment was not configured with a stop + Allowing client to know stream duration when no seeking happened. + https://bugzilla.gnome.org/show_bug.cgi?id=783435 + +2017-09-25 19:40:17 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: Don't cache any media if NULL was returned as key + The docs already mentioned this, but we actually stored it in the hash + table with key==NULL and leaked its reference forever. + +2017-09-18 19:31:31 +0200 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + rtspclientsink: Use a mutex for protecting against concurrent send/receives + This is a simple port of: + * a722f6e8329032c6eda4865d6a07f4ba5981d7ea + * c438545dc9e2f14f657bc0ef261fff726449867b + * cd17c71dcea5c9310d21f1347c7520983e5869ac + in gst-plugins-good. + +2017-08-31 13:24:15 +0530 Satya Prakash Gupta <sp.gupta@samsung.com> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: fix Memory leak in error case + https://bugzilla.gnome.org/show_bug.cgi?id=787059 + +2017-08-18 17:37:01 +0100 Tim-Philipp Müller <tim@centricular.com> + + * pkgconfig/meson.build: + meson: don't install -uninstalled.pc file + https://bugzilla.gnome.org/show_bug.cgi?id=786457 + +2017-08-17 12:26:17 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From 48a5d85 to 3f4aa96 + +2017-08-14 21:04:23 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix typo in debug message + +2017-08-11 14:14:32 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: hide symbols by default unless explicitly exported + +2017-08-10 14:20:12 +0100 Tim-Philipp Müller <tim@centricular.com> + + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + pkgconfig: remove -I@srcdir@/.. which duplicates abs_top_srcdir + Fixes meson warning about undefined @srcdir@. + +2017-07-21 13:36:00 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/meson.build: + meson: skip tests on windows for now + As we do in the other modules. As libgstcheck is currently not + built on windows. Fixes "Fallback variable 'gst_check_dep' in + the subproject 'gstreamer' does not exist"" Meson error. + +2017-06-22 07:25:07 -0700 Julien Isorce <julien.isorce@gmail.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fix connection delay due to wrong assumption on last-sample + Commit 852cc09f542af5cadd79ffd7fe79d6475cf57e14 assumed that + multiudpsink's last-sample always comes from the payloader. Which + is wrong if auxiliary streams are multiplexed in the same stream. + So check the buffer's ssrc against the caps'ssrc before to use its + seqnum. If not the same ssrc just use the payloader as done prior + the commit above or when there is no last-sample yet. + https://bugzilla.gnome.org/show_bug.cgi?id=784094 + +2017-06-23 16:19:04 -0400 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * meson.build: + meson: Allow using glib as a subproject + +2017-06-26 09:55:49 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: fix with-package-name option + https://bugzilla.gnome.org/show_bug.cgi?id=784082 + +2017-06-09 20:16:28 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * Makefile.am: + Distribute meson_options.txt + +2017-06-09 20:11:47 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * Makefile.am: + And config.h.meson is no longer dist either + +2017-06-09 21:27:09 +0100 Tim-Philipp Müller <tim@centricular.com> + + * config.h.meson: + * meson.build: + meson: config.h.meson is no longer needed + +2017-06-07 13:04:41 -0400 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * tests/check/meson.build: + * tests/meson.build: + meson: Fix building tests and activate them again + +2017-06-07 12:55:41 -0400 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * tests/check/meson.build: + meson: Do not use path separator in test names + Avoiding warnings like: + WARNING: Target "elements/audioamplify" has a path separator in its name. + +2017-05-20 15:07:31 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + * meson_options.txt: + meson: add options to set package name and origin + https://bugzilla.gnome.org/show_bug.cgi?id=782172 + +2017-05-18 10:35:18 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-params.h: + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-server/rtsp-thread-pool.h: + * gst/rtsp-server/rtsp-token.h: + Mark symbols explicitly for export with GST_EXPORT + +2017-05-16 14:44:43 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * configure.ac: + * gst/rtsp-sink/Makefile.am: + Remove plugin specific static build option + Static and dynamic plugins now have the same interface. The standard + --enable-static/--enable-shared toggle are sufficient. + +2017-05-04 18:59:14 +0300 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + * meson.build: + Back to development + +=== release 1.12.0 === + +2017-05-04 15:40:46 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.12.0 + +=== release 1.11.91 === + +2017-04-27 17:42:02 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.11.91 + +2017-04-24 20:30:37 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From 60aeef6 to 48a5d85 + +2017-04-13 14:20:10 -0300 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream.c: + gi: Fix some annotations and docstrings + +2017-04-13 13:52:26 -0300 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * gst/rtsp-server/meson.build: + * meson.build: + * meson_options.txt: + meson: Build gir + +2017-04-10 23:51:12 +0100 Tim-Philipp Müller <tim@centricular.com> + + * autogen.sh: + * common: + Automatic update of common submodule + From 39ac2f5 to 60aeef6 + +=== release 1.11.90 === + +2017-04-07 16:35:03 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * meson.build: + Release 1.11.90 + +2017-03-27 18:19:33 +0100 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-launch.c: + examples: make test-launch pipeline shared by default as well + +2017-02-27 19:10:44 +0200 Sebastian Dröge <sebastian@centricular.com> + + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + gstreamer-rtsp-server: Add both srcdir and builddir to the include path + Just the build dir is not going to work for srcdir!=builddir. + +2017-02-24 15:59:54 +0200 Sebastian Dröge <sebastian@centricular.com> + + * meson.build: + meson: Update version + +2017-02-24 15:37:49 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.11.2 === + +2017-02-24 15:10:07 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.11.2 + +2017-02-14 20:40:26 +0000 Tim-Philipp Müller <tim@centricular.com> + + * Makefile.am: + meson: dist meson build files + Ship meson build files in tarballs, so people who use tarballs + in their builds can start playing with meson already. + +2017-02-07 23:39:37 +1100 Jan Schmidt <jan@centricular.com> + + * examples/test-record.c: + examples/test-record: Add extra line to initial printout + Add an example line of how to deliver a stream to the + RTSP RECORD example + +2017-01-19 14:57:19 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Also handle the (S|G)ET_PARAMETER case of size==0 || !data as keep-alive + If there is no Content-Length header, no body would be allocated and the + '\0' would also not be appended to the body. + +2017-01-19 14:24:07 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix handling of keep-alive GET_PARAMETER/SET_PARAMETER + While they logically have 0 bytes length, GstRTSPConnection is appending + a '\0' to everything making the size be 1 instead. + +2017-01-13 12:39:36 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: bump version + +2017-01-12 19:04:23 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: Only remove deprecated API if requested to do so, not just when disabling + gst_rtsp_session_is_expired() and gst_rtsp_session_next_timeout() were + affected. + +2017-01-12 16:32:59 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.11.1 === + +2017-01-12 16:14:46 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + * win32/common/libgstrtspserver.def: + Release 1.11.1 + +2017-01-10 08:34:50 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: corrected if-statement in _get_server_port() + This bug was accidentally introduced while fixing a segfault + in _get_server_port() function. + https://bugzilla.gnome.org/show_bug.cgi?id=776345 + +2017-01-09 14:12:05 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/stream.c: + rtsp-stream: fixed segmenation fault in _get_server_port() + Calling function gst_rtsp_stream_get_server_port() results in + segmenation fault in the RTP/RTSP/TCP case. + Port that the server will use to receive RTCP makes only + sense in the UDP case, however the function should handle + the TCP case in a nicer way. + https://bugzilla.gnome.org/show_bug.cgi?id=776345 + +2017-01-09 12:22:40 +0300 Aleksandr Slobodeniuk <alenuke@yandex.ru> + + * gst/rtsp-server/rtsp-media-factory.c: + dosc: Fix a little typo + https://bugzilla.gnome.org/show_bug.cgi?id=777037 + +2017-01-04 16:20:54 +0100 Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> + + * pkgconfig/Makefile.am: + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + * pkgconfig/meson.build: + meson: generate pkg-config -uninstalled pc files + Generating those files is useful for users building the GStreamer stack + using meson and having to link it to another project which is still + using the autotools. + https://bugzilla.gnome.org/show_bug.cgi?id=776810 + +2017-01-04 16:11:08 +0100 Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> + + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + pkgconfig: fix -uninstalled pc file + pcfiledir was never defined so the paths were wrong. + https://bugzilla.gnome.org/show_bug.cgi?id=776867 + +2016-12-21 13:41:50 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/rtspserver.c: + rtsp-stream: Fixed TCP transport case + Make sure that the appsink element is actually added to + the bin before trying to link it with the elements in it. + https://bugzilla.gnome.org/show_bug.cgi?id=776343 + +2016-12-16 17:26:04 +0000 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + * Makefile.am: + * configure.ac: + * gst-rtsp.spec.in: + Remove generated .spec file + Likely extremely bitrotten, and we should not ship this anyway. + +2016-12-03 08:21:02 +0100 Edward Hervey <bilboed@bilboed.com> + + * common: + Automatic update of common submodule + From f980fd9 to 39ac2f5 + +2016-12-02 15:40:09 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + media: Fix pt map caps + Since decryption is handled within rtpbin, all outcoming stream + caps will be application/x-rtp (i.e. regular rtp) + Fixes RECORD with SRTP streams + +2016-12-02 15:38:04 +0100 Edward Hervey <edward@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: Create media objects with the proper transport mode + The function called immediately afterwards (collect_streams()) will + need it to work properly + +2016-12-02 14:36:50 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-auth.c: + rtsp-auth: Don't remove digest-auth nonces that already/still have a client connected + +2016-12-01 18:04:34 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: Don't create a pipeline for the media pipeline string + We're going to put a pipeline into a pipeline otherwise, which is not + exactly ideal. + +2016-10-25 15:41:28 +0300 Kseniia Vasilchuk <vasilchukkseniia@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + media: Fix race condition around finish_unprepare() if called multiple time + https://bugzilla.gnome.org/show_bug.cgi?id=755329 + +2016-11-30 14:06:36 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Don't leave stale pointer after unref + Fix a warning on shutdown - don't keep a pointer to an + alread-unreffed object. + +2016-11-26 11:24:50 +0000 Tim-Philipp Müller <tim@centricular.com> + + * .gitmodules: + common: use https protocol for common submodule + https://bugzilla.gnome.org/show_bug.cgi?id=775110 + +2016-11-21 23:29:56 +1100 Matthew Waters <matthew@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: block the output of rtpbin instead of the source pipeline + 85c52e194bcb81928b96614be0ae47d59eccb1ce introduced a more correct + detection of the srtp rollover counter to add to the SDP. + Unfortunately, it was incomplete for live pipelines where the logic + blocks the source bin before creating the SDP and thus would never have + the necessary informaiton to create a correct SDP with srtp encryption. + Move the pad blocks to rtpbin's output pads instead so that the + necessary information can be created before we need the information for + the SDP. + https://bugzilla.gnome.org/show_bug.cgi?id=770239 + +2016-11-21 16:02:39 +0100 Dag Gullberg <dagg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: add IDLE timeout, before session exists + The RTSP server will not timeout an idle RTSP connection + (note this is different from doing timeout on a RTSP + session). + At least for Apache this is a problem when running RTSP over + HTTPS since it uses one of the threads (there is a rather + limited number) that are available for handling requests. + https://bugzilla.gnome.org/show_bug.cgi?id=771830 + +2016-11-23 09:45:08 +0000 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + .gitignore more + +2016-11-21 13:05:50 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Set close-socket FALSE on UDP src:es + With this RTSP server can use the sockets independent on the udpsrc + state. + When the udp src is finalized it will unref socket and when g_socket + is finalized the socket will be closed. + https://bugzilla.gnome.org/show_bug.cgi?id=765673 + +2016-11-18 17:47:13 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Move to new helper function to parse authentication responses + https://bugzilla.gnome.org/show_bug.cgi?id=774416 + +2016-11-16 08:42:24 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/Makefile.am: + * examples/test-auth-digest.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * win32/common/libgstrtspserver.def: + rtsp-auth: Add support for Digest authentication + https://bugzilla.gnome.org/show_bug.cgi?id=774416 + +2016-11-17 09:41:53 -0800 Scott D Phillips <scott.d.phillips@intel.com> + + * Makefile.am: + * gst/rtsp-server/meson.build: + * meson.build: + * tests/check/meson.build: + * win32/MANIFEST: + * win32/common/libgstrtspserver.def: + Enable building with MSVC + https://bugzilla.gnome.org/show_bug.cgi?id=774640 + +2016-11-18 20:23:14 -0300 Thibault Saunier <thibault.saunier@osg.samsung.com> + + * meson.build: + meson: gstreamer gst_check_dep does not exist on windows + +2016-11-17 09:43:37 -0800 Scott D Phillips <scott.d.phillips@intel.com> + + * gst/rtsp-server/rtsp-client.c: + client: update do_send_message to match type GstRTSPClientSendFunc + This type mismatch fails building with MSVC + https://bugzilla.gnome.org/show_bug.cgi?id=774640 + +2016-11-11 14:42:08 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Fix indentation + +2016-11-10 05:16:00 +0000 Neha Arora <arora.neha@samsung.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Only signal "new-state" if the state has actually changed + https://bugzilla.gnome.org/show_bug.cgi?id=774173 + +2016-08-24 11:39:13 +0200 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: emit signal in the beginning of each rtsp request + These signals let the application validate the requests, configure the + media/stream in a certain way and also generate error status code in + case of error or bad request. + https://bugzilla.gnome.org/show_bug.cgi?id=758062 + +2016-11-01 18:10:35 +0000 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: update version + +=== release 1.11.0 === + +2016-11-01 18:53:15 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.10.0 === + +2016-11-01 18:06:46 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.10.0 + +2016-10-28 18:38:01 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspserver.c: + * tests/check/gst/stream.c: + tests: try to avoid using the same ports in different tests + Causes problems with client multicast tests otherwise if + tests are run in parallel. + https://bugzilla.gnome.org/show_bug.cgi?id=773640 + +2016-10-28 17:50:59 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/client.c: + tests: client: use fail_unless_equals_foo() for better failure reporting + +2016-09-26 11:16:04 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Session filter in unwatch session + Call session filter with filter_session_media as paramer in + client_unwatch_session if using drop_backlog = FALSE. + In client_unwatch_session its allowed to grow the watchs backlog. + If using drop_backlog = FALSE and the backlog is full it will cause + a deadlock when setting session media state to NULL + if the backlog is not allowed to grow. + https://bugzilla.gnome.org/show_bug.cgi?id=771983 + +2016-10-20 21:40:18 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: add fallbacks for gst modules + For gst-all. + +2016-09-14 17:48:39 +0300 Nikita Bobkov <NikitaDBobkov@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix factory leaking in find_media() in error cases + https://bugzilla.gnome.org/show_bug.cgi?id=771488 + +2016-10-06 11:47:50 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Fix randomly missing streams from SDP with dynamic elements + When using dynamic elements, gst_rtsp_stream_join_bin() is called from + "pad-added" signal. In that case priv->srcpad could already have its caps, + and they'll be sent to priv->send_src[0] pad. That means that when it + connects "notify::caps" signal, that pad could already have received its + caps and the signal won't be emitted anymore. + In that case priv->caps stay to NULL and when building the SDP that stream + gets ignored. Leading to missing video or audio when playing in client side. + https://bugzilla.gnome.org/show_bug.cgi?id=772478 + +2016-09-30 11:42:08 +0100 Tim-Philipp Müller <tim@centricular.com> + + * meson.build: + meson: update version + +=== release 1.9.90 === + +2016-09-30 13:04:12 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.9.90 + +2016-09-17 13:17:19 +0100 Ian Jamison <ian.dev@arkver.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: Hint that set_multicast_iface expects the name of the interface + To prevent any possibly confusion with IPs or anything else. + https://bugzilla.gnome.org/show_bug.cgi?id=771530 + +2016-09-18 09:58:55 -0400 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Call g_free() instead of g_object_unref() on multicast-iface strings + https://bugzilla.gnome.org/show_bug.cgi?id=763000#c5 + +2016-09-14 11:31:15 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + configure: Depend on gstreamer 1.9.2.1 + +2016-09-10 20:52:31 +1000 Jan Schmidt <jan@centricular.com> + + * autogen.sh: + * common: + Automatic update of common submodule + From b18d820 to f980fd9 + +2016-09-10 09:58:31 +1000 Jan Schmidt <jan@centricular.com> + + * autogen.sh: + * common: + Automatic update of common submodule + From 6f2d209 to b18d820 + +2016-09-07 18:44:34 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Remove unused _locked() variant of a function + It was added during refactoring. + +2016-09-07 10:21:09 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: cosmetic cleanup + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-09-07 10:16:19 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Compare IP addresses case insensitive in more places + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-09-07 10:12:18 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * common: + * gst/rtsp-server/rtsp-stream.c: + stream: Fix leaked joined_bin + There is no need to keep a strong ref on it, and _leave_bin() was + setting it to NULL before calling g_clear_object() so it was leaked. + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-09-06 19:15:23 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Compare IP address strings case insensitive + Otherwise IPv6 addresses might fail this comparision. + +2016-09-06 19:10:21 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Bind multicast sockets to ANY as before + https://bugzilla.gnome.org/show_bug.cgi?id=766612#c48 + +2016-09-05 18:31:36 +0300 Kseniia <vasilchukkseniia@gmail.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: Fix segfault when doing keep-alive after removing the session + If keep-alive happens after removing the session but before finalizing the + stream transport, we would segfault. + https://bugzilla.gnome.org/show_bug.cgi?id=750544 + +2016-09-05 18:04:50 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Always create multicast UDP elements if the protocol flag is set + Adding them later will cause deadlocks due to + 1) pre-rolling and staying in PAUSED with the unicast/TCP sinks + 2) adding the multicast sink + 3) waiting for it to get data to preroll again + 3) never happens because the queues after the tee are full. + +2016-09-05 16:32:57 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix up various multicast related issues + +2016-09-05 13:40:59 +0300 Sebastian Dröge <sebastian@centricular.com> + + * tests/check/gst/stream.c: + tests: Fix compilation + +2016-07-28 15:33:05 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/stream.c: + stream: revert back to create udpsrc/udpsink on DESCRIBE for unicast + This is basically reverting changes introduced in commit f62a9a7, + because it was introducing various regressions: + - It introduces a leak of udpsrc elements that got wrongly fixed by adding + an hash table in commit cba045e. We should have at most 4 udpsrc for unicast: + ipv4/ipv6, rtp/rtcp. They can be reused for all unicast clients. + - If a mcast client connects, it creates a new socket in SETUP to try to respect + the destination/port given by the client in the transport, and overrides the + socket already set on the udpsink element. That means that if we already had a + client connected, the source address on the udp packets it receives suddenly + changes. + - If a 2nd mcast client connects, the destination/port in its transport is + ignored but its transport wasn't updated. + What this patch does: + - Revert back to create udpsrc/udpsink for unicast clients on DESCRIBE. + - Always have a tee+queue when udp is enabled. This could be optimized + again in a later patch, but is more complicated. If no unicast clients + connects then those elements are useless, this could be also optimized + in a later patch. + - When mcast transport is added, it creates a new set of udpsrc/udpsink, + seperated from those for unicast clients. Since we already support only + one mcast address, we also create only one set of elements. + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-28 15:20:31 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: factor our plug_src function + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-21 21:46:16 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: factor out plug_sink function + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-20 23:05:09 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: small documentation clarification + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-20 15:35:44 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: rename addr_v4/6 to mcast_addr_v4/6 for clarity + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-14 11:10:31 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Keep a ref on joined bin + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-20 15:11:32 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: code cleanup + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-20 23:18:23 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: small fix in error code path + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-07-20 20:09:57 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + Revert "rtsp-stream: Fix crash on cleanup with shared media and multiple udpsrc" + This partly reverts commit cba045e1b19fad6e689e10206f57903e15f1229a, + but keeps unit tests. + https://bugzilla.gnome.org/show_bug.cgi?id=766612 + +2016-09-01 12:33:00 +0300 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.9.2 === + +2016-09-01 12:32:51 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.9.2 + +2016-01-27 01:03:52 +0000 Tim-Philipp Müller <tim@centricular.com> + + * config.h.meson: + * examples/meson.build: + * gst/meson.build: + * gst/rtsp-server/meson.build: + * gst/rtsp-sink/meson.build: + * meson.build: + * pkgconfig/meson.build: + * tests/check/meson.build: + * tests/meson.build: + Add support for Meson as alternative/parallel build system + https://github.com/mesonbuild/meson + +2016-08-26 21:56:13 +0200 Josep Torra <n770galaxy@gmail.com> + + * configure.ac: + * tests/check/Makefile.am: + build: silence error about pthread for 'make check' in osx + Fixes "clang: error: argument unused during compilation: '-pthread'" + +2015-09-25 15:04:00 +0000 Nikita Bobkov <NikitaDBobkov@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix leaking of media in error cases + With additional fixes by Kseniya Vasilchuk <vasilchukkseniia@gmail.com> + and myself to make the media refcounting a bit easier to follow. + https://bugzilla.gnome.org/show_bug.cgi?id=755632 + +2016-08-02 15:08:22 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix leaking of session in error cases + https://bugzilla.gnome.org/show_bug.cgi?id=755632 + +2016-07-11 21:16:04 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From f363b32 to f49c55e + +2016-07-06 13:51:15 +0300 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.9.1 === + +2016-07-06 13:28:12 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.9.1 + +2016-06-24 02:02:20 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * configure.ac: + configure: Need to add -DGST_STATIC_COMPILATION when building only statically + https://bugzilla.gnome.org/show_bug.cgi?id=767463 + +2016-06-21 11:49:02 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * common: + Automatic update of common submodule + From ac2f647 to f363b32 + +2016-04-14 22:56:11 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + sdp: add rollover counters for all sender SSRC + We add different crypto sessions in MIKEY, one for each sender + SSRC. Currently, all of them will have the same security policy, 0. + The rollover counters are obtained from the srtpenc element using the + "stats" property. + https://bugzilla.gnome.org/show_bug.cgi?id=730539 + +2016-06-07 20:44:42 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-server.h: + docs: fix some typos + +2016-05-25 10:28:43 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/Makefile.am: + g-i: pass compiler env to g-ir-scanner + It's what introspection.mak does as well. Should + fix spurious build failures on gnome-continuous + (caused by g-ir-scanner getting compiler details + via python which is broken in some environments + so passing the compiler details bypasses that). + +2016-05-18 16:48:44 +0100 Ian <ian.arkver.dev@gmail.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: RFC2326 does not allow a space between ; and timeout in the Session header + This works with rtspsrc and live555, but fails with e.g. ffmpeg. + https://bugzilla.gnome.org/show_bug.cgi?id=766619 + +2016-03-07 14:48:38 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Check return value of sscanf + And just make sure we always have 0/0 if we have an error + CID #1352031 + +2016-04-25 08:55:25 -0400 Jake Foytik <jake.foytik@ipconfigure.com> + + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/rtspserver.c: + * tests/check/gst/stream.c: + rtsp-stream: Fix crash on cleanup with shared media and multiple udpsrc + - Unicast udpsrcs are now managed in a hash table. This allows for proper cleanup in with shared streams and fixes a memory leak. + - Unicast udpsrcs are now properly cleaned up when shared connections exit. See the update_transport() function. + - Create unit test for shared media. + https://bugzilla.gnome.org/show_bug.cgi?id=764744 + +2016-04-11 10:55:23 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Always bind to ANY when address is a multicast address and not only on Windows + For IPv6 addresses, binding to a multicast group does not work on Linux + either. Always bind to ANY and then later join the multicast group. + https://bugzilla.gnome.org/show_bug.cgi?id=764679 + +2016-04-14 10:05:02 +0100 Julien Isorce <j.isorce@samsung.com> + + * common: + Automatic update of common submodule + From 6f2d209 to ac2f647 + +2016-04-06 10:09:46 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + rtsp-thread-pool: explained why GSource is a part of ThreadImpl + Clarified why it is necessary to add source information to + GstRTSPThreadImpl. See the reported bug in GLib: + https://bugzilla.gnome.org/show_bug.cgi?id=720186 + for more information. + https://bugzilla.gnome.org/show_bug.cgi?id=761702 + +2016-04-04 12:58:38 +0300 Sebastian Dröge <sebastian@centricular.com> + + * examples/Makefile.am: + examples: Clean up CFLAGS/LDADD even more + The internal .la should come first and is part of LDADD, as is + GST_CFLAGS/LIBS. + +2016-04-04 12:39:39 +0300 Sebastian Dröge <sebastian@centricular.com> + + * examples/Makefile.am: + examples: Clean up CFLAGS/LDADD to link with the correct versions of all libraries + +2016-04-03 12:06:29 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/Makefile.am: + rtsp-server: Use $(GST_NET_LIBS) / $(GST_NET_CFLAGS) + +2015-12-30 18:39:05 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-server: Implement clock signalling according to RFC7273 + For NTP and PTP clocks we signal the actual clock that is used and signal + the direct media clock offset. + For all other clocks we at least signal that it's the local sender clock. + This allows receivers to know which clock was used to generate the media and + its RTP timestamps. Receivers can then implement network synchronization, + either absolute or at least relative by getting the sender clock rate directly + via NTP/PTP instead of estimating it from RTP timestamps and packet receive + times. + https://bugzilla.gnome.org/show_bug.cgi?id=760005 + +2016-03-02 19:42:58 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Add support for setting the multicast interface + https://bugzilla.gnome.org/show_bug.cgi?id=763000 + +2016-03-02 19:42:13 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-media: Add support for setting the multicast interface + https://bugzilla.gnome.org/show_bug.cgi?id=763000 + +2016-03-07 08:50:01 +0900 Vineeth TM <vineeth.tm@samsung.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: use new gst_element_class_add_static_pad_template() + https://bugzilla.gnome.org/show_bug.cgi?id=763196 + +2016-03-24 13:33:43 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.8.0 === + +2016-03-24 13:00:35 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.8.0 + +2016-03-16 23:35:09 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Don't set the state of the appsrc from PLAYING to PAUSED again during setup + This would get us NO_PREROLL in the bin again and break seeking. + Thanks to Carlos Rafael Giani for helping to debug this! + https://bugzilla.gnome.org/show_bug.cgi?id=740509 + +=== release 1.7.91 === + +2016-03-15 12:26:13 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.7.91 + +2016-03-10 13:54:38 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Ensure that the pipeline is live and later-added udpsrcs are syncing the state with the parent bin + Without this, RECORD pipelines are broken because + a) we wait for ASYNC_DONE which never happens anymore because udpsrc would be + added later. Previously it was there earlier and due to NO_PREROLL caused the + pipeline to preroll immediately + b) the udpsrc for the pipeline is added later and never set to PLAYING state, + as the corresponding code previously was only for PLAY pipelines. + https://bugzilla.gnome.org/show_bug.cgi?id=763281 + +2016-03-11 01:22:54 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix typo in the docstring + gst_rtsp_stream_set_client_side -> gst_rtsp_stream_is_client_side + +2016-03-05 10:52:11 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Disable multicast loopback for all our sockets + On Windows this is a receiver-side setting, on Linux a sender-side setting. As + we provide a socket ourselves to udpsrc, udpsrc is never setting the multicast + loopback setting on the socket... while udpsink does which unfortunately has + no effect here on Windows but on Linux. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-03-03 15:07:06 +0100 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/stream.c: + stream tests: added new tests + Test a case when the address pool only contains multicast addresses + and the client is requesting unicast udp. + Added tests for multicast ports allocation. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-03-04 13:51:12 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Only bind multicast sockets to ANY on Windows + On Linux it is still needed to bind to the multicast address + to filter out random other packets, while on Windows binding + to multicast addresses just fails. + +2016-03-03 10:41:51 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Only use the address pool for unicast UDP if it contains unicast addresses + Otherwise we fail to allocate UDP ports if the pool only contains multicast + addresses, which is something that used to work before. For unicast addresses + if the pool contains none, we just allocate them as if there is no pool at + all. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-03-02 11:48:49 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: Fix indentation + +2016-03-02 11:47:47 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Don't bind the sockets to multicast addresses + This works on Linux but fails completely on Windows. You're supposed + to bind to ANY and then join the multicast group. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +=== release 1.7.90 === + +2016-03-01 19:00:45 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.7.90 + +2016-02-26 12:42:51 +0200 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From b64f03f to 6f2d209 + +2016-02-24 00:10:52 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + * tests/check/gst/rtspclientsink.c: + rtspsink: Fix some leaks in rtspclientsink and the unit test. + https://bugzilla.gnome.org/show_bug.cgi?id=762525 + +2016-02-23 15:01:22 +0100 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/media.c: + * tests/check/gst/rtspclientsink.c: + * tests/check/gst/rtspserver.c: + * tests/check/gst/stream.c: + tests: unit test fixes + Removed port allocation test from the media suite. + The port allocation failure is now in the stream suite. + rtspserver: + Make sure that the media is suspended after the DESCRIBE request + before reconfiguring the UDP sinks. + rtspclientsink: + In the RECORD case we have to set async property to false + for the appsink element in the test in order to make sure + that the media pipeline doesn't hang in start_preroll(). + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-02-23 14:59:32 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: postpone UDP socket allocation until SETUP + Postpone the allocation of the UDP sockets until we know + what transport has been chosen by the client. + Both unicast and multicast UDP sources are created in one + function. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-01-13 11:29:35 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: postpone the creation of the UDP sources + Code refactoring: allocate the UDP ports after the sender and + the reciver parts have been created. + We postpone the creation of the UDP sources until the UDP + ports have been allocated. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-01-13 10:55:40 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: added function for setting UDP sources to PLAYING state + Code refactoring: Introduced a function for setting UDP sources + to PLAYING state. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2015-11-20 15:34:43 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: added function for creating and configuring UDP sources + Code refactoring: create and configure UDP sources in a separate function. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2015-11-20 14:43:38 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: added function for RTP/RTCP socket configuration + Code refactoring: configure RTP and RTCP sockets for UDP sinks + in a separate function. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2015-11-20 08:38:42 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: added function for creating and configuring UDP sinks + Code refactoring: create and configure UDP sinks in a separate function. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2015-11-19 14:09:25 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: added helper function for creating the sender/receiver parts + Code refactoring: introduced helper function for creating + the receiver and the sender parts of the streaming pipeline. + https://bugzilla.gnome.org/show_bug.cgi?id=757488 + +2016-02-19 12:38:42 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.7.2 === + +2016-02-19 12:03:18 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.7.2 + +2016-02-18 15:20:05 +0000 Julien Isorce <j.isorce@samsung.com> + + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + uninstalled.pc: add support for non libtool build systems + Currently the .la path is provided which requires to use libtool as + mentioned in the GStreamer manual section-helloworld-compilerun.html. + It is fine as long as the application is built using libtool. + So currently it is not possible to compile a GStreamer application + within gst-uninstalled with CMake or other build system different + than autotools. + This patch allows to do the following in gst-uninstalled env: + gcc test.c -o test $(pkg-config --cflags --libs gstreamer-1.0 \ + gstreamer-rtsp-server-1.0) + Previously it required to prepend libtool --mode=link + https://bugzilla.gnome.org/show_bug.cgi?id=720778 + +2016-02-09 10:34:22 +0000 Luis de Bethencourt <luisbg@osg.samsung.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: remove check for impossible condition + Goto error label checks stream to see if it needs to be unreferenced before + returning, but this goto jumps happens before the stream is ever set, so it + will always be NULL in this error label. + CID #1352034 + +2016-02-08 23:33:03 +0000 Luis de Bethencourt <luisbg@osg.samsung.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: clean switch statements + Coverity demands for fallthrough statements to be clearly commented, + to distinguish from accidental fall throughs. And it also needs all + cases to finish with a break, even if the break is never going to be + executed like in the case of a continue jump. + CID #1352039 + CID #1352040 + +2016-02-05 20:03:01 -0300 Thiago Santos <thiagoss@osg.samsung.com> + + * tests/check/Makefile.am: + tests: extend the AM_TESTS_ENVIRONMENT from check.mak + To get the CK_DEFAULT_TIMEOUT defined for all tests + Also removes a 120 seconds timeout that was set as default + explicitly in this module + https://bugzilla.gnome.org/show_bug.cgi?id=761472 + +2016-02-05 18:11:41 -0300 Thiago Santos <thiagoss@osg.samsung.com> + + * autogen.sh: + * common: + Automatic update of common submodule + From 86e4663 to b64f03f + +2016-02-02 09:01:51 +0100 Steven Hoving <sh@bigbrother.nl> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: fix state_lock not locked again when preroll fails + https://bugzilla.gnome.org/show_bug.cgi?id=761399 + +2016-01-28 22:05:56 +0100 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + configure: Move plugin specific flags below all the others + They use some of the other flags, like $GST_ALL_LDFLAGS which is adding + -no-undefined. And -no-undefined is required on Windows to build DLLs. + +2016-01-28 04:58:00 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-sink/gstrtspclientsink.c: + rtspclientsink: Simplify slightly using new -base API + Use the new Mikey and SDP API in the base plugins libs + to simplify some code. + https://bugzilla.gnome.org/show_bug.cgi?id=758180 + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * .gitignore: + * configure.ac: + * gst/Makefile.am: + * gst/rtsp-sink/Makefile.am: + * gst/rtsp-sink/gstrtspclientsink.c: + * gst/rtsp-sink/gstrtspclientsink.h: + * gst/rtsp-sink/plugin.c: + * tests/check/Makefile.am: + * tests/check/gst/rtspclientsink.c: + rtspsink: Add rtspclientsink element + Add an rtspclientsink element that accepts streams for which + there is a registered payloader and sends them to + an RTSP server using RECORD. + Sending is synchronised to the pipeline clock. Payload-types + are automatically selected. The 'new-payloader' signal is fired + for custom configuration of payloaders when they are created. + Can now stream a movie like this: + receiver: + ./test-record "( decodebin name=depay0 ! videoconvert ! autovideosink \ + decodebin name=depay1 ! audioconvert ! autoaudiosink )" + sender: + gst-launch-1.0 filesrc location=file-with-aac-and-h264.mp4 ! qtdemux name=d ! \ + queue ! aacparse ! rtspclientsink location=rtsp://127.0.0.1:8554/test name=s \ + https://bugzilla.gnome.org/show_bug.cgi?id=758180 + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: Add functions for using rtsp-stream from the client + Add a boolean to indicate that the rtsp-stream is running on the + 'client' side of an RTSP connection, for sending streams via + RECORD. In that case, the roles of the client/server ports + in transport setup are swapped. + https://bugzilla.gnome.org/show_bug.cgi?id=758180 + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + rtsp-sdp: Add gst_rtsp_sdp_from_stream() + A new function that adds info from a GstRTSPStream into an SDP message. + https://bugzilla.gnome.org/show_bug.cgi?id=758180 + +2016-01-28 09:22:18 +0100 Steven Hoving <sh@bigbrother.nl> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix mutex beeing unlocked while they should be locked + https://bugzilla.gnome.org/show_bug.cgi?id=761226 + +2016-01-15 07:01:37 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: add missing break in "clock" property setter + CID 1348453 + +2016-01-05 13:10:36 +0100 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fixed assert during update transport + When RTSP server trying update transport during multicast, it throws an + assert. The assert is thrown because it is trying to get the parent of + an non-existing funnel element. + https://bugzilla.gnome.org/show_bug.cgi?id=760150 + +2016-01-03 17:26:31 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-thread-pool.h: + * gst/rtsp-server/rtsp-token.h: + docs: remove dummy function declarations with G_INLINE_FUNC for gtk-doc + gtk-doc can handle static inline functions just fine these days, + there's no need for this stuff any more. + +2015-10-07 18:53:01 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + sdp: replace duplicated codes to call new base sdp apis + https://bugzilla.gnome.org/show_bug.cgi?id=745880 + +2015-12-30 16:34:30 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock.c: + test-netclock: Use the new API to configure a clock directly + +2015-12-30 16:31:13 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp-media: Add API to directly configure a clock on the media pipelines + +2015-12-30 16:43:17 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Fix typo in docs gst_rtsp_media_set_latncy() -> latency() + +2015-12-30 16:30:38 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: Add FIXME for 2.0 + +2015-12-30 16:29:45 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix indentation + +2015-12-22 12:08:02 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Do not prepare media after media times out + Deferred calls to start_prepare() can be deferred past the point until + which wait_preroll() and by proxy gst_rtsp_media_get_status() is + prepared to wait. Previously there was no lock and no check for this + situation. This meant that a media could be prepared and unprepared + simultaneously by two different threads. Now a lock is in place and a + suitable check is done. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=759773 + +2015-12-09 18:24:24 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp-media: Add property to decide if sending media should be stopped when a client disconnects without TEARDOWN + Without TEARDOWN it might be desireable to keep the media running and continue + sending data to the client, even if the RTSP connection itself is + disconnected. + Only do this for session medias that have only UDP transports. If there's at + least on TCP transport, it will stop working and cause problems when the + connection is disconnected. + https://bugzilla.gnome.org/show_bug.cgi?id=758999 + +2015-12-24 15:29:33 +0100 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.7.1 === + +2015-12-24 14:54:06 +0100 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.7.1 + +2015-12-21 00:43:49 +0100 Koop Mast <kwm@rainbow-runner.nl> + + * configure.ac: + configure: Make -Bsymbolic check work with clang. + Update the -Bsymbolic check with the version glib has. This version + works with clang. + https://bugzilla.gnome.org/show_bug.cgi?id=759713 + +2015-11-17 22:30:54 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-session-pool.c: + rtsp-session-pool: Avoid dollar sign ($) in session ids + Live555 in VLC strips off dollar signs and then gets very confused, + we don't loose too much entropy by just skipping it. + +2015-11-10 14:17:18 -0500 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-server/rtsp-thread-pool.h: + * gst/rtsp-server/rtsp-token.h: + rtsp-server: Add g_autoptr() support to all types + https://bugzilla.gnome.org/show_bug.cgi?id=754464 + +2015-12-08 08:27:20 +0100 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fixed valgrind error + Fixed the valgrind error in unit test. The UDP source created during + gst_rtsp_stream_join_bin() was not released while destroying the rtp + bin. + https://bugzilla.gnome.org/show_bug.cgi?id=759010 + +2015-12-07 09:11:35 -0500 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * autogen.sh: + * common: + Automatic update of common submodule + From b319909 to 86e4663 + +2015-11-18 11:14:39 +0100 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: suspend media during setup request + SETUP request from clients needs to suspend the media to clear the + prerolled buffers. Otherwise it will not affect the prerolled buffer + and the prerolled buffers will be incorrect (for example block-size + from setup request will not affect the prerolled buffer unless the + media is suspended). + https://bugzilla.gnome.org/show_bug.cgi?id=758268 + +2015-12-04 08:01:37 +0100 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: create stream pipeline based on transport + Based on the protocol, create the rtsp stream pipeline. If only TCP or + only UDP is set as the transport protocol, it will not add the extra tee + or queue element to the pipeline. Both these elements will be added, if + it supports both TCP and UDP protocols. This improves the pipeline + performance when one protocol is present. + https://bugzilla.gnome.org/show_bug.cgi?id=758179 + +2015-11-19 15:01:16 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Only create RTP sending/receiving rtpbin pads if needed + Adding them when not needed will start some logic inside rtpbin that might be + problematic. Also if e.g. for a sender media we suddenly receive RTP data, we + would start up a rtpjitterbuffer and behave in weird ways. + We still set up the UDP sources for RTP receiving for a sender media to be + able to receive any packets sent by the client for NAT traversal. They will + all go to a fakesink though. + Having an rtpjitterbuffer in the media pipeline will cause the pipeline to be + NO_PREROLL, which will cause deadlocks when seeking the media as it will never + receive ASYNC_DONE after a seek. + https://bugzilla.gnome.org/show_bug.cgi?id=758319 + +2015-11-17 12:44:38 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Disable multicast loopback for the multicast udp sources too + On POSIX this setting is for sender sockets, on Windows for receiver sockets. + Previously we were only setting this for sender sockets, which caused looped + back packets to be received on Windows if a multicast transport was used. + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * examples/test-record-auth.c: + * examples/test-record.c: + examples: Actually use the provided port in the record examples + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * examples/test-record-auth.c: + test-record-auth: Add the option to build in TLS support + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * examples/test-auth.c: + test-auth: Use an 'anonymous' user for unauthenticated default + There's a comment on one of the resources that 'user' and 'admin' + shouldn't even be able to see it, but they can if the default + token is 'admin2', since that gives them access anyway. + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-record-auth.c: + Add test-record-auth example + +2015-11-17 01:12:28 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + rtsp-client: Report RECORD and ANNOUNCE as supported in the OPTIONS + +2015-11-11 14:58:33 +0100 Marcus Prebble <prebble@axis.com> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: Change the logic so we don't pop a NULL context + When doing a port scan (e.g. with nmap) the call to GST_RTSP_CHECK() + will sometimes fail. This call is made before any context is pushed + resulting in an attempt to pop a NULL context. + https://bugzilla.gnome.org/show_bug.cgi?id=757949 + +2015-10-22 14:32:30 +0200 David Svensson Fors <davidsf@axis.com> + + * tests/check/gst/rtspserver.c: + rtspserver: Add udp-mcast transport SETUP test + Refactor utility functions in the test file so they can handle + more than UDP and TCP as lower transport. + https://bugzilla.gnome.org/show_bug.cgi?id=756969 + +2015-10-22 09:15:21 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Always unref return value of gst_object_get_parent() + Fixes a leak of a GstBin in the udp-mcast case. + https://bugzilla.gnome.org/show_bug.cgi?id=756968 + +2015-10-21 14:37:19 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From b99800a to b319909 + +2015-10-20 17:29:42 +0300 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Use new GST_ENABLE_EXTRA_CHECKS #define + https://bugzilla.gnome.org/show_bug.cgi?id=756870 + +2015-10-21 14:28:47 +0300 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From 6babecd to b99800a + +2015-10-02 22:25:47 +0300 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Update GLib dependency to 2.40.0 + +2015-10-02 16:11:05 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * examples/test-mp4.c: + * gst/rtsp-server/rtsp-stream.c: + stream: listen to sender ssrc signals + https://bugzilla.gnome.org/show_bug.cgi?id=746747 + +2015-09-29 13:00:51 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + common: update for new suppression + Makes check-valgrind pass with glib 2.46 + +2015-09-28 17:40:59 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Take reference to media that will be prepared + default_prepare() takes a transfer-none reference GstRTSPMedia object. + Later on a g_idle_source_new() is created and a pointer to the media + object is passed as user data. If the media is freed before the idle + source is dispatched the media object pointer is invalid, but the idle + source callback expects it to still be valid. To fix this a reference to + the media object is taken when registering the source callback function + and a corresponding release of the reference is done when the souce is + destroyed. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=755748 + +2015-08-20 17:01:24 +0900 Vineeth TM <vineeth.tm@samsung.com> + + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-record.c: + * examples/test-uri.c: + rtsp-server: Fix memory leaks when context parse fails + When g_option_context_parse fails, context and error variables are not getting free'd + which results in memory leaks. Free'ing the same. + And replacing g_error_free with g_clear_error, which checks if the error being passed + is not NULL and sets the variable to NULL on free'ing. + https://bugzilla.gnome.org/show_bug.cgi?id=753863 + +2015-09-25 23:51:17 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.6.0 === + +2015-09-25 23:32:52 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.6.0 + +=== release 1.5.91 === + +2015-09-18 20:12:06 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.5.91 + +2015-09-17 20:07:34 +0100 Tim-Philipp Müller <tim@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-stream.c: + stream: fix docs for recently-added get/set_buffer_size API + https://bugzilla.gnome.org/show_bug.cgi?id=749095 + +2015-09-04 11:23:43 +1000 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't crash on encrypted RTX SDP + In parse_keymgmt(), don't mutate the input string that's been passed + as const, especially since we might need the original value again if + the same key info applies to multiple streams (RTX, for example). + https://bugzilla.gnome.org/show_bug.cgi?id=754753 + +2015-08-22 20:59:40 +1000 Jan Schmidt <jan@centricular.com> + + * examples/test-mp4.c: + test-mp4: Support filenames with spaces in them. Error out on too few arguments + +2015-08-17 02:36:31 +1000 Jan Schmidt <jan@centricular.com> + + * examples/test-record.c: + test-record: Check parameter count and print out help + If no launch pipeline was supplied, print out some help + +2015-08-31 22:48:34 +1000 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: Implement UDP buffer size setting. + Add gst_rtsp_stream_(get|set)_buffer_size and use it to configure the + UDP TX buffer size. + Incorporates a patch by Hyunjun Ko <zzoon.ko@samsung.com> + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=749095 + +2015-08-31 22:47:45 +1000 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-media.h: + rtsp-media: Fix small typo causing gtk-doc to complain + +=== release 1.5.90 === + +2015-08-19 14:15:23 +0300 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.5.90 + +2015-08-12 14:33:44 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: get port number through gst_rtsp_url_get_port + https://bugzilla.gnome.org/show_bug.cgi?id=753473 + +2015-08-13 11:24:10 +0200 Francisco Velazquez <francisv@ifi.uio.no> + + * tests/check/gst/media.c: + media-test: Removing unnecessary assertion + https://bugzilla.gnome.org/show_bug.cgi?id=753385 + +2015-07-23 14:50:30 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-server.c: + Document that source keeps a ref on server until it's destroyed + https://bugzilla.gnome.org/show_bug.cgi?id=749227 + +2015-08-08 11:09:57 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * tests/check/gst/media.c: + media-test: Test for multiple dynamic payload + https://bugzilla.gnome.org/show_bug.cgi?id=753385 + +2015-08-08 09:40:09 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: Only add fakesink once per pipeline + The intention is to prevent going PLAYING state before pads are created. + If there was mutilple dynamic payload, it would leak few fakesink and + actually prevent from ever reaching playing state. + https://bugzilla.gnome.org/show_bug.cgi?id=753385 + +2015-08-08 09:08:37 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + Revert "rtsp-media: Only add 1 fakesink per pipeline" + This reverts commit 22bf61f16c1210bb458fc3f53642179a0211104f. + +2015-08-07 09:21:36 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Only add 1 fakesink per pipeline + There should be only one fakesink per pipeline, not per dynpay. This + would lead to element naming clash. + +2015-07-30 15:32:43 +0900 Vineeth TM <vineeth.tm@samsung.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: assertion error due to wrong condition check + In media to caps function, reserved_keys array is being used for variable i, + leading to GLib-CRITICAL **: g_ascii_strcasecmp: assertion 's1 != NULL' failed + changed it to variable j + https://bugzilla.gnome.org/show_bug.cgi?id=753009 + +2015-07-29 11:27:05 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Strip keys from the fmtp that we use internally in our caps + Skip keys from the fmtp, which we already use ourselves for the + caps. Some software is adding random things like clock-rate into + the fmtp, and we would otherwise here set a string-typed clock-rate + in the caps... and thus fail to create valid RTP caps + https://bugzilla.gnome.org/show_bug.cgi?id=753009 + +2015-07-20 16:37:44 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + threadpool: Fix possible warning in gst_rtsp_thread_pool_cleanup() + https://bugzilla.gnome.org/show_bug.cgi?id=752640 + +2015-07-03 22:00:00 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From f74b2df to 9aed1d7 + +2015-06-25 00:04:28 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.5.2 === + +2015-06-24 23:44:37 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.5.2 + +2015-06-18 13:12:04 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * tests/check/gst/client.c: + rtsp-client: allow application to decide what requirements are supported + Add "check-requirements" signal and vfunc to allow application + (and subclasses) to check the requirements. + Based on patch from Hyunjun Ko <zzoon.ko@samsung.com> + https://bugzilla.gnome.org/show_bug.cgi?id=749417 + +2015-06-16 17:50:26 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> + + * common: + Automatic update of common submodule + From 6015d26 to f74b2df + +2015-06-11 17:39:00 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Always use real payloader when creating streams + A bin that contains the real payloader might be used as payloader. In this + case we have to get the real payloader for the various properties it provides. + Example use cases for this are bins that payload some media and then have + additional elements that add metadata or RTP extension headers to the stream. + https://bugzilla.gnome.org/show_bug.cgi?id=750800 + +2015-06-13 17:14:43 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + test-netclock: Use gst_pipeline_set_latency() to set a high-enough, equal latency for all receivers + +2015-06-12 23:35:32 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + * examples/test-netclock.c: + test-netclock: Use new ntp-time-source property on rtpbin + Select the clock time to be used as NTP time source. This allows proper + synchronization between receivers, independent of sharing base times, and just + requires them to use the same clock. + +2015-06-11 20:41:31 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + * examples/test-netclock.c: + test-netclock: Setting the same base time on sender and receiver is not necessary + It's going to be fixed up by rtpbin when using ntp-sync=TRUE + +2015-06-11 17:38:52 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: add description for gst_rtsp_stream_request_aux_sender + https://bugzilla.gnome.org/show_bug.cgi?id=750764 + +2015-06-11 18:10:12 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * docs/libs/gst-rtsp-server.types: + docs: add missing types + https://bugzilla.gnome.org/show_bug.cgi?id=750764 + +2015-06-11 17:37:25 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * docs/libs/gst-rtsp-server-sections.txt: + docs: add missing apis + https://bugzilla.gnome.org/show_bug.cgi?id=750764 + +2015-06-10 17:14:18 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + test-netclock-client: Use ntp-sync=TRUE and buffer-mode=SYNC for proper synchronization + +2015-06-05 22:35:39 -0400 Xavier Claessens <xavier.claessens@collabora.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + GstRTSPAuth: Add client certificate authentication support + https://bugzilla.gnome.org/show_bug.cgi?id=750471 + +2015-06-09 13:53:47 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + test-netclock-client: Use new GstClock API to wait for clock synchronization + +2015-06-09 13:51:02 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-netclock-client.c: + test-netclock-client: Use a GMainLoop and playbin's source-setup signal + A mainloop is needed to get glimagesink to display something on OSX, and + the source-setup signal just makes things a little bit easier. + +2015-06-09 11:30:54 +0200 Edward Hervey <bilboed@bilboed.com> + + * common: + Automatic update of common submodule + From d9a3353 to 6015d26 + +2015-06-08 23:08:34 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From d37af32 to d9a3353 + +2015-06-07 23:07:31 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 21ba2e5 to d37af32 + +2015-06-07 17:32:29 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From c408583 to 21ba2e5 + +2015-06-07 17:06:40 +0200 Stefan Sauer <ensonic@users.sf.net> + + * docs/libs/Makefile.am: + docs: remove variables that we define in the snippet from common + This is syncing our Makefile.am with upstream gtkdoc. + +2015-06-07 17:16:47 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 44a3517 to c408583 + +2015-06-07 16:44:55 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.5.1 === + +2015-06-07 11:20:01 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.5.1 + +2015-05-25 16:36:18 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: No flush during Teardown. + When calling gst_rtsp_watch_write_data in gstrtspconnection.c and + backlog is empty it can happen that just a part of a message will be + sent and rest is in backlog queue. If then flush during teardown + just a part of message will be sent.This can lead to client miss + teardown response since it expect to get the last part of message. + The flushing during teardown was introduced to fix a deadlock that now + is fixed more generally in handle_request by temporary setting backlog + size to unlimited. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=749845 + +2015-05-27 17:04:41 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/Makefile.am: + tests: Use AM_TESTS_ENVIRONMENT + Needed by the new automake test runner and the + current version of the common submodule. + +2015-05-20 17:05:47 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.h: + rtsp-server: Use single-include rtsp header to make sure we get all definitions + +2015-05-05 16:46:57 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Mark some more functions static + +2015-05-05 16:46:19 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Only unblock the media in suspend() when actually changing the state + Otherwise we're going to lose a few packets for live streams during DESCRIBE. + +2015-05-04 16:33:08 +0200 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-video-rtx.c: + examples: Use AVPF profile for the RTX example + +2015-05-04 16:31:20 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Only add RTX to the SDP when using a feedback profile + +2015-04-27 19:35:53 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: get valid clock-rate from last-sample + clock-rate in last-sample's caps is integer, not unsigned. + To get this value properly, variable needs to be type-casted to int. + https://bugzilla.gnome.org/show_bug.cgi?id=747614 + +2015-04-26 15:00:05 +0100 Tim-Philipp Müller <tim@centricular.com> + + * autogen.sh: + * common: + autogen.sh: only run autopoint if gettext requested in configure.ac + Not just because there happens to be a po directory. + https://bugzilla.gnome.org/show_bug.cgi?id=748058 + +2015-04-26 14:58:49 +0100 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + Revert "configure.ac: uncomment gettext version setup" + This reverts commit 1545d8fef7065081079172ec264a0061039ac075. + We don't need a gettext setup here and there's no po + directory either, so no reason why autopoint would be + run in the first place. + See https://bugzilla.gnome.org/show_bug.cgi?id=748058 + +2015-04-23 18:53:08 +0100 Alistair Buxton <a.j.buxton@gmail.com> + + * examples/test-multicast.c: + * examples/test-multicast2.c: + * examples/test-sdp.c: + * examples/test-video-rtx.c: + * examples/test-video.c: + * tests/test-cleanup.c: + * tests/test-reuse.c: + Fix timeout function signatures across tests and examples + +2015-04-23 17:27:40 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/Makefile.am: + tests: define GST_CHECK_TEST_ENVIRONMENT_BEACON + Make sure the test environment is set up. + https://bugzilla.gnome.org//show_bug.cgi?id=747624 + +2015-04-23 17:22:59 +0100 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + configure: bump automake requirement to 1.14 and autoconf to 2.69 + This is only required for builds from git, people can still + build tarballs if they only have older autotools. + https://bugzilla.gnome.org//show_bug.cgi?id=747624 + +2015-04-20 08:49:57 +0100 Vincent Penquerc'h <vincent.penquerch@collabora.co.uk> + + * configure.ac: + configure.ac: uncomment gettext version setup + Fixes autogen.sh. It would run autopoint, which would complain + that it could not find the gettext version in configure.ac. + https://bugzilla.gnome.org/show_bug.cgi?id=748058 + +2015-04-15 10:06:30 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * examples/test-video-rtx.c: + test-video-rtx: set exact payload type to PCMA payloader + Setting wrong payload type causes failure to do retransmission through audio stream + https://bugzilla.gnome.org/show_bug.cgi?id=747839 + +2015-04-15 09:45:23 +0900 Hyunjun Ko <zzoon.ko@samsung.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: fix to get valid each stream data for request-aux-sender signal + Because of duplicated g_signal_connect for request-aux-sender signal, + wrong stream pointer is passed to the signal handler. + Instead of passing each stream, pass stream array and get the relevant stream. + https://bugzilla.gnome.org/show_bug.cgi?id=747839 + +2015-04-06 10:32:52 +0100 Tim-Philipp Müller <tim@centricular.com> + + * acinclude.m4: + * autogen.sh: + Update autogen.sh to latest version from common + Fixes build after aclocal_check etc. helpers have been removed. + +2015-04-03 18:58:26 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From bc76a8b to c8fb372 + +2015-03-23 21:03:20 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Limit the queues to 1 buffer + We only need them to be able to pre-roll, queueing up more data here + is only going to harm latency and memory usage. + +2015-03-23 20:59:52 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Update comment and ASCII art to the latest code + We have a queue in front of the udpsink too to prevent the pipeline from + locking up. + +2015-03-21 11:04:05 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-media: Properly return first rtptime + Instead we where returning first GstBuffer timestamp. This would result + in clock skew and unwanted behaviour in RTSP playback. + https://bugzilla.gnome.org/show_bug.cgi?id=746479 + +2015-03-18 16:44:19 -0400 Nicolas Dufresne <nicolas.dufresne@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Don't leave buffer mapped + If the seq is NULL, the RTP buffer was left mapped. We should always + unmap the buffer. + +2015-03-15 12:27:39 +0000 Sebastian Dröge <sebastian@centricular.com> + + * README: + Fix typo in README + +2015-03-10 09:39:22 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * tests/check/gst/client.c: + Fix double semicolons + +2015-03-09 16:00:07 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Get the seqnum-base and other information from the last buffer in the sink + This gives more accurate values than asking the payloader. There might be + queueing happening between the payloader and the sink. + https://bugzilla.gnome.org/show_bug.cgi?id=745704 + +2015-03-09 13:00:25 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't seek for PLAY if the position will not change + https://bugzilla.gnome.org/show_bug.cgi?id=745704 + +2015-03-09 10:21:49 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't include payload type in the caps for framesize + When the sdp media attribute framesize are converted to caps + the <payload> should not be included. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725335 + Based on the patch for rtspsrc by Linus Svensson <linussn@axis.com> + +2014-02-26 22:34:06 +0100 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: add payload type to the sdp framesize attribute + The sdp framesize attribute is desribed in RFC6064. It is specified + for payloading of H263 and has the following form + a=framesize:<payload type> <width>-<height>. The <width>-<height> part + should be added to the caps in a payloader and the <payload type> should + be added by the rtsp-server. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725334 + +2015-03-03 13:51:01 +0000 Luis de Bethencourt <luis.bg@samsung.com> + + * examples/test-uri.c: + examples: test-uri: fix tainted variable + Insignificant but this keeps Coverity happy. + CID #1268404 + +2015-03-03 01:49:42 +1100 Jan Schmidt <jan@centricular.com> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-netclock-client.c: + * examples/test-netclock.c: + examples: Add a simple example of network synch for live streams. + An example server and client that works for synchronising live streams + only - as it can't support pause/play. + +2015-03-03 01:49:42 +1100 Jan Schmidt <jan@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + rtsp-media-factory: Add functions to set/get the media gtype + Allow specifying the GType of a GstRtspMedia subclass to create + as a simpler way to get the factory to create a custom + GstRtspMedia sub-class, without subclassing GstRtspMediaFactory. + +2015-02-27 17:45:42 +0100 Gregor Boirie <gregor.boirie@parrot.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: fix double unlock in _get_buffer_size() + Fixes an abort when calling gst_rtsp_media_get_buffer_size() + because of double g_mutex_unlock () usage. + https://bugzilla.gnome.org/show_bug.cgi?id=745434 + +2015-02-19 10:43:16 +0200 Kent-Inge Ingesson <kenti@axis.com> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp-session: Use monotonic time for RTSP session timeout + Changed RTSP session timeout handling to monotonic time + and deprecating the API for current system time. + This fixes timeouts when the system time changes. + https://bugzilla.gnome.org/show_bug.cgi?id=743346 + +2015-02-13 12:21:16 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-client: Only error out in PLAY if seeking actually failed + If the media was just not seekable, we continue from whatever position we are + and let the client decide if that is what is wanted or not. + Only if the actual seek failed, we can't really recover and should error out. + +2015-02-12 10:46:28 +0100 Andreas Frisch <fraxinas@opendreambox.org> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Add necessary queues between tee and multiudpsink + https://bugzilla.gnome.org/show_bug.cgi?id=744379 + +2015-02-12 16:48:46 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-media: If seeking fails, don't wait forever for the media to preroll again + Instead error out properly the same way as if the SEEKING query already + failed. + +2015-02-11 17:24:38 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-stream.h: + rtsp-stream: minor code formatting fix + +2015-02-10 16:39:58 +0000 Luis de Bethencourt <luis.bg@samsung.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: fix logic for collect_streams + Fix the logic of gst_rtsp_media_collect_streams() so after looping collecting + all streams it knows if it got any, and can check if the transport mode is OK. + CID #1268400 + +2015-02-09 10:21:50 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Don't set the transport mode based on what elements we find + Just print a warning if the one that was set before disagrees with what + elements we found. It must already be set to something before as this + function is called after we received the SDP from ANNOUNCE in RECORD mode, + and we would reject ANNOUNCE if the RECORD flag was not set. + +2015-02-08 18:05:50 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspserver.c: + tests: rtspserver: rename shadowed variable + We have two different 'sink' variables here, + rename one of them for clarity. + +2015-02-08 12:08:36 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: fix awkward if clause + +2015-02-06 19:34:17 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-uri.c: + examples: test-uri: improve uri argument handling and accept file names + Print an error if the argument passed is not a URI and can't + be converted into one, or no arguments have been provided. + +2015-02-06 19:15:40 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-uri.c: + examples: test-uri: don't remove mount point after 10 seconds + It's very irritating when trying to test stuff repeatedly + and serves no real purpose other than showing that it can + be done. + +2015-01-21 17:32:21 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/.gitignore: + examples: add new test-record to .gitignore + +2015-01-28 18:54:01 +0100 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-record.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * tests/check/gst/rtspserver.c: + rtsp-media: Use flags to distinguish between PLAY and RECORD media + +2015-01-28 17:49:16 +0100 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-record.c: + test-record: Set latency for playback-style example to 2s instead of 200ms + +2015-01-21 17:27:56 +0000 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspserver.c: + tests: add some unit tests for ANNOUNCE and RECORD + https://bugzilla.gnome.org/show_bug.cgi?id=743175 + +2015-01-21 16:32:44 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: fix a couple of leaks in handle_announce + +2015-01-19 13:20:39 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp-media: Expose latency setting for setting the rtpbin latency + +2015-01-17 10:28:13 +0100 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-record.c: + test-record: Use GOptionContext to parse the server port and take the pipeline from the commandline + +2015-01-16 20:48:42 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Put the timestamp of receival of the initial packet over TCP on the first buffer + +2015-01-09 12:40:47 +0100 Sebastian Dröge <sebastian@centricular.com> + + * examples/Makefile.am: + * examples/test-record.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + Add initial support for RECORD + We currently only support media that is RECORD or PLAY only, not both at once. + https://bugzilla.gnome.org/show_bug.cgi?id=743175 + +2015-01-30 12:50:20 +0100 Anila Balavan <anilabn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: RTCP and RTP transport cache cookies seperated + RTCP packets were not sent because the same tr_cache_cookie was used for + both RTP and RTCP. So only one of the tr_cache lists were populated + depending on which one was sent first. If the tr_cache list is not + populated then no packets can be sent. Most often this happened to be + RTCP. Now seperate RTCP and RTP transport cache cookies are added which + resulted in both the tr_cache_lists to be populated regardless of which + one was sent first. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=743734 + +2015-01-21 14:57:03 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: fix false compiler warning + rtsp-stream.c:3034: error: ‘visited’ may be used uninitialized in this function + +2015-01-19 20:35:15 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: log interleaved data received + +2015-01-19 20:18:20 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: fix unintentional fallthrough to debug warning when receiving interleaved data + +2015-01-19 13:09:20 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: If we have a single-stream media and SETUP contains no control, use the one and only stream + +2015-01-18 19:08:36 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Use a random session ID in the SDP + RFC4566 Section 5.2 says that it should make the username, session id, + nettype, addrtype and unicast address tuple globally unique. Always using + 1188340656180883 is not going to guarantee that: https://xkcd.com/221/ + Instead let's create a 64 bit random number, which at least brings us + closer to the goal of global uniqueness. + https://tools.ietf.org/html/rfc4566#section-5.2 + +2015-01-17 10:29:36 +0100 Sebastian Dröge <sebastian@centricular.com> + + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-uri.c: + examples: Don't call gst_init() and gst_get_option_group() + The latter calls the former at the appropriate time. + +2015-01-16 20:04:01 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Drop trailing \0 of RTSP DATA messages + We add a trailing \0 in GstRTSPConnection to make parsing of + string message bodies easier (e.g. the SDP from DESCRIBE) but + for actual data this means we have to drop it or otherwise + create invalid data. + +2015-01-16 11:10:20 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Have one copy of the transports cache for RTP and RTCP each + Fixes crash when two threads access handle_new_sample() at the same + time, one for RTP, one for RTCP. + Otherwise, when iterating over the transports cache, it might be modified by + another thread at the same time if the transports cookie has changed. + https://bugzilla.gnome.org/show_bug.cgi?id=742954 + +2015-01-15 19:34:20 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Set format=TIME on our app sources for TCP + +2015-01-13 15:29:29 +0100 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-session-pool.c: + Revert "rtsp-session-pool: Make sure session IDs are properly URI-escaped" + This reverts commit 935e8f852d050b4939f1d0f44b38e9b55a2fbe36. + RFC 2326 states that session IDs may consist of alphanumeric as well as + the safe characters $-_.+ -- N.B. the percent character is not allowed. + Previously the session ID was URI-escaped, this meant that any character + which was not alphanumeric or any of the characters +-._~ would be + percent encoded. While the RFC (surprisingly) mentions that linear white + space in session IDs should be URI-escaped, it does not say anything + about other characters. Moreover no white space is allowed in the + session ID. Finally the percent character which is the result of + URI-escaping is not allowed in a session ID. + So there is no reason to do any URI-escaping, and now it is removed. + https://bugzilla.gnome.org/show_bug.cgi?id=742869 + +2015-01-12 16:14:12 +0100 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From f2c6b95 to bc76a8b + +2014-12-31 13:04:57 +0000 Tim-Philipp Müller <tim@centricular.com> + + * Makefile.am: + Fix 'make check' from top-level directory + +2014-12-30 18:13:49 +0530 Nirbheek Chauhan <nirbheek@centricular.com> + + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-uri.c: + examples: Add command-line parsing and take a 'port' argument + This allows users to run multiple servers on different ports for testing. + Only done for examples that actually take arguments and hence are capable of + outputting different streams for each instance on each port. + https://bugzilla.gnome.org/show_bug.cgi?id=742115 + +2014-12-29 12:06:50 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: Add a send_message default signal handler + This allows subclasses to easily hook into the response sending + mechanism without doing everything from a signal, which seems + awkward from subclasses. + +2014-12-18 10:56:44 +0100 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From ef1ffdc to f2c6b95 + +2014-12-17 20:02:05 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * Makefile.am: + * configure.ac: + configure: add --disable-examples switch + https://bugzilla.gnome.org/show_bug.cgi?id=741678 + +2014-12-01 23:42:34 +1100 Matthew Waters <matthew@centricular.com> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-video-rtx.c: + examples: add a retransmisison example implementing RFC4588 + Currently only SSRC-multiplexed rtx streams are supported + +2014-12-16 16:46:15 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix some minor memory leaks + +2014-12-16 16:46:06 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Some minor cleanup + +2014-12-16 16:42:13 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Fix compiler warnings + rtsp-stream.c:1351:3: error: non-void function 'gst_rtsp_stream_get_retransmission_time' should return a value [-Wreturn-type] + g_return_if_fail (GST_IS_RTSP_STREAM (stream)); + ^ + rtsp-stream.c:1384:3: error: non-void function 'gst_rtsp_stream_get_retransmission_pt' should return a value [-Wreturn-type] + g_return_if_fail (GST_IS_RTSP_STREAM (stream)); + ^ + +2014-11-27 01:12:36 +1100 Matthew Waters <matthew@centricular.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + media: implement ssrc-multiplexed retransmission support + based off RFC 4588 and the server-rtpaux example in -good + +2014-11-28 12:45:14 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp: Ref transports in hash table. + Also ref streams for transports. + This solves a crash when reciving a rtcp after teardown but before + client finalize. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=740845 + +2014-11-27 17:13:05 +0100 Edward Hervey <bilboed@bilboed.com> + + * common: + Automatic update of common submodule + From 7bb2bce to ef1ffdc + +2014-11-07 12:48:53 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: refactor cleanup of cached media + +2014-10-23 13:39:10 +0200 Linus Svensson <linussn@axis.com> + + * tests/check/gst/client.c: + tests: Remove FIXME + The session leak is now fixed, lets remove those FIXME comments. + +2014-10-23 17:54:37 +0200 Linus Svensson <linussn@axis.com> + + * tests/check/gst/rtspserver.c: + tests: Test to setup two sessions on one connection + https://bugzilla.gnome.org/show_bug.cgi?id=739112 + +2014-10-24 12:05:27 +0200 Linus Svensson <linussn@axis.com> + + * tests/check/gst/rtspserver.c: + tests: Test setup with tcp transport + https://bugzilla.gnome.org/show_bug.cgi?id=739112 + +2014-10-24 12:04:54 +0200 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Configure transport after creating session media + The default implementation of configure_client_transport() in + rtsp-client uses the session media when it chooses channels for + interleaved traffic. + https://bugzilla.gnome.org/show_bug.cgi?id=739112 + +2014-10-23 12:54:03 +0200 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + client: Stop caching media in client when doing setup + If the media has been managed by a session media, it should not be + cached in the client any longer. The GstRTSPSessionMedia object is now + responsible for unpreparing the GstRTSPMedia object using + gst_rtsp_media_unprepare(). Unprepare the media when finalizing the + session media. + https://bugzilla.gnome.org/show_bug.cgi?id=739112 + +2014-10-31 23:01:53 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: unref srtp decoder when leaving bin + https://bugzilla.gnome.org/show_bug.cgi?id=739481 + +2014-10-29 21:01:39 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: mikey memory leaks + https://bugzilla.gnome.org/show_bug.cgi?id=739383 + +2014-10-27 18:01:35 +0100 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From 84d06cd to 7bb2bce + +2014-10-24 17:48:04 +0100 Tim-Philipp Müller <tim@centricular.com> + + * Makefile.am: + Parallelise 'make check-valgrind' + +2014-10-21 13:04:14 +0100 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From a8c8939 to 84d06cd + +2014-10-21 13:00:49 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 36388a1 to a8c8939 + +2014-10-01 07:12:30 -0400 Vincent Penquerc'h <vincent.penquerch@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: deactivate media when shutting down from paused + This was only done when going directly from playing. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=737829 + +2014-10-20 15:40:59 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-context.h: + rtsp-client: add stream transport to context + We add the stream transport to the context so we can get the configured + client stream transport in the setup request signal. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=738905 + +2014-10-02 12:02:48 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: release lock even not all transports have been removed + We don't want to keep the lock even we return FALSE because not all the + transports have been removed. This could lead into a deadlock. + https://bugzilla.gnome.org/show_bug.cgi?id=737797 + +2014-10-10 18:43:00 -0400 Olivier Crête <olivier.crete@ocrete.ca> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Rename clock-base and seqnum-base to timestamp-offset and seqnum-offset + These were renamed in GstRTPBasePayload in 1.0 + +2014-09-30 16:36:51 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + client: set session media to NULL without the lock + We need to set session medias to NULL without the client lock otherwise + we can end up in a deadlock if another thread is waiting for the lock + and media unprepare is also waiting for that thread to end. + https://bugzilla.gnome.org/show_bug.cgi?id=737690 + +2014-09-30 23:22:45 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Set state to UNPREPARING in all cases + +2014-09-30 19:17:04 +0200 Ognyan Tonchev <otonchev@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + media: set state to unpreparing when unprepare is initiated + https://bugzilla.gnome.org/show_bug.cgi?id=737675 + +2014-09-30 01:35:02 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Remove backlog limit while processings requests + If the backlog limit is kept two cases of deadlocks may be + encountered when streaming over TCP. Without the backlog + limit this deadlocks can not happen, at the expence of + memory usage. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=737631 + +2014-09-22 13:32:06 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: do not free main context before rtsp watch + https://bugzilla.gnome.org/show_bug.cgi?id=737110 + +2014-09-19 18:29:00 +0200 Branko Subasic <branko@axis.com> + + * tests/check/gst/rtspserver.c: + tests: Extend unit test timeout to accomodate for valgrind + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=736647 + +2014-09-19 18:28:50 +0200 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream-transport.c: + rtsp-*: Treat sending packets to clients as keepalive + As long as gst-rtsp-server can successfully send RTP/RTCP data to + clients then the client must be reading. This change makes the server + timeout the connection if the client stops reading. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=736647 + +2014-09-19 18:28:30 +0200 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Allow backlog to grow while expiring session + Allow the send backlog in the RTSP watch to grow to unlimited size while + attempting to bring the media pipeline to NULL due to a session + expiring. Without this change the appsink element cannot change state + because it is blocked while rendering data in the new_sample callback. + This callback will block until it has successfully put the data into the + send backlog. There is a chance that the send backlog is full at this + point which means that the callback may block for a long time, possibly + forever. Therefore the media pipeline may also be prevented from + changing state for a long time. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=736647 + +2014-09-22 09:30:39 +0200 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Make old compilers happy + rtsp-client.c:2553:50: error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast] + Just in case that guint8 doesn't fit in a pointer. Just in case ... + +2014-09-16 11:41:52 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: raise the backlog limits before pausing + We need to raise the backlog limits before pausing the pipeline or else + the appsink might be blocking in the render method in wait_backlog() and + we would deadlock waiting for paused. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=736322 + +2014-09-16 11:29:38 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: make define for the WATCH_BACKLOG + See https://bugzilla.gnome.org/show_bug.cgi?id=736322 + +2014-09-09 18:11:39 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: simplify session transport handling + link/unlink of the transport in a session was done to keep track of all + TCP transports and to send RTP/RTCP data to the streams. We can simplify + that by putting all the TCP transports in a hashtable indexed with the + channel number. + We also don't need to link/unlink the transports when we pause/resume + the streams. The same effect is already achieved when we pause/play the + media. Indeed, when we pause the media, the transport is removed from + the media and the callbacks will not be called anymore. + See https://bugzilla.gnome.org/show_bug.cgi?id=736041 + +2014-09-09 18:10:12 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + stream-transport: make method to handle received data + Make a method to handle the data received on a channel. It sends the + data to the stream of the transport on the RTP or RTCP pads based on + the channel number. + +2014-09-15 16:54:05 +0200 Wim Taymans <wtaymans@redhat.com> + + * examples/test-mp4.c: + test: add example of dumping RTCP reports + +2014-09-08 09:26:23 +0200 Srimanta Panda <srimanta@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-media: Make sure that sequence numbers are monotonic after pause + The sequence number is not monotonic for RTP packets after pause. The + reason is basepayloader generates a randon sequence number when the + pipeline goes from ready to pause. With this fix generation of sequence + number will be monotonic when going from pause to play request. + https://bugzilla.gnome.org/show_bug.cgi?id=736017 + +2014-08-28 13:35:15 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Protect saved clients watch with a mutex + Fixes a crash when close() is called while merging clients + in handle_tunnel(). In that case close() would destroy the + watch while it is still being used in handle_tunnel(). + https://bugzilla.gnome.org/show_bug.cgi?id=735570 + +2014-08-13 17:22:16 +0300 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Remove the multicast group udp sources when removing from the bin + +2014-08-05 16:12:19 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp-media: Query position and stop time only on the RTP parts of the pipeline + The RTCP parts, in specific the RTCP udpsinks, are not flushed when + seeking and will always continue counting the time. This leads to + the NPT after a backwards seek to be something completely different + to the actual seek position. + https://bugzilla.gnome.org/show_bug.cgi?id=732644 + +2014-08-09 14:41:35 +0100 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-appsrc.c: + examples: fix another reference leak + gst_rtsp_media_get_element() returns a new ref. + +2014-07-17 01:34:17 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * examples/test-appsrc.c: + examples: unref element after usage + gst_bin_get_by_name_recurse_up() returns an element + reference that must be unreffed after usage. + https://bugzilla.gnome.org/show_bug.cgi?id=734546 + +2014-07-02 22:45:07 +0530 Arun Raghavan <arun@accosted.net> + + * gst/rtsp-server/rtsp-media.c: + signals: Fix copy-pasto in target-state signal offset + +2014-08-01 10:46:44 +0200 Edward Hervey <edward@collabora.com> + + * Makefile.am: + * common: + Makefile: Add usage of build-checks step + Allows building checks without running them + +2014-06-25 18:23:10 +0200 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Listen on the multicast group for RTP/RTCP packets + When a UDP multicast transport is used it is expected that the server listens + for RTP and RTCP packets on the multicast group with the corresponding port. + Without this we will never get RTCP packets from clients in multicast mode. + https://bugzilla.gnome.org/show_bug.cgi?id=732238 + +2014-07-19 18:04:52 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.4.0 === + +2014-07-19 17:56:31 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.4.0 + +2014-07-16 20:39:42 +0900 Hyunjun Ko <zzoonis@gmail.com> + + * gst/rtsp-server/rtsp-media.h: + media: correct misspelled words in description + https://bugzilla.gnome.org/show_bug.cgi?id=733244 + +=== release 1.3.91 === + +2014-07-11 12:19:08 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.3.91 + +2014-07-10 17:37:45 +0200 Wim Taymans <wtaymans@redhat.com> + + * docs/libs/gst-rtsp-server-sections.txt: + docs: update docs + +2014-07-10 17:10:06 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-server.c: + server: implement client REMOVE filter + +2014-07-10 17:05:13 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: expose _close() method + Expose a previously internal close method to close the client + connection. + +2014-07-10 12:20:15 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-session-pool.c: + session-pool: signal session-removed outside of the lock + Release the lock before emiting the session-removed signal. + +2014-07-10 11:32:20 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream.c: + filter: Release lock in filter functions + Release the object lock before calling the filter functions. We need to + keep a cookie to detect when the list changed during the filter + callback. We also keep a hashtable to make sure we only call the filter + function once for each object in case of concurrent modification. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=732950 + +2014-07-09 15:16:08 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: check if watch is set in handle_teardown() + The unit tests run without a watch + +2014-07-09 14:19:10 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/gst/client.c: + client tests: send teardown to cleanup session + +2014-07-09 14:17:46 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/gst/rtspserver.c: + server tests: send teardown to cleanup session + +2014-07-09 15:01:31 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: keep ref to client for the session removed handler + This extra ref will be dropped when all client sessions have been + removed. A session is removed when a client sends teardown, closes its + endpoint of the TCP connection or the sessions expires. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=732226 + +2014-07-08 12:36:12 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session.c: + * tests/check/gst/client.c: + client: manage media in session as a last step + Once we manage a media in a session, we can't unmanage it anymore + without destroying it. Therefore, first check everything before we + manage the media, otherwise if something is wrong we have no way to + unmanage the media. + If we created a new session and something went wrong, remove the session + again. Fixes a leak in the unit test. + +2014-07-03 19:52:42 +0100 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-mp4.c: + * examples/test-ogg.c: + examples: print 'stream ready at url' for mp4 and ogg example + +2014-07-02 16:04:53 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.c: + rtsp: fix for MIKEY api change + +2014-07-01 16:12:13 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: free watch context only once + The watch context is freed when the source is destroyed. Avoids + a CRITICAL when we try to unref the context twice. + +2014-07-01 15:02:15 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: fix build + +2014-07-01 14:41:14 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: protect sessions with lock + Protect the list of sessions with the lock. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=732226 + +2014-07-01 12:13:47 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + Client: keep a ref to the session + Don't just keep a weak ref to the session objects but use a hard ref. We + will be notified when a session is removed from the pool (expired) with + the new session-removed signal. + Don't automatically close the RTSP connection when all the sessions of + a client are removed, a client can continue to operate and it can create + a new session if it wants. If you want to remove the client from the + server, you have to use gst_rtsp_server_client_filter() now. + Based on patch from Ognyan Tonchev <ognyan.tonchev at axis.com> + See https://bugzilla.gnome.org/show_bug.cgi?id=732226 + +2014-06-30 15:14:34 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + session-pool: add session-removed signal + Add a signal to be notified when a session is removed from the pool. + +2014-06-30 00:37:59 -0700 Evan Nemerson <evan@nemerson.com> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-server.h: + Make rtsp-server.h a single-include header, use it for G-I + https://bugzilla.gnome.org/show_bug.cgi?id=732411 + +=== release 1.3.90 === + +2014-06-28 11:48:29 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.3.90 + +2014-06-27 16:54:22 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: crypto can be NULL + +2014-06-11 16:42:08 -0700 Evan Nemerson <evan@nemerson.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + introspection: add missing allow-none annotations + https://bugzilla.gnome.org/show_bug.cgi?id=730952 + +2014-06-11 16:38:36 -0700 Evan Nemerson <evan@nemerson.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-token.c: + introspection: add (nullable) annotations to return values + https://bugzilla.gnome.org/show_bug.cgi?id=730952 + +2014-06-24 09:48:45 +0200 Evan Nemerson <evan@nemerson.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + gi: improve annotations + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=730953 + +2014-06-24 09:43:44 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + signals: use generic marshal function + Use the generic C marshal function. + Use more explicit type instead of G_TYPE_POINTER + +2014-06-24 09:42:47 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-context.h: + context: add type macro + +2014-06-24 09:34:50 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + sdp: hide key length defines + They don't have a namespace. + +2014-06-22 19:37:31 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.3.3 === + +2014-06-22 19:36:14 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.3.3 + +2014-05-20 14:48:37 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + mikey: add different key length parameters + Add encryption and authentication key length parameters to MIKEY. For + the encoders, the key lengths are obtained from the cipher and auth + algorithms set in the caps. For the decoders, they are obtained while + parsing the key management from the client. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=730472 + +2014-03-16 17:29:48 +0100 Ognyan Tonchev <otonchev@gmail.com> + + * tests/check/gst/stream.c: + stream tests: Make sure we get right multicast address from stream + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=731577 + +2014-06-12 13:49:17 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: ref the context until rtsp watch is alive + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=731569 + +2014-06-12 13:48:44 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Destroy the rtsp watch after connection close + +2014-06-13 16:46:06 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: fix confusing comment + +2014-05-27 12:36:52 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-session.c: + rtsp-session: Timeout in header. + Adding the possbilty to always have timout in header. + This is configurabe with setting "timeout-always-visible". + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=728264 + +2014-05-21 13:23:40 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.3.2 === + +2014-05-21 13:06:36 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * common: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.3.2 + +2014-05-21 10:54:05 +0200 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From 211fa5f to 1f5d3c3 + +2014-05-20 15:57:30 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: store TCP ports in transport + Store the TCP ports in the transport when we are doing RTSP over TCP. + This way, we can easily get to the ports from the transport. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=729776 + +2014-05-15 18:15:04 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: add signals for new RTP/RTCP encoders + New signals to allow the user to configure the dynamically created + encoders. + https://bugzilla.gnome.org/show_bug.cgi?id=730228 + +2014-05-14 09:31:31 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: Make suspend()/unsuspend() virtual + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=730109 + +2014-05-09 17:25:07 -0700 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + client: fix send-message signal marshaller + Use generic marshalling for the send-message signal. It has + two POINTER arguments, not just one. + https://bugzilla.gnome.org/show_bug.cgi?id=729900 + +2014-05-09 15:08:48 +0200 Wim Taymans <wtaymans@redhat.com> + + * tests/check/gst/media.c: + tests: add and remove pads only once + In this test we simulate a dynamic pad by watching the caps event. + Because of renegotiation in the base payloader now, this caps is sent + multiple times but we can only deal with 1 invocation, use a variable to + only 'add and remove' the pad once. + +2014-05-02 20:06:29 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/rtspserver.c: + tests: add unit test for correct handling of Require headers + https://bugzilla.gnome.org/show_bug.cgi?id=729426 + +2014-05-02 19:59:23 +0100 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: handle Require headers and respond with OPTION_NOT_SUPPORTED + Servers must handle Require headers and must report a failure + if they don't handle any of the Required options, see RFC 2326, + section 12.32: https://tools.ietf.org/html/rfc2326#page-54 + https://bugzilla.gnome.org/show_bug.cgi?id=729426 + +2014-05-03 20:48:43 +0200 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + Back to development + +=== release 1.3.1 === + +2014-05-03 18:40:24 +0200 Sebastian Dröge <sebastian@centricular.com> + + * ChangeLog: + * NEWS: + * RELEASE: + * configure.ac: + * gst-rtsp-server.doap: + Release 1.3.1 + +2014-05-03 10:18:00 +0200 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From bcb1518 to 211fa5f + +2014-05-02 19:58:15 +0100 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + Update .gitignore + +2014-05-02 19:57:23 +0100 Tim-Philipp Müller <tim@centricular.com> + + * tests/check/gst/sessionmedia.c: + tests: fix memory leak in sessionmedia unit test + +2014-05-01 06:17:06 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: emit a signal before sending a message + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=728970 + +2014-05-01 06:07:08 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: pass context to send_message + Pass the current context to send_message, we will need it later. + +2014-05-01 05:29:54 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: fix typo in comment + +2014-04-14 15:17:14 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: Do not stop thread twice if default_prepare() fails + +2014-04-15 16:51:17 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: set the watch to flushing before going to NULL + First set the watch to flushing so that we unblock any current and + future attempt to send data on the watch, Then set the pipeline to + NULL. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=728153 + +2014-04-11 23:52:49 +0200 Linus Svensson <linusp.svensson@gmail.com> + + * gst/rtsp-server/rtsp-session-pool.c: + * tests/check/gst/sessionpool.c: + rtsp-session-pool: Fixes annotation + Fixes annotation for gst_rtsp_session_pool_create() and memory leaks + in the sessionpool test. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=728060 + +2014-04-09 16:44:21 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: make media_prepare virtual + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=728029 + +2014-04-12 05:57:00 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + media: stop the thread in more error cases + +2014-04-12 05:53:15 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + media: allow NULL as the thread + Use the default context whan passing a NULL thread. + +2014-04-10 16:39:11 +0100 Vincent Penquerc'h <vincent.penquerch@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: indent cleanup + Coverity was moaning about unreachable code, and I think it was just + confused by { being before the label. We'll see if it pops up again. + Coverity 1197705 + +2014-04-01 13:04:21 +0200 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + client: Add drop-backlog property + When we have too many messages queued for a client (currently hardcoded + to 100) we overflow and drop the messages. Add a drop-backlog property + to control this behaviour. Setting this property to FALSE will retry + to send the messages to the client by waiting for more room in the + backlog. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725898 + +2014-04-03 12:19:51 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: support for POST before GET when setting up a tunnel + +2014-04-02 12:03:32 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: remove watch of the second client after http tunnel setup + The second client will be freed after the HTTP tunnel has been set up. + Make sure it's RTSP watch is never dispatched again. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=727488 + +2014-03-31 11:00:11 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + media: Make media_prepare() fail if port allocation fails + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=727376 + +2014-04-01 16:55:13 +0200 Linus Svensson <linussn@axis.com> + + * tests/check/gst/media.c: + media test: cleanup the thread pool in tests + +2014-04-01 13:16:26 +0200 Linus Svensson <linussn@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + rtsp-media: Unblock blocked streams in unprepare + The streams will be blocked when a live media is prepared. + The streams should be unblocked in gst_rtsp_media_unprepare. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=727231 + +2014-04-08 14:49:41 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: release the state lock when going to NULL + Set our state to UNPREPARING and release the state-lock before + setting the pipeline to the NULL state. This way, any pad-added + callback will be able to take the state-lock and check that we are now + unpreparing instead of deadlocking. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=727102 + +2014-04-08 12:08:17 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: protect status with lock + Make sure we only update the status with the lock. + +2014-04-04 17:39:36 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.c: + rtsp: update for MIKEY API changes + +2014-04-03 12:52:51 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: parse the mikey response from the client + Parse the mikey response from the client and update the policy for + each SSRC. + +2014-04-02 12:36:16 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add method to set crypto info + Make a method to configure the crypto information of a stream. + Set udpsrc in READY instead of PAUSED so that we can configure caps + later. + +2014-04-03 12:57:13 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: cleanup error paths + +2014-04-02 12:27:24 +0200 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: fix docs + +2014-03-25 12:42:39 +0100 Wim Taymans <wtaymans@redhat.com> + + * examples/test-video.c: + test: enable SRTP only on RTSPS + We only want to enable SRTP when doing rtsp over TLS so that we can + exchange the keys in a secure way. + +2014-03-25 12:41:33 +0100 Wim Taymans <wtaymans@redhat.com> + + * examples/test-video.c: + test: print an error on failure + +2014-03-13 17:35:21 +0100 Wim Taymans <wtaymans@redhat.com> + + * configure.ac: + * examples/test-video.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/Makefile.am: + stream: add SRTP support + Install srtp encoder and decoder elements in rtpbin + Add MIKEY in SDP + +2014-03-16 19:45:26 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/Makefile.am: + * tests/check/gst/sessionpool.c: + tests: Add unit tests for sessionpool + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=726470 + +2014-03-22 13:24:27 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/gst/threadpool.c: + tests: Improve code coverage of rtsp-threadpool tests + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=726873 + +2014-03-23 21:26:00 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/gst/sessionmedia.c: + tests: Improve code coverage for rtsp-session-media + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=726940 + +2014-03-23 21:24:48 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + gobject-introspection: Add annotations to support language bindings + In addition a few cosmetic changes: + * Adjust the order of arguments + * Fix typo: occured -> occurred + * Fix indentation after Return:-clauses + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=726941 + +2014-03-14 19:03:24 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Don't mix IPv4 and IPv6 addresses + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=726362 + +2014-03-13 14:27:15 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: take caps after the session manager + Take the caps for the SDP after they leave the rtpbin so that we can + also get the properties added by rtpbin elements. + +2014-03-13 14:20:17 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: release lock while pushing out packets + Keep a cache of the transports and use this to iterate the transport + while pushing packets. This allows us to release the lock early. + See https://bugzilla.gnome.org/show_bug.cgi?id=725898 + +2014-03-06 13:52:02 +0100 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: vmethod for modifying tunnel GET response + Add a vmethod tunnel_http_response where the response to the HTTP GET + for tunneled connections can be modified. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725879 + +2014-03-03 16:56:53 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: make 1 media line per profile + If we have multiple profiles (AVP or AVPF) for a stream, make one m= + line in the SDP for each profile. The client is then supposed to pick + one of the profiles in the SETUP request. Because the m= lines have the + same pt, the client also knows that only 1 option is possible. + +2014-03-03 16:55:48 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + factory: add profile property and pass to media and streams + +2014-03-03 15:12:55 +0100 Wim Taymans <wtaymans@redhat.com> + + * examples/test-multicast.c: + * gst/rtsp-server/rtsp-sdp.c: + sdp: pass multicast connection for multicast-only stream + Pass the multicast address of the stream in the connection info in the + SDP so that clients try a multicast connection first. + Only allow multicast connections in the test-multicast example. Also + increase the TTL a little. + +2014-03-02 05:12:01 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * .gitignore: + .gitignore: Ignore gcov intermediate files + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725484 + +2014-03-03 12:17:48 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: release some locks in error cases + +2014-03-02 05:12:10 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + docs: Enable and fix gtk-doc warnings + * Makefile: Enable gtk-doc warnings, like the rest of GStreamer + * addresspool/mediafactory: Add missing annotation colon + * stream: Annotate return value + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=725528 + +2014-02-28 09:36:49 +0100 Sebastian Dröge <sebastian@centricular.com> + + * common: + Automatic update of common submodule + From fe1672e to bcb1518 + +2014-02-26 22:15:51 +0100 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 1a07da9 to fe1672e + +2014-02-25 15:13:40 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/Makefile.am: + examples: use LDADD for libs instead of LDFLAGS + +2014-02-25 14:42:09 +0000 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + configure: make sure releases are in .doap file + +2014-02-25 14:11:00 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-cgroups.c: + examples: test-cgroups: don't put code with side effects into g_assert() + The g_assert() might get compiled out with the right + compiler/preprocessor flags. + +2014-02-25 14:07:50 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/.gitignore: + examples: add cgroup test binary to .gitignore + +2014-02-25 14:06:47 +0000 Tim-Philipp Müller <tim@centricular.com> + + * examples/test-cgroups.c: + examples: fix cgroup test build + Fixes build failure caused by compiler warning: + test-cgroups.c:82:35: error: no previous prototype for ‘gst_rtsp_cgroup_pool_get_type’ [-Werror=missing-prototypes] + +2014-02-21 16:46:45 +0000 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + .gitignore: ignore temp files created in the course of 'make check' + +2014-02-18 09:44:34 +0100 Branko Subasic <branko@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: don't loose frames handling new PLAY request + If client supplied a range check if the range specifies the start point. + If not, then do an accurate seek to the current position. If a start + point was specified do do a key unit seek to make sure the streaming + starts with decodeable frames. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=724611 + +2014-02-18 16:58:45 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + Revert "media: only flush when setting a new start position" + This reverts commit f67fc23aab59f28796bebf130504ff46ccb97b0a. + We need to do the flush in all cases, demuxer block currently for + non-flushing seeks. + +2014-02-18 16:38:39 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: only flush when setting a new start position + Only flush the pipeline when we change the start position with + a seek. + See https://bugzilla.gnome.org/show_bug.cgi?id=724611 + +2014-02-17 10:43:05 +0100 Göran Jönsson <goranjn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: set ttl-mc before adding the socket + Set ttl-mc before adding the socket. Otherwise the value ttl-mc will + never be set on socket. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=724531 + +2014-02-11 14:20:39 -0800 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-media.c: + media: stop thread if media is already prepared + in gst_rtsp_media_prepare() the thread is not used if media is already + prepared (e.g. media shared) so we want to stop the thread. otherwise, a + leak occurs. + https://bugzilla.gnome.org/show_bug.cgi?id=724182 + +2014-02-09 10:52:29 +0100 Sebastian Dröge <sebastian@centricular.com> + + * Makefile.am: + build: Ship gst-rtsp-server.doap file + +2014-02-09 10:47:09 +0100 Sebastian Dröge <sebastian@centricular.com> + + * tests/check/gst/rtspserver.c: + tests: Fix another compiler warning with gcc + +2014-02-09 10:45:28 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + rtsp-server: Fix lots of compiler warnings with clang + +2014-02-09 10:41:14 +0100 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + * gst-rtsp-server.doap: + * tests/Makefile.am: + configure: Synchronise with the configure scripts of the other modules + +2014-02-09 10:25:44 +0100 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + configure: Update version to 1.3.0.1 and require GStreamer 1.3.0 + +2014-02-09 10:19:50 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + Revert "rtsp-server: support build against last stable release" + This reverts commit 099a10f61f11413ad0ada8ee0b7b7ad1210b1b2f. + Let us require 1.2.3 now, which is going to be released in a few + minutes. + +2014-02-07 16:39:49 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + session: improve RTP-Info + Ignore streams that can't generate RTP-Info instead of failing. + Don't return the empty string when all streams are unconfigured but + return NULL so that we don't generate and empty RTP-Info header. + Improve docs a little. + +2014-02-03 22:41:48 +0200 Andrey Utkin <andrey.krieger.utkin@gmail.com> + + * gst/rtsp-server/rtsp-session-media.c: + Don't free rtpinfo GString when it is NULL + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723554 + +2014-02-06 09:48:05 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: only set keyframe flag when modifying start + Only set the keyframe flag when we modify the start position. The + keyframe flag should probably be ignored when no change is requested but + until we can claim this is all documented properly and all demuxer + implement this, avoid setting the flag. + See also https://bugzilla.gnome.org/show_bug.cgi?id=723075 + +2014-02-06 09:03:50 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + thread-pool: Unref source after mainloop has quit to avoid races in GLib + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723741 + +2014-02-04 16:27:12 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: handle NULL seqnum and rtptime arguments + +2014-01-31 15:02:22 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + * tests/check/gst/threadpool.c: + thread-pool: Unref reused threads in gst_rtsp_thread_stop() + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723519 + +2014-02-04 10:14:45 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: add fallback for missing stats property + Use a fallback when the payloader does not have a stats property + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723554 + +2014-01-30 10:45:56 +0100 Edward Hervey <bilboed@bilboed.com> + + * common: + Automatic update of common submodule + From f7bc1c3 to 1a07da9 + +2014-01-28 14:51:26 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: don't leak stats structure + Don't leak the stats structure and deal with NULL stats. + +2014-01-22 22:03:14 +0100 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Get rtpinfo properties atomically from payloader + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=722844 + +2014-01-21 14:46:47 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: refactor state change functions and signals + Make functions to set the target state and the pipeline state and emit + the signals from those functions. + +2014-01-21 12:01:25 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add signal to notify of pending state changes + +2014-01-12 16:55:21 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: support build against last stable release + Until 1.2.3 is out with the new get_type function and we + can require that. + +2014-01-07 15:28:05 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: fix compilation + +2014-01-07 12:21:09 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add property to configure profiles + +2014-01-07 12:28:47 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: let stream check supported transport + Delegate the check if a transport is allowed to the stream. + See https://bugzilla.gnome.org/show_bug.cgi?id=720696 + +2014-01-07 12:14:15 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add method to check supported transport + Add a method to check if a transport is supported + +2013-12-27 13:11:45 +0100 Sebastian Dröge <sebastian@centricular.com> + + * configure.ac: + configure.ac: Only check for gstreamer-check, not check + We include check in gstreamer-check since quite some time now. + +2013-12-26 17:02:50 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: return clock-rate from get_rtpinfo + And use it to correct the rtptime to the requested start-time. + See https://bugzilla.gnome.org/show_bug.cgi?id=712198 + +2013-12-26 16:28:59 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + session-media: calculate start-time + +2013-12-26 14:43:35 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: also return the running-time + Return the running-time in the rtpinfo as well. + +2013-12-26 15:41:14 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + session-media: let the session-media make the RTPInfo + Add method to create the RTPInfo for a stream-transport. + Add method to create the RTPInfo for all stream-transports in a + session-media. + Use the session-media RTPInfo code in client. This allows us to refactor + another method to link the TCP callbacks. + +2013-12-20 16:39:07 -0800 Aleix Conchillo Flaqué <aleix@oblong.com> + + mount-points: sort sequence before g_sequence_lookup + * gst/rtsp-server/rtsp-mount-points.c (gst_rtsp_mount_points_remove_factory): + sort sequence if dirty, otherwise lookup will fail. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=720855 + +2013-12-22 23:16:56 +0000 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + configure: rename package from gst-rtsp to gst-rtsp-server + To match git module name and avoid confusion with the + rtsp lib in gst-plugins-base and rtsp plugin in -good. + +2013-12-22 23:15:02 +0000 Tim-Philipp Müller <tim@centricular.com> + + * configure.ac: + configure: bump core/base/good requirement to 1.2.0 + Bump to released stable version and make implicit + requirements explicit. + +2013-12-22 23:04:48 +0000 Tim-Philipp Müller <tim@centricular.com> + + * autogen.sh: + * common: + * configure.ac: + Fix broken gettext setup which is not used anyway + +2013-12-22 22:36:06 +0000 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From dbedaa0 to d48bed3 + +2013-12-18 16:37:27 +0100 Aleix Conchillo Flaqué <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add setup_sdp vmethod + gst/rtsp-server/rtsp-media.[ch]: added setup_sdp vmethod and public + gst_rtsp_media_setup_sdp. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=720155 + +2013-12-19 14:26:34 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: Check return value of sscanf + streamid is only valid if sscanf matched something. + +2013-12-19 14:24:54 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Fix iteration + Wouldn't even enter the code block otherwise (i++ was used as the check + and not the postfix). + +2013-12-18 15:57:03 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add vmethod to configure media and streams + Implement a vmethod that can be used to configure the media and the + streams based on the current context. Handle the blocksize handling in + the default handler. + See https://bugzilla.gnome.org/show_bug.cgi?id=720667 + +2013-12-12 00:38:07 +0000 Tim-Philipp Müller <tim@centricular.com> + + * .gitignore: + Make git ignore more unit test binaries + +2013-12-12 00:36:07 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.h: + * gst/rtsp-server/rtsp-thread-pool.h: + * gst/rtsp-server/rtsp-token.h: + rtsp-server: add padding to many public structures + Not mini objects though, since they are not subclassable + anyway, nor kept on the stack or inlined in a structure. + +2013-12-03 11:54:42 -0800 Aleix Conchillo Flaqué <aleix@oblong.com> + + media: add new create_rtpbin vmethod + * gst/rtsp-server/rtsp-media.[ch]: add new create_rtpbin vmethod. + https://bugzilla.gnome.org/show_bug.cgi?id=719734 + +2013-12-03 00:34:52 +0100 Sebastian Rasmussen <sebras@gmail.com> + + * tests/check/gst/media.c: + tests: fix memory leak, free test's thread pool + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=719733 + +2013-11-29 15:50:52 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + stream-transport: free url in finalize + +2013-11-29 15:50:23 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: also do state change in suspended state + +2013-11-29 10:53:08 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + media: also handle prepare and range in suspended state + When we are suspended, we are already prepared. + We can get the range in the suspended state. + +2013-11-27 15:04:04 +0100 Branko Subasic <branko@axis.com> + + * tests/check/Makefile.am: + * tests/check/gst/sessionmedia.c: + check: add test for uri in setup + Added unit tests for the new functionality in GstRTSPStreamTransport. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=715168 + +2013-11-28 17:47:18 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: store setup uri and use in PLAY response + Store the uri used when doing the setup and use that in the PLAY + response. + fixes https://bugzilla.gnome.org/show_bug.cgi?id=715168 + +2013-11-28 17:35:45 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + stream-transport: add method to get/set url + +2013-11-28 14:14:35 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-client.c: + client: suspend after SDP and unsuspend before PLAYING + Based on patches by Ognyan Tonchev <ognyan@axis.com> + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=711257 + +2013-11-28 14:10:19 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session.c: + * tests/check/gst/media.c: + * tests/check/gst/mediafactory.c: + media: add suspend modes + Add support for different suspend modes. The stream is suspended right after + producing the SDP and after PAUSE. Different suspend modes are available that + affect the state of the pipeline. NONE leaves the pipeline state unchanged and + is the current and old behaviour, PAUSE will set the pipeline to the PAUSED + state and RESET will bring the pipeline to the NULL state. + A stream is also unsuspended when it goes back to PLAYING, for RESET streams, + this means that the pipeline needs to be prerolled again. + Base on patches by Ognyan Tonchev <ognyan@axis.com> + See https://bugzilla.gnome.org/show_bug.cgi?id=711257 + +2013-11-28 14:06:53 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: start live streams in blocked state + Start live streams in the blocked state and make them preroll using the + messages. This ensure that no data is played by the sink until we explicitly + unblock the stream right before going to PLAYING. + See https://bugzilla.gnome.org/show_bug.cgi?id=711257 + +2013-11-28 13:58:05 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: refactor starting and waiting for preroll + Based on patches from Ognyan Tonchev <ognyan@axis.com> + See https://bugzilla.gnome.org/show_bug.cgi?id=711257 + +2013-11-28 13:42:21 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add API to block streams + Add an API to block on the streams and make it post a message. + Based on patch by Ognyan Tonchev <ognyan@axis.com> + See https://bugzilla.gnome.org/show_bug.cgi?id=711257 + +2013-11-27 15:42:45 +0100 Edward Hervey <edward@collabora.com> + + * docs/libs/Makefile.am: + docs: Specify the override file + Even if it's empty (for now) it avoids make distcheck complaining + +2013-11-26 17:23:04 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: move default implementations to where they are used + +2013-11-26 16:25:37 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: take the right lock in gst_rtsp_media_set_pipeline_state() + We need to take the state_lock when calling this method. + +2013-11-26 16:24:35 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + media: handle add-added on non-bins too + Handle dynamic payloaders that are not bins, as used in the unit-test. + +2013-11-22 01:30:53 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + rtsp-media/-factory: Fix request pad name comments + These must be escaped for gtk-doc to parse the comments without warnings. + +2013-11-20 15:51:54 -0800 Aleix Conchillo Flaque <aleix@oblong.com> + + rtsp-media: remove transports if media is in error status + * gst/rtsp-server/rtsp-media.c (gst_rtsp_media_set_state): if we are + trying to change to GST_STATE_NULL and media is in error status, we + remove all transports. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=712776 + +2013-11-22 11:16:20 +0100 Wim Taymans <wtaymans@redhat.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: use element metadata to find payloader + Use the element metadata to find the payloader instead of checking + for the base class. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=712396 + +2013-11-15 12:14:32 -0800 Aleix Conchillo Flaque <aleix@oblong.com> + + rtsp-stream: add getter for payload type + * gst/rtsp-server/rtsp-stream.c: add new method gst_rtsp_stream_get_pt. + * gst/rtsp-server/rtsp-media.c (pad_added_cb): find real payloader + element and create the stream with this one instead of the dynpay%d + element. + https://bugzilla.gnome.org/show_bug.cgi?id=712396 + +2013-11-22 02:28:28 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-token.c: + rtsp-*: Refer to NULL as a constant in comments + Plus one typo fix. + https://bugzilla.gnome.org/show_bug.cgi?id=714988 + +2013-11-22 03:10:01 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + rtsp-*: Fix type name typos in comments + * rtsp-auth: Refer to GstRTSPToken, not GstRTSPtoken + * rtsp-auth: Refer to part of constant name as text + * rtsp-auth/-permissions/-token: Refer to Permissions not Permission + * rtsp-session-media: Fix GstRTSPSessionMedia typo + * rtsp-stream: Fix typo when refering to GstBin + https://bugzilla.gnome.org/show_bug.cgi?id=714988 + +2013-11-22 00:45:17 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * docs/README: + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + docs: Improve documentation + * Include annotation-glossary to quiet gtk-doc + * Rename remaining ClientState -> Context + * Rename object hierarchy file + * Remove stale chapter references + * Add missing function and object references + * Include missing GstRTSPAddressPoolResult + https://bugzilla.gnome.org/show_bug.cgi?id=714988 + +2013-11-18 10:47:04 +0000 Tim-Philipp Müller <tim@centricular.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp-server: sprinkle some allow-none annotations for g-i + +2013-11-18 11:18:15 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add method to filter transports + Add a method to safely iterate and collect the stream transports + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=711664 + +2013-11-15 16:35:05 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + rtsp: allow NULL func in filters + Passing a null function make the filters return a list of + refcounted objects. + +2013-11-12 16:52:35 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * tests/check/gst/addresspool.c: + address-pool: fix address increment + Use a guint instead of guint8 to increment the address. It's still not + completely correct because a guint might not be able to hold the complete + address range, but that's an enhacement for later. + Add unit test to test improved behaviour. + https://bugzilla.gnome.org/show_bug.cgi?id=708237 + +2013-11-12 10:55:14 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + client: allow absolute path in requests + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=711689 + +2013-11-07 13:22:09 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: make make_path_from_uri a vmethod + +2013-11-12 12:04:55 +0100 Wim Taymans <wim.taymans@gmail.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/Makefile.am: + * tests/check/gst/stream.c: + stream: Add functions to get rtp and rtcp sockets + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=710100 + +2013-11-12 11:21:55 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-context.c: + * gst/rtsp-server/rtsp-context.h: + context: defing a GType for the context + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=710018 + +2013-10-12 23:56:00 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-context.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream.c: + Fixed several GIR warnings + +2013-11-12 11:15:46 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-auth.c: + auth: small typos + +2013-10-19 19:25:27 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/Makefile.am: + * tests/check/gst/token.c: + tests: Add unit tests for token + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=710520 + +2013-10-19 19:24:34 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-token.c: + token: Validate args for gst_rtsp_token_is_allowed + See https://bugzilla.gnome.org/show_bug.cgi?id=710520 + +2013-10-19 19:21:53 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-token.c: + token: Fix bug when creating empty token + We always want to have a valid GstStructure in the token. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=710520 + +2013-11-12 10:28:55 +0100 Wim Taymans <wim.taymans@gmail.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + thread-pool: avoid race in shutdown + If we call g_main_loop_quit before the thread has entered g_main_loop_run, we + don't actually stop the mainloop ever. Solve this race by adding an idle source + to the mainloop that calls the _quit. This way we immediately exit the mainloop + if quit was called before we started it. + +2013-10-19 17:36:05 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/Makefile.am: + * tests/check/gst/permissions.c: + tests: Add unit tests for permissions + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=710202 + +2013-10-15 18:50:47 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/gst/mediafactory.c: + tests: Test mediafactory permissions + See https://bugzilla.gnome.org/show_bug.cgi?id=710202 + +2013-10-19 17:39:35 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: Fix refcounting when adding/removing roles + Previously a role that was removed was unreffed twice, and when + replacing an existing role the replaced role was freed while still being + referenced. Both bugs are now fixed. + See https://bugzilla.gnome.org/show_bug.cgi?id=710202 + +2013-10-15 18:01:38 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/gst/media.c: + * tests/check/gst/mediafactory.c: + * tests/check/gst/rtspserver.c: + tests: Check gst_rtsp_url_parse return value + See https://bugzilla.gnome.org/show_bug.cgi?id=710202 + +2013-11-05 11:22:51 +0000 Tim-Philipp Müller <tim@centricular.com> + + * common: + Automatic update of common submodule + From 865aa20 to dbedaa0 + +2013-10-14 12:03:07 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: Fix socket leak + https://bugzilla.gnome.org/show_bug.cgi?id=710088 + +2013-10-30 22:16:54 +0100 Sebastian Dröge <sebastian@centricular.com> + + * gst/rtsp-server/rtsp-session-pool.c: + rtsp-session-pool: Make sure session IDs are properly URI-escaped + https://bugzilla.gnome.org/show_bug.cgi?id=643812 + +2013-10-15 16:37:34 -0700 Aleix Conchillo Flaque <aleix@oblong.com> + + * examples/.gitignore: + * examples/test-video.c: + examples: fix compilation when WITH_AUTH is defined + https://bugzilla.gnome.org/show_bug.cgi?id=710228 + +2013-10-30 19:10:59 +0100 Sebastian Dröge <sebastian@centricular.com> + + * .gitignore: + gitignore: Add new test binary + +2013-10-09 15:19:12 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/Makefile.am: + * tests/check/gst/threadpool.c: + thread-pool: Add unit test for the thread pools + https://bugzilla.gnome.org/show_bug.cgi?id=710228 + +2013-10-09 15:25:10 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + thread-pool: Fix thread leak when reusing threads + https://bugzilla.gnome.org/show_bug.cgi?id=709730 + +2013-10-14 08:30:33 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-server.c: + * tests/check/gst/rtspserver.c: + tests: fixed racy behavior in rtspserver tests + https://bugzilla.gnome.org/show_bug.cgi?id=710078 + +2013-10-14 19:36:24 +0200 Sebastian Rasmussen <sebras@hotmail.com> + + * tests/check/gst/addresspool.c: + tests: Improve address pool unit tests + Add a range with mixed IPV4 and IPV6 addresses to pool. + Get an IPV4 address from an IPV6-only pool. + Get an IPV6 address from an IPV4-only pool. + Reserve a IPV6 address from an IPV4-only pool. + Check for unicast addresses in multicast-only pool. + Check for unicast addresses in uni-/multicast-mixed pool. + https://bugzilla.gnome.org/show_bug.cgi?id=710128 + +2013-10-04 06:29:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: append query string in PAUSE/PLAY/TEARDOWN as well + +2013-10-01 14:04:17 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Add query to control path + If the SETUP url contains a query it must be appended to the control + path so that it matches any already created stream in the media. The + query will also be appended to the session media path. + +2013-10-04 05:48:52 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: remove old line + +2013-10-01 13:15:19 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Correct control comparison + https://bugzilla.gnome.org/show_bug.cgi?id=709176 + +2013-09-09 21:51:44 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: Check dynamically if the pipeline supports seeking + We should not depend on whether or not the pipeline state change + returned NO_PREROLL or not. A media could dynamically change its + element and switch from seekable to non seekable so it's best to test + the seekable nature of the pipeline dynamically when we try to do a seek. + +2013-09-09 21:51:23 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: Return FALSE if seeking is not supported + +2013-10-01 17:16:11 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: don't seek accurate by default + Accurate seeking is perhaps a little overkill in the most common situation and + causes some formats (mp3) over slow media to seek extremely slowly. + +2013-09-26 14:36:58 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/gst/rtspserver.c: + tests: fix unit test + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=708742 + +2013-09-26 11:20:05 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Reply 400 if media cannot be constructed + Reply 400 Bad Request instead of 503 Service Unavailable if media + cannot be constructed in SETUP. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=708821 + +2013-09-26 09:41:10 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Send setup reply once only + If find_media() failed in handle_setup_request() two replies was sent. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=708819 + +2013-09-24 18:35:36 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 6b03ba7 to 865aa20 + +2013-09-23 14:28:04 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-server.c: + server: Emit client-connected signal earlier + Emit client-connected before the client ref is given to a GSource, + otherwise client-connected can be emitted after the client object has + been freed. + +2013-09-24 17:30:18 +0200 Patrick Radizi <patrick.radizi at axis.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/addresspool.c: + addresspool: return reason of failure + Let gst_rtsp_address_pool_reserve_address() return the reason why + the address could not be reserved. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=708229 + +2013-09-20 16:47:56 +0200 Edward Hervey <edward@collabora.com> + + * autogen.sh: + autogen.sh: Sync behaviour with other GStreamer modules + Allows building from outside of tree amongst other things + +2013-09-20 16:18:54 +0200 Edward Hervey <edward@collabora.com> + + * common: + Automatic update of common submodule + From b613661 to 6b03ba7 + +2013-09-19 18:46:14 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 74a6857 to b613661 + +2013-09-19 17:39:24 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 01a7a46 to 74a6857 + +2013-09-19 15:44:26 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: Do not read beyond end of path string + If the setup was done without a control url, make sure we don't try to read the + non-existing control string and crash. + +2013-09-17 14:39:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: Fix RTPInfo header + Refactor the method to make the content_base. + Use the content-base and the control url to construct the RTPInfo + url. + +2013-09-17 12:21:02 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: map url to path only in describe + Only map the request url to a path in the DESCRIBE method. The SDP then + contains the base and control urls that should be used to SETUP/PAUSE/ + PLAY/TEARDOWN the media. + +2013-09-17 11:41:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Revert "client: map URL to path in requests" + This reverts commit e3fded2cec897a2ec003450607b916cc1601fd2d. + This is not correct, we only remap the URL to a path in DESCRIBE, the SDP then + contains the base and control urls which are used in the SETUP, PLAY, + PAUSE and TEARDOWN requests. + +2013-09-16 17:16:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: map URL to path in requests + +2013-09-16 16:47:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-mount-points.h: + mount-points: make vmethod to make path from uri + Make a vmethod to transform an url into a path. The path is then used to lookup + the factory. This makes it possible to also use other bits of the url, such as + the query parameters, to locate the factory. + +2013-09-09 11:05:26 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + thread-pool: Add cleanup to wait for the threadpool to finish + Also fix race condition if two threads are asking for the first + thread from the thread pool at once. This would case two internal + GThreadPools to be created. + https://bugzilla.gnome.org/show_bug.cgi?id=707753 + +2013-09-05 08:56:02 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * tests/check/gst/client.c: + client: free threadpool + https://bugzilla.gnome.org/show_bug.cgi?id=707638 + +2013-09-06 17:23:20 +0200 Jonas Holmberg <jonashg@axis.com> + + * tests/check/gst/mountpoints.c: + mountpoints tests: unref matched factories + https://bugzilla.gnome.org/show_bug.cgi?id=707638 + +2013-09-05 18:01:18 +0200 Jonas Holmberg <jonashg@axis.com> + + * tests/check/gst/media.c: + media tests: unref thread pool and caps + https://bugzilla.gnome.org/show_bug.cgi?id=707638 + +2013-09-05 08:53:55 +0200 Jonas Holmberg <jonashg@axis.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + auth, media, media-factory: unref permissions + https://bugzilla.gnome.org/show_bug.cgi?id=707638 + +2013-08-23 15:15:12 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + Makefile: add rule for appsrc example + +2013-08-23 15:14:29 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-appsrc.c: + tests: add appsrc example + Add an example on how to use appsrc to feed the server pipeline with data. + +2013-08-22 12:10:39 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: remove query part from content-base string + Make sure that after the control url has been resolved, it's + not a part of the query-string. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=706568 + +2013-08-23 10:38:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: don't check url in response + There is no url or method in the response to check + +2013-08-08 10:57:42 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Add handle-response signal for when we receive a GET_PARAMETER response + +2013-08-16 12:42:22 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + Fix gst_rtsp_server_client_filter, using wrong variable type + +2013-08-22 18:39:59 +0100 Tim-Philipp Müller <tim@centricular.net> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-media-factory-uri: check AAC properly for whether it's parsed or not + For AAC we need to check for framed=true instead of parsed=true. + https://bugzilla.gnome.org/show_bug.cgi?id=701384 + +2013-08-16 17:05:24 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: optimize pipeline for protocols + When TCP is not an allowed protocol for the stream, avoid creating the + appsrc/appsink/queue and tee elements. + +2013-08-16 16:34:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: set protocols on streams + +2013-08-16 16:16:31 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use protocols supported by stream + +2013-08-16 16:16:00 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + media-factory: allow all protocols + +2013-08-16 16:10:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: configure protocols in new streams + +2013-08-16 16:08:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add protocols property + +2013-08-05 10:46:33 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: send state in "new-state" signal + https://bugzilla.gnome.org/show_bug.cgi?id=705110 + +2013-08-02 14:11:01 +0200 Lubosz Sarnecki <lubosz@gmail.com> + + * configure.ac: + build: add subdir-objects to AM_INIT_AUTOMAKE + Fixes warnings with automake 1.14 + https://bugzilla.gnome.org/show_bug.cgi?id=705350 + +2013-08-02 17:15:09 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: add method to iterate clients of server + +2013-06-11 19:10:01 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add vmethod for rtsp-media subclass to access rtpbin + +2013-07-11 16:12:04 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.h: + small documentation fix + +2013-07-11 16:11:55 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Do not take range header if range is invalid + +2013-08-02 16:57:26 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media.c: + media: add docs for new method + +2013-07-02 18:55:28 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add API to rtsp-media set the pipeline's state + +2013-06-11 19:09:42 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + Update current position/duration when gst_rtsp_media_get_range_string is called + +2013-07-22 17:27:27 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-cgroups.c: + tests: add some more docs + +2013-07-22 14:25:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-cgroups.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-context.c: + * gst/rtsp-server/rtsp-context.h: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-params.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + * tests/check/gst/client.c: + ClientState -> Context + Rename the clientstate to context and put the code in a separate file. + +2013-07-18 12:19:25 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + auth: add support for default token + The default token is used when the user is not authenticated and can be used to + give minimal permissions. + +2013-07-18 11:44:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + auth: use defines when possible + +2013-07-18 11:44:21 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-address-pool.c: + address-pool: improve docs + +2013-07-18 12:26:45 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: add the role to the copy + +2013-07-17 19:35:33 -0400 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: Also copy the roles + +2013-07-17 19:32:09 -0400 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: Make it build + +2013-07-16 12:36:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-address-pool.h: + docs: small fixes + +2013-07-16 12:32:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/client.c: + docs: improve docs + +2013-07-16 12:32:00 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * tests/check/gst/addresspool.c: + * tests/check/gst/rtspserver.c: + address-pool: cleanups + Remove redundant method, improve docs. + +2013-07-15 17:31:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-token.c: + docs: improve docs + +2013-07-15 17:12:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: implement _remove_role + +2013-07-15 17:12:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-permissions.c: + permissions: update docs + +2013-07-15 16:48:37 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + tests: simplify tests + Client settings are now disabled by default so we don't need an auth + module to disable them. + +2013-07-15 16:47:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: add default authorizations + When no auth module is specified, use our table of defaults to look up the + default value of the check instead of always allowing everything. This was + we can disallow client settings by default. + +2013-07-15 16:05:02 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + README: update readme + +2013-07-15 15:25:00 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + thread-pool: add more docs + +2013-07-15 14:50:38 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + thread-pool: fix race in thread reuse + If we try to reuse a thread right after we made it stop, we end up using a + stopped thread. Catch this case and only reuse threads that are not stopping. + +2013-07-15 14:50:26 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: add small debug + +2013-07-15 11:58:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + client: fix test + Add some permissions to media so we can use the auth and enable + client settings. + +2013-07-15 11:57:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: support pushed context in handle_request + If we already have a pushed state, reuse it and add our own things. This makes + it easier to write tests. + +2013-07-15 11:56:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: don't auth on methods + Don't authorize on methods anymore but on the resources that we + try to access, this is more flexible. + Move the authorization checks to where they are needed and let the + check return the response on error. + +2013-07-15 11:51:34 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-mount-points.c: + mount-points: add some debug + +2013-07-12 17:26:55 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + tests: almost fix test + +2013-07-12 17:07:53 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + auth: let the auth module check client_settings + Let the auth module decide if client settings are allowed for the + current client. + +2013-07-12 17:06:37 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-token.c: + * gst/rtsp-server/rtsp-token.h: + token: add method to check boolean permission + +2013-07-12 16:36:05 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * examples/test-cgroups.c: + * gst/rtsp-server/rtsp-token.c: + * gst/rtsp-server/rtsp-token.h: + token: simplify token constructor + Use variable arguments to make easier API. + +2013-07-12 16:17:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * examples/test-cgroups.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add convenience API for factory + +2013-07-12 16:03:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * examples/test-cgroups.c: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + permissions: simplify API a little + Avoid passing GstStructure in the add_role method, use varargs instead + to construct the structure behind the scenes. We can then also use the + structure name as the role and simplify some more logic. + +2013-07-12 16:01:14 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: fix typo + +2013-07-12 15:19:29 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + auth: handle unauthorized response + Move handling of the unauthorized response to the auth module, it can add + the appropriate headers to request authorization for the required method + much better than the client. + +2013-07-12 15:13:48 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: allow for sending any message, not only requests + Change the _send_request() method to _send_message() so that we + can both send requests and replies. + +2013-07-12 14:10:13 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-server.h: + docs: fix docs + +2013-07-12 12:41:52 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + auth: move TLS handling to auth module + Remove the TLS settings on the server and move it to the auth module because + that is where security related bits go. + +2013-07-12 12:38:54 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add state push/pop + +2013-07-12 12:36:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add connection to state + +2013-07-11 20:45:11 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-mount-points.c: + mount-points: fix debug + +2013-07-11 17:28:17 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/media.c: + tests: fix media test + +2013-07-11 17:28:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-thread-pool.c: + thread-pool: we don't require a state + +2013-07-11 17:18:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: let context ref the server + So that we don't risk losing the server object early anc crash. + +2013-07-11 17:05:00 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + tests: fix client test + +2013-07-11 16:57:14 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-token.c: + docs: improve docs + +2013-07-11 16:28:09 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + session-pool: make vmethod to create a session + Make a vmethod to create a sessions so that subclasses can create + custom session objects + +2013-07-11 12:24:33 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-stream.h: + docs: more updates + +2013-07-11 12:18:26 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-thread-pool.h: + docs: update docs + +2013-07-11 10:28:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + * examples/Makefile.am: + configure: compile cgroup example conditionally + Only compile the cgroup example when we have libcgroup + +2013-07-10 20:57:12 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + * examples/Makefile.am: + * examples/test-cgroups.c: + examples: add cgroups example + +2013-07-10 20:55:03 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/rtspserver.c: + tests: fix compilation + +2013-07-10 20:48:47 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-thread-pool.c: + thread-pool: fix vmethod invocation + +2013-07-10 20:48:18 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + thread-pool: store thread type in thread + +2013-07-10 17:09:27 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: pass thread from pool to media _prepare + Get a thread from the configured threadpool and pass it to the prepare method of + the media. + +2013-07-10 17:08:14 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: Accept a thread in _prepare + Remove out own threadpool handling and use the provided thread and + maincontext for the bus messages and the state changes. + +2013-07-10 17:07:13 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: configure client thread pool + +2013-07-10 17:06:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add method to configure thread pool + +2013-07-10 16:49:55 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: use thread pool + Use the thread pool instead of doing our own thing. + +2013-07-10 16:47:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-thread-pool.c: + * gst/rtsp-server/rtsp-thread-pool.h: + thread-pool: add object to manage threads + Add an object to manage the client and media threads. + +2013-07-10 15:28:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: debug authorization check + +2013-07-09 20:44:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: start media pipeline in context + Start the media pipeline in the provided context (or our default one + when NULL). This makes sure that we run the bus thread in this context and that + all media threads are children of this context. + +2013-07-09 16:38:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + factory: pass permissions to media by default + +2013-07-09 16:09:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + test: add permissions to auth test + Ass some permissions to the media factory in the test. + +2013-07-09 16:04:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + auth: simplify auth checks + Remove client from methods, it's now in the state + Perform the check specified by the string, use the information from the + thread local context. + +2013-07-09 16:01:29 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add state to current thread + Add the client to the ClientState object. + Place the ClientState on the current thread. + +2013-07-09 14:33:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: make it possible to set permissions + Make it possible to set permissions on media and media factory objects + +2013-07-09 14:31:15 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-permissions.c: + * gst/rtsp-server/rtsp-permissions.h: + permissions: add permissions object + Add a mini object to store permissions based on a role. + +2013-07-08 16:29:01 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + auth: add auth checks + Add an enum with auth checks and implement the checks in the auth object. + Perform the checks from the client. + +2013-07-05 20:48:18 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + auth: use the token after authentication + After we authenticated a user, keep the Token around in the state. + +2013-07-05 20:43:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * tests/check/gst/media.c: + media: add optional context for bus messages + Add an optional mainloop to _prepare that will handle the bus messages instead + of always using the shared mainloop. + +2013-07-05 20:34:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-token.c: + * gst/rtsp-server/rtsp-token.h: + token: add authorization token + Add a simply miniobject that contains the authorizations. The object contains a + GstStructure that hold all authorization fields. When a user is authenticated, + the auth module will create a Token for the user. The token is then used to + check what operations the user is allowed to do and various other configuration + values. + +2013-07-05 12:08:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + auth: remove auth from media and factory + Remove the auth object from media and factory. We want to have the RTSPClient + authenticate and authorize resources, there is no need to place another auth + manager on the media/factory. + +2013-07-04 14:33:59 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.h: + auth: add support for multiple basic auth tokens + Make it possible to add multiple basic authorisation tokens to one authorization + object. Associate with each token an authorization group that will define what + capabilities are allowed. + +2013-07-03 16:15:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: error out on non-aggregate control + We require aggregate control (for now) for PLAY, PAUSE and TEARDOWN. + +2013-07-03 15:55:38 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: rework setup request a little + Cache the media in DESCRIBE based on the longest matching path with the uri + that we can find in the mount points. + Rework the setup request a little to get the media from the session or from + the longest matching path, this way we can derive the control string as + everything after the path instead of hardcoding it. + Find the stream based on the control string and only open a session when all + this can be done. + +2013-07-03 15:14:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add method to find a stream by control url + +2013-07-03 15:13:45 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add method to check control url of stream + +2013-07-03 12:37:48 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + session: use path matching for session media + Use a path string instead of a uri to lookup session media in the sessions. Also + use path matching to find the largest possible path that matches. + +2013-07-03 11:04:53 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-mount-points.h: + * tests/check/gst/mountpoints.c: + mount-points: remove useless vmethod + Making lookups in the mount points should not be done with a URL, if there is a + mapping to be done from URL to mount points, we'll need to do it somewhere + else. + +2013-07-03 10:25:46 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-mount-points.h: + * tests/check/gst/mountpoints.c: + mount-points: improve mount point searching + Use a GSequence to keep track of the mount points. + Match a URL to the longest matching registered mount point. This should be the + URL to perform aggreagate control and the remainder is the stream specific + control part. + Add some unit tests for this. + +2013-07-03 10:40:33 +0200 Sebastian Dröge <slomo@circular-chaos.org> + + * gst/rtsp-server/Makefile.am: + rtsp-server: Allow building of static library + +2013-07-02 15:59:16 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/mediafactory.c: + tests: fix compilation + +2013-07-02 15:54:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: get control string from stream + Use the control string as configured in the stream. + +2013-07-02 14:44:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add methods and property to set control string + +2013-07-02 11:58:02 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: cleanups + Rename variables for clarity + Keep media in state when we can + +2013-07-01 16:46:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add more support for IPv6 + Rename _get_address to _get_multicast_address in GstRTSPStream to + make it clear that this function only deals with multicast. + Make it possible to have both an IPv4 and IPv6 multicast address on + a stream. Give the client an IPv4 or IPv6 address depending on the + address it used to connect to the server. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702002 + +2013-07-01 15:18:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix comment + +2013-07-01 14:45:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: handle failed port allocation + Allow for ipv4 or ipv6 socket allocations to fail. Only report failure if we + can't allocate any family at all. Also keep track of what port families we + allocated. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=703175 + +2013-07-01 12:20:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: improve docs + +2013-07-01 12:04:45 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream-transport.c: + stream-transport: remove old if 0 block + +2013-06-27 11:21:42 +0200 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/client.c: + tests: fix tests + gst_rtsp_client_get_uri() has been removed + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=703173 + +2013-06-26 17:18:33 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add method to filter managed sessions + Add a method to filter the sessions managed by this client connection. + See https://bugzilla.gnome.org/show_bug.cgi?id=703016 + +2013-06-26 16:32:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: remove _get_uri() method + Remove the get_uri() method on the client. A client has no uri, the uri + property is an internal property to manage the last cached media for + the client. + +2013-06-26 16:31:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: fix typo + +2013-06-26 14:42:15 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Do not leak the query in default_query_stop + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=703120 + +2013-06-25 15:46:41 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: don't unlock when conversion fails + Don't unlock the state lock when conversion fails because it was not locked. + +2013-06-10 17:32:40 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add query_position and query_stop vmethods to rtsp-media + +2013-06-10 17:33:01 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + Fix typo in property install for rtsp-media's time-provider + +2013-06-25 15:09:13 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: clean some variables + Clean some variables and add some guards to _send_request() + +2013-06-10 17:32:12 -0400 Youness Alaoui <youness.alaoui@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Add gst_rtsp_client_send_request API + This makes it possible to send arbitrary messages to a client, such as + SET_PARAMETER or GET_PARAMETER + +2013-06-24 23:56:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add _get_element() method + Add method to get the element used when creating the media. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=703008 + +2013-06-24 23:51:38 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: fix docs + +2013-06-24 11:41:27 -0700 Aleix Conchillo Flaque <aleix@oblong.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: allow access to the rtp session + https://bugzilla.gnome.org/show_bug.cgi?id=703004 + +2013-06-24 10:43:59 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + dscp qos support in gst-rtsp-stream + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702645 + +2013-06-20 17:30:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/rtspserver.c: + tests: fix test + Actually do what the comment says. Also keep the old code around, not sure what + should happen when you get a 454 from a TEARDOWN, does it close the connection? + it currently doesn't. + +2013-06-20 12:20:21 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: also watch newly created session + When we newly created a session, start watching it immediately instead of + on the next request. + +2013-06-20 12:18:23 +0200 Patricia Muscalu <patricia@axis.com> + + * tests/check/gst/client.c: + tests: add unit test for new-session + See https://bugzilla.gnome.org/show_bug.cgi?id=701587 + +2013-06-20 12:16:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: emit new-session when new session is created + Only emit new-session when we created a new session for a client, not when a + client picked up a previous session. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701587 + +2013-06-20 11:17:29 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: handle asterisk as path in requests + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701266 + +2013-06-20 11:14:31 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: handle segment query format mismatch + It's possible that the segment query returns with a different format than what + we asked for, handle this case also. + +2013-06-11 15:28:32 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: use segment stop in collect_media_stats + Use segment stop instead of duration as range end point. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701185 + +2013-06-17 16:47:56 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + rtsp-media: Do not leak the element in take_pipeline + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702470 + +2013-06-17 16:18:37 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: Make configure_client_transport virtual + This patch makes configure_client_transport virtual. The functionality is + needed to handle some weird clients sending multicast transport settings as url + options. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702173 + +2013-06-12 12:23:56 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: Make param_set and param_get virtual + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702072 + +2013-06-05 15:49:45 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: convert_range replaces get_range_times + get_range_times worked for handling UTC ranges for seeks, but we also + need to convert back from NPT to the requested unit in + get_range_string. convert_range is now used for both. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702084 + +2013-06-14 16:05:59 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + sdp: cleanup sdp info + We don't need to pass the proto, we can more easily check a boolean. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=702063 + +2013-06-12 15:22:57 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + use 0.0.0.0 or :: for c= line instead of server address + +2013-06-12 10:56:16 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-client.c: + use local address, not remote, in SDP + See https://bugzilla.gnome.org/show_bug.cgi?id=702063 + +2013-06-05 15:18:26 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 098c0d7 to 01a7a46 + +2013-05-29 13:45:00 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: possibility to override range time conversion + Make it possible to override the conversion from GstRTSPTimeRange to + GstClockTimes, that is done before seeking on the media + pipeline. Overriding can be useful for UTC ranges, where the default + conversion gives nanoseconds since 1900. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701191 + +2013-06-03 12:04:44 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + rtsp-server: Expose the use_client_settings API + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=699935 + +2013-05-30 08:07:48 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtspstream: handle both ipv4 and ipv6 clients + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701129 + +2013-05-31 15:28:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + Revert "rtsp-sdp: Parse width/height from caps and set SDP attribute" + This reverts commit 5fd034ff1a517db7f629ffcc3ed16839c61f5c97. + We already have a way to place extra attributes in the SDP by using a string + property with prefix x- or a- in the caps. + +2013-05-31 15:27:48 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + Revert "rtsp-sdp: Parse framerate caps field and set SDP attribute" + This reverts commit d6a4dee03642a2d2c05fec4752dc3ccb60b19494. + We already have a way to place extra attributes in the SDP, just make a string + property in the payloader with a- or x- prefix. + +2013-05-31 15:41:55 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp: place a- and x- properties as attributes + application/x-rtp has properties with a- and x- prefixes that should be + placed as attributes in the SDP for the media instead of being added to the + fmtp. + +2013-05-31 12:10:28 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-video.c: + example: add TLS example + +2013-05-31 11:42:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: add support for TLS + Add methods to set and get a TLS certificate. + Add vmethod to configure a new connection. By default, configure the TLS + certificate in a new connection if needed. + +2013-05-31 11:14:17 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: remove accept_client vmethod + This vmethod is not very useful so remove it. + +2013-05-30 17:23:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: don't crash on NULL GError + +2013-05-30 10:46:33 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-session-pool.c: + rtsp-session-pool: corrected session timeout detection + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=701253 + +2013-05-30 10:52:46 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: improve debug + +2013-05-30 07:18:22 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + server: refactor connection setup + Let the server accept the socket connection and construct a GstRTSPConnection + from it. Remove the code from the client and let the client only deal with + a fully configure GstRTSPConnection object. + We will need this later when the server will configure the connection for + TLS. + +2013-05-30 06:49:20 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: keep the transport object alive + Keep the transport object alive while we have it as qdata on the + source. + +2013-05-27 12:58:07 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + rtsp-server: Do not crash on nmapping of server + * generate error when gst_rtsp_connection_accept fails + * do not stop accepting incoming connections because + accepting a client fails + https://bugzilla.gnome.org/show_bug.cgi?id=701072 + +2013-05-24 13:39:50 +0200 Alexander Schrab <alexas@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: ipv4 adress should not be marked ipv6 even if socket is ipv6 + https://bugzilla.gnome.org/show_bug.cgi?id=700953 + +2013-05-22 03:29:38 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Parse framerate caps field and set SDP attribute + The SDP attribute and its format is described in RFC4566. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=700747 + +2013-05-22 03:29:30 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + rtsp-sdp: Parse width/height from caps and set SDP attribute + The SDP attribute and its format is described in RFC6064. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=700747 + +2013-04-29 14:46:30 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-sdp.c: + * tests/check/gst/client.c: + rtsp-sdp: add bandwidth line + https://bugzilla.gnome.org/show_bug.cgi?id=699220 + +2013-05-15 10:55:09 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 5edcd85 to 098c0d7 + +2013-04-23 11:28:39 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/gst/media.c: + tests: add dynamic payloader prepare/unprepare check + +2013-04-23 10:27:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: release lock when removing fakesink + +2013-04-23 10:16:17 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: set elements to NULL before removing + When removing a stream, set the elements to NULL first. This avoids + element-is-not-in-NULL-state errors when we dispose the elements. + +2013-04-22 23:55:48 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 3cb3d3c to 5edcd85 + +2013-04-22 17:34:37 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: listen to pad-removed signals + Listen to the pad-removed signal and remove the stream associated with the + removed pad. + Add signal to be notified of the removed pad. + Remove the fakesink in unprepare() + Fix signatures of the signal methods + +2013-04-22 17:33:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-sdp.c: + tests: add example of reusable pipelines + +2013-04-22 17:32:31 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add method to get the srcpad + +2013-04-22 16:49:39 +0200 Ognyan Tonchev <ognyan@axis.com> + + * tests/check/gst/media.c: + check: add media prepare/unprepare test + See https://bugzilla.gnome.org/show_bug.cgi?id=698376 + +2013-04-22 16:40:48 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: disconnect from signal handlers in unprepare() + We connected to the pad-added and no-more-pads signals in prepare() so + we need to disconnect from them in unprepare(). + See https://bugzilla.gnome.org/show_bug.cgi?id=698376 + +2013-04-22 16:25:17 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: don't free streams array + Don't free the streams array in the unprepare() method, they were not + added in prepare(). + See https://bugzilla.gnome.org/show_bug.cgi?id=698376 + +2013-04-22 16:19:35 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: don't unref the pipeline in unprepare + Unprepare() should undo what prepare() does. Because the pipeline is + not created in prepare(), we should not unref it in unprepare() + +2013-04-22 16:09:22 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: clear session and caps for reuse + Set the session and caps to NULL after unref otherwise we might unref + them again later. + See https://bugzilla.gnome.org/show_bug.cgi?id=698376 + +2013-04-15 12:21:54 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: send out teardown signal before tearing down + The advantage is that in the signal handler you get direct access to + information about what streams are about to get torn down (in the + GstRTSPClientState). + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=697686 + +2013-04-15 12:17:34 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: expose connection + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=697546 + +2013-04-14 17:58:22 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From aed87ae to 3cb3d3c + +2013-04-12 11:34:38 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + media: add method to get the base_time of the pipeline + Together with a shared clock, this base-time could eventually be sent to + the client so that it can reconstruct the exact running-time of the clock + on the server. + +2013-04-09 22:35:28 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + media: add GstNetTimeProvider support + Add a property to let the media provide a GstNetTimeProvider for its clock. + Make methods to get the clock and nettimeprovider + Add a x-gst-clock property to the SDP with the IP and port number of the nettime + provider and also the current time of the clock. This should make it possible + for (GStreamer) clients to slave their clock to the server clock. + +2013-04-09 21:02:47 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 04c7a1e to aed87ae + +2013-04-09 20:39:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: wait for buffering to complete + Wait for buffering to complete before changing the state to the target state. + +2013-04-09 20:11:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: small cleanup + +2013-03-20 12:33:54 +0100 David Svensson Fors <davidsf@axis.com> + + * tests/check/gst/rtspserver.c: + tests: remove extra unref in test_setup_non_existing_stream + The unref is not needed anymore, teardown runs without it. + https://bugzilla.gnome.org/show_bug.cgi?id=696542 + +2013-03-20 11:28:11 +0100 David Svensson Fors <davidsf@axis.com> + + * tests/check/gst/rtspserver.c: + tests: GSocketService cleanup in test_bind_already_in_use + Use g_socket_service_stop so the rtspserver test stops listening for + incoming connections in test_bind_already_in_use. + https://bugzilla.gnome.org/show_bug.cgi?id=696541 + +2013-03-22 18:25:07 -0400 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-media-factory.c: + rtsp-media-factory: g_signal_connect_object is not thread safe, can't use it here + Instead use a GWeakRef which is safe to use + This is a known GLib bug, see: + https://bugzilla.gnome.org/show_bug.cgi?id=667145 + +2013-02-22 14:17:29 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * tests/check/gst/media.c: + * tests/check/gst/rtspserver.c: + rtsp-media/client: Reply to PLAY request with same type of Range + Remember the type of Range from the PLAY request and use the same type for + the reply. + +2013-03-18 09:25:54 +0100 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * tests/check/gst/client.c: + rtsp-client: expose uri + +2013-03-13 17:46:58 -0400 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/mediafactory.c: + tests: Hold ref while creating second media + To test if the media aren't shared, make sure we keep the first one while creating a second + otherwise the same memory address may be reused. + +2013-03-12 00:10:18 +0000 Tim-Philipp Müller <tim@centricular.net> + + * configure.ac: + configure: remove out-of-date comment + +2013-03-12 00:05:49 +0000 Tim-Philipp Müller <tim@centricular.net> + + * .gitignore: + .gitignore: ignore more build files + +2013-03-12 00:03:36 +0000 Tim-Philipp Müller <tim@centricular.net> + + * tests/check/Makefile.am: + tests: use right _LIBS variable for gst-plugins-base libs + +2013-03-11 11:35:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + check: add librtp to libs + +2013-02-20 19:37:51 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: Add test to check selecting a port the server will send from + +2013-02-20 18:30:01 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: Make sure packets are actually received + +2013-02-19 18:27:20 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Select unicast address from pool if appropriate + +2013-02-19 16:43:08 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-stream.c: + stream: Properties are always there in Gst 1.0 + +2013-02-19 16:36:20 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/addresspool.c: + tests: Add tests for unicast addresses in pool + +2013-02-20 14:26:03 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * tests/check/gst/addresspool.c: + address-pool: Verify that multicast addresses are used for multicast and vice-versa + +2013-02-19 16:34:16 -0500 Olivier Crête <olivier.crete@collabora.com> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-stream.c: + * tests/check/gst/addresspool.c: + address-pool: Add unicast addresses + +2013-02-19 13:19:41 -0500 Olivier Crête <olivier.crete@collabora.com> + + * configure.ac: + * gst/rtsp-server/rtsp-server.c: + * tests/check/gst/rtspserver.c: + rtsp-server: Limit the number of threads per server instance + If we exceed the maximum, just round robin the clients over the existing + threads. + +2013-02-19 12:31:23 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: No need to store the GMainContext in the client context + +2013-02-18 20:22:18 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: Add test for client disconnection + +2013-02-18 20:15:41 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: Test client and session timeouts with multiple threads + +2013-02-18 14:59:58 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + Document locking and its order + +2013-02-15 20:02:31 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/rtspserver.c: + tests: Test that slow DESCRIBE don't block other clients + +2013-02-14 19:52:09 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/client.c: + tests: Add tests for client-requested multicast address + +2013-02-14 13:44:54 -0500 Olivier Crête <olivier.crete@collabora.com> + + * docs/libs/gst-rtsp-server-sections.txt: + docs: Put the various functions in the right sections + +2013-02-14 13:38:07 -0500 Olivier Crête <olivier.crete@collabora.com> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + docs: Generate docs for GstRTSPAddressPool + +2013-02-13 18:32:20 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + client: Check client provided addresses against the address pool + +2013-02-13 18:01:43 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * tests/check/gst/addresspool.c: + address-pool: Add API to request a specific address from the pool + Also add relevant unit tests. + +2013-02-12 19:34:24 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/mediafactory.c: + tests: Check the passing around of a RTSPAddressPool + Make sure the RTSPAddressPool is propagated from the MediaFactory all the + way down to the stream. + +2013-02-12 16:34:37 -0500 Olivier Crête <olivier.crete@collabora.com> + + * tests/check/gst/addresspool.c: + tests: Add more tests for the address pool + +2013-02-12 16:29:25 -0500 Olivier Crête <olivier.crete@collabora.com> + + * gst/rtsp-server/rtsp-address-pool.c: + address-pool: Fix off by one error + When splitting a port range, the port after a skip is not part of range. + +2013-03-07 00:04:19 +0000 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 2de221c to 04c7a1e + +2013-02-07 16:18:08 -0600 George McCollister <george.mccollister@gmail.com> + + * configure.ac: + configure: replace deprecated AM_CONFIG_HEADER with AC_CONFIG_HEADERS + AM_CONFIG_HEADER was removed in automake 1.13 + https://bugzilla.gnome.org/show_bug.cgi?id=693368 + +2013-01-28 20:45:44 +0100 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From a942293 to 2de221c + +2013-01-28 10:31:50 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: make sure the watch exists while sending data + Protect the send_func with a lock. This allows us to wait for sending + to complete before changing the send_func and user_data. We add an + extra ref to the watch to make sure that it remains valid during + sending. + When closing the connection, set the send_func to NULL + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=692433 + +2013-01-16 12:16:32 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * tests/check/Makefile.am: + tests: use GST_*_1_0 environment variables everywhere + The _1_0 suffixed environment variables override the + non-suffixed ones, so if we're in an environment that + sets the _1_0 suffixed ones, such as jhbuild, we need + to set those to make sure ours actually always get + used. + +2013-01-15 15:09:24 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * common: + Automatic update of common submodule + From acb04d9 to a942293 + +2012-12-14 11:58:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: set the client backlog + Set the client backlog to a reasonable default + +2012-12-04 09:47:35 +0100 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: Make the element a constructor parameter + https://bugzilla.gnome.org/show_bug.cgi?id=689594 + +2012-12-04 01:05:31 +0100 Sebastian Rasmussen <sebras@hotmail.com> + + * docs/libs/Makefile.am: + docs: Link with gcov library when gcov is enabled + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=689583 + +2012-11-30 15:03:15 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: match prepare with unprepare + Really unprepare when there were an equal amount of prepare calls. + +2012-11-30 14:58:46 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: media has to be unprepared in finalize + Because unprepare takes away the last ref on the media. + +2012-11-30 14:36:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Revert "client: never call gst_rtsp_media_unprepare, let gst_rtsp_media_finalize do it" + This reverts commit ba5b78ff2ff223049188eb456e228c709ccd3e05. + We can't use the refcount to trigger unprepare because it is the unprepare call + that removes the last refcount after all messages are consumed. What we should + probably do is make a prepared refcount and only unprepare when the refcount + reaches 0. + +2012-11-30 13:35:05 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: let the source unref the last media ref + the last ref to the media is held by the source so we don't need to add more ref + and unrefs, we simply destroy the media when the source is gone. + +2012-11-30 12:54:10 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: improve debug + +2012-11-30 12:53:02 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: check state + Make sure we are in the right state when collecting the position and duration. + Only make ourselves PREPARED when we were previously PREPARING. + +2012-11-30 10:05:48 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: use g_object_ref/unref for GObjects + +2012-11-30 07:05:25 +0100 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + client: never call gst_rtsp_media_unprepare, let gst_rtsp_media_finalize do it + Calling gst_rtsp_media_unprepare breaks shared medias. Just unref + GstRTSPMedia instances and let gst_rtsp_media_finalize unprepare when a media + isn't being used anymore. + +2012-11-30 06:17:46 +0100 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + Fix compiler warning + +2012-11-30 06:14:49 +0100 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + Add missing g_type_class_add_private in GstRTSPMediaFactoryURI + +2012-11-29 17:21:12 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-media.h: + small cleanup + +2012-11-29 17:20:56 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * tests/check/gst/media.c: + media: avoid element leak + +2012-11-29 17:20:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: require an element in media constructor + +2012-11-29 17:07:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Revert "client: TEARDOWN brings that state to Init again" + This reverts commit 4b61fdad85a3ca84752bf074fdb2fa203954b32e. + The object is already disposed, there is no point in setting the state. + +2012-11-29 12:30:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: TEARDOWN brings that state to Init again + +2012-11-29 11:11:05 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * examples/test-auth.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/media.c: + rtsp: make object details private + Make all object details private + Add methods to access private bits + +2012-11-28 14:50:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + * tests/check/gst/media.c: + tests: add media tests + +2012-11-28 14:45:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: check if prepared for some methods + Check that the media object is prepared before doing seek and getting the + current position etc. + Add some g_return checks. + +2012-11-28 12:40:46 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + * tests/check/gst/mediafactory.c: + tests: add mediafactory test + +2012-11-28 12:40:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: improve debug + +2012-11-28 12:39:37 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: unref pipeline in finalize to avoid leaking it + +2012-11-28 12:10:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media.c: + rtsp: use gst_object_unref on GstObjects + +2012-11-28 12:10:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: require an url + +2012-11-28 11:40:33 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-uri.c: + examples: fix include + +2012-11-28 11:17:27 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.h: + server: remove unused include + +2012-11-28 11:07:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + * tests/check/gst/mountpoints.c: + tests: add test for mountpoints + +2012-11-28 11:05:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix factory leak + Keep the factory in the state object only for authorization checks and make + sure we unref it on failure. Also don't keep invalid objects in the state + object. + +2012-11-28 10:40:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-mount-points.c: + mounts: add g_return_if guards + +2012-11-27 12:51:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + tests: add more tests + +2012-11-27 12:33:02 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: improve debug + +2012-11-27 12:24:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: improve debug and fix leaks + Cleanup the uri and session when there is a bad request. + +2012-11-27 12:17:05 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * common: + update common + +2012-11-27 12:13:59 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/gst/client.c: + test: add test for session in options request + +2012-11-27 12:11:41 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use 454 when session can't be found + We should use 454 when a session can't be found because there was no session + pool configured in the server. This is not a server configuration problem + because the server on which the request is done might not be the same one that + will keep the sessions for us and so it does not need to support sessions. + +2012-11-27 11:17:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: only free connection when there is one + It's possible that the client doesn't have a connection when we try to free it. + +2012-11-27 11:17:31 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + * tests/check/gst/client.c: + tests: add unit test for the client object + +2012-11-26 17:35:51 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: small cleanup + +2012-11-26 17:34:35 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.h: + client: remove unused include + +2012-11-26 17:34:24 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix compilation + +2012-11-26 17:28:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: call destroy without the lock + +2012-11-26 17:20:39 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: make the client usable without a socket + Make a method to let the client handle a message and a callback when the client + wants us to send a response message back. This makes it possible to also use the + client object without the sockets, which should make it easier to test. + +2012-11-26 16:45:04 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: small cleanup + +2012-11-26 16:39:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + client: remove reference to server + We don't need to keep a ref to the server + +2012-11-26 16:30:16 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add locking + Also add some g_return_if() + +2012-11-26 13:37:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: log more errors + +2012-11-26 13:35:48 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix compilation + +2012-11-26 13:16:59 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add generic close-after-send support + Add a property to send_response() to close the connection after the response has + been sent to the client. + +2012-11-26 12:34:05 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * docs/libs/gst-rtsp-server.types: + * examples/test-auth.c: + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-multicast.c: + * examples/test-multicast2.c: + * examples/test-ogg.c: + * examples/test-readme.c: + * examples/test-sdp.c: + * examples/test-uri.c: + * examples/test-video.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-mount-points.c: + * gst/rtsp-server/rtsp-mount-points.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * tests/check/gst/rtspserver.c: + MediaMapping -> MountPoints + Describes better what the object manages. + +2012-11-26 09:36:09 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: bump required version of -base + +2012-11-21 17:21:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: fix seeking + +2012-11-21 16:41:56 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: support more Range formats + Use the new -base methods to convert the Range string into a seek start and stop + value. + +2012-11-21 16:41:37 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-launch.c: + examples: fix whitespace + +2012-11-20 13:34:46 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + test-auth: add example of how to remove sessions + Add an example of the session filter api. + +2012-11-20 12:47:49 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-uri.c: + test-uri: remove mapping example + +2012-11-20 12:47:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-uri.c: + test-uri: fix callback signature + +2012-11-20 12:29:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + factory: keep ref to factory while media active + While the media from a factory is alive, keep a ref to the factory. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=663555 + +2012-11-20 12:29:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory-uri: add some debug + +2012-11-20 12:24:13 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: set udp sources to PLAYING + Set the UDP sources to PLAYING and locked state before we add it to the pipeline + so that it doesn't cause our pipeline to produce ASYNC-DONE. + +2012-11-20 12:10:16 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory-uri: take ref to factory + Take a ref to the factory that we place in our list. + +2012-11-20 11:30:09 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/Makefile.am: + * tests/test-reuse.c: + test: add test for server reuse + See https://bugzilla.gnome.org/show_bug.cgi?id=688395 + +2012-11-15 14:02:37 +0100 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-server.c: + server: start and stop multiple times + Stop listening on the RTSP port when the GSource is removed, so clients + can't connect and the server can be started again. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=688395 + +2012-11-20 11:24:35 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: fix small leak + +2012-11-20 09:42:51 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: unref source in finish_unprepare + The source is created in prepare, unref it in finish_unprepare. + See https://bugzilla.gnome.org/show_bug.cgi?id=688707 + +2012-11-19 15:47:08 +0100 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + rtsp-media: remove bus watch before finalizing + * A GDestroyNotify function is set for the bus watch in gst_rtsp_media_prepare. + * An extra media ref is added for the bus watch. This extra ref is unreffed by + the GDestroyNotify function. + * gst_rtsp_media_unprepare destroys the source so the bus watch is removed. + * GstRTSPClient, which calls gst_rtsp_media_prepare, also calls + gst_rtsp_media_unprepare before unreffing the media. + This way, the bus watch will be removed before the media is finalized. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=688707 + +2012-11-17 14:51:52 +0100 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: wait until the TEARDOWN response is sent to close the connection + Responses can be sent async so we need to wait until the TEARDOWN response has + been written before we close the connection to the client. This avoids the risk + of writing/polling closed sockets. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=688535 + +2012-11-19 15:44:27 +0100 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-stream.c: + rtsp-stream: plug socket leak + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=688703 + +2012-11-19 11:31:12 +0000 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 6bb6951 to a72faea + +2012-11-17 00:11:27 +0000 Tim-Philipp Müller <tim@centricular.net> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-server: don't use deprecated API + +2012-11-17 00:03:42 +0000 Tim-Philipp Müller <tim@centricular.net> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: fix unused-but-set-variable compiler warning + rtsp-client.c:1260:21: error: variable 'protocols' set but not used + +2012-11-15 17:11:16 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * TODO: + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-client.c: + rtsp: cleanups + +2012-11-15 16:52:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-multicast2.c: + examples: add another multicast example + Add an example for how to configure separate multicast ranges for each media + stream. + +2012-11-15 16:21:51 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-multicast.c: + test: set shared + +2012-11-15 16:18:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + stream: use the address managed by the stream + Use the address managed by the stream for multicast. This allows us to have 1 + multicast address for each stream. + Because the address is now managed by the stream we don't have to pass it around + anymore. + Set the address pool on the streams. + +2012-11-15 16:15:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + rtsp: improve debug + +2012-11-15 15:41:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add signal for new streams + This allows applications to listen for new streams and configure properties on + them, like the address pool. + +2012-11-15 15:41:19 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: configure address pool in new streams + +2012-11-15 15:36:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add methods to deal with address pool + Add methods to get and set the address pool for the stream + Add method to allocate and get the multicast addresses for this stream. + +2012-11-15 15:32:43 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: remove MTU property + It is a stream property + +2012-11-15 15:29:35 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: set blocksize only on stream + Set the blocksize only on the current stream. + +2012-11-15 13:52:07 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: share src and sink sockets + the allocated socket is in the used-socket property, not socket. + +2012-11-15 13:25:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * tests/check/gst/addresspool.c: + rtsp: make address-pool return an address object + Return a boxed GstRTSPAddress from the GstRTSPAddressPool. This allows us to + store more info in the structure and allows us to more easily return the address + to the right pool when no longer needed. + Pass the address to the StreamTransport so that we can return it to the pool + when the stream transport is freed or changed. + +2012-11-15 13:22:54 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-multicast.c: + examples: add multicast example + Show how to set up the multicast address pool so that media can be + server with multicast. + +2012-11-14 17:23:59 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp: use AddressPool + Remove the multicast_group property. + Use the configured addresspool to allocate multicast addresses. + +2012-11-14 16:17:33 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + address-pool: add clear method + +2012-11-14 16:10:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-address-pool.c: + address-pool: small cleanups + +2012-11-14 15:50:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/check/Makefile.am: + * tests/check/gst/addresspool.c: + tests: add addresspool unit test + +2012-11-14 15:49:06 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-address-pool.c: + * gst/rtsp-server/rtsp-address-pool.h: + address-pool: add object to manage multicast addresses + Make an object that can manage a rage of multicast addresses and ports. + +2012-11-13 12:05:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: set default max-threads property + +2012-11-13 11:54:17 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: wait for concurrent _prepare + If a prepare is busy, wait for the result. + +2012-11-13 11:49:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: add lock around message handler + We don't want to dispatch messages while we are still processing the result of + the state change. + +2012-11-13 11:15:35 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add lock to protect state changes + +2012-11-13 11:14:49 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: add locking + +2012-11-12 17:11:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + stream-transport: add keep-alive method + +2012-11-12 17:06:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + stream-transport: add method to handle RTP/RTCP + Call new methods instead of poking into the structures directly. + +2012-11-12 16:51:03 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + session-media: add locking + +2012-11-12 16:42:37 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + session: add locking + +2012-11-12 16:30:16 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: free old socket + +2012-11-12 16:18:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + mapping: add locking + +2012-11-12 16:14:19 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: add locking + +2012-11-12 16:03:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + auth: add locking + +2012-11-12 15:53:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: add max-thread property + +2012-11-12 15:29:39 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: use a threadpool for the mainloops + +2012-11-12 14:30:43 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: rename method + gst_rtsp_client_create_from_socket -> gst_rtsp_client_use_socket: we + don't really create the client from the socket, we use the socket for the + client. + +2012-11-12 14:09:09 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + server: rework maincontext handling in clients + Make a separate method to attach a client to a MainContext. + Let the server decide in what GMainContext the client will operate and give this + context to the client in attach. Then the server can later decide to use a + separate thread for each client or just use the mainthread. + +2012-11-12 12:40:34 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + session: move session header code in session object + +2012-11-04 00:14:25 +0000 Tim-Philipp Müller <tim@centricular.net> + + * COPYING: + * COPYING.LIB: + * examples/test-auth.c: + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-readme.c: + * examples/test-sdp.c: + * examples/test-uri.c: + * examples/test-video.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-params.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + * tests/check/gst/rtspserver.c: + * tests/test-cleanup.c: + Fix FSF address + +2012-10-28 13:48:44 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session.c: + rtsp-server: added annotations to indicate type of ownership transfer of return values + https://bugzilla.gnome.org/show_bug.cgi?id=680777 + +2012-10-28 15:37:51 +0000 Tim-Philipp Müller <tim@centricular.net> + + * configure.ac: + No need to define GST_USE_UNSTABLE_API any more, 1.0 is stable now + +2012-10-28 15:09:04 +0000 Tim-Philipp Müller <tim@centricular.net> + + * Makefile.am: + * bindings/Makefile.am: + * bindings/vala/Makefile.am: + * bindings/vala/gst-rtsp-server-0.10.deps: + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.deps: + * bindings/vala/packages/gst-rtsp-server-0.10.files: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + * bindings/vala/packages/gst-rtsp-server-0.10.namespace: + * configure.ac: + bindings: remove vala bindings + They'll be reunited with the other GStreamer bindings + https://bugzilla.gnome.org/show_bug.cgi?id=680777 + +2012-10-28 00:23:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + rtsp: only create transport when needed + Only create the StreamTransport when configured. + +2012-10-27 23:53:35 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: small cleanup + +2012-10-27 23:49:24 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + rtsp: refactor configuration of transport + Move the configuration of the transport to a place where it makes + more sense. + +2012-10-27 21:26:55 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: refactor transport parsing + +2012-10-27 21:05:03 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: refuse to change the MTU on shared media + If we change the MTU of chared media, it changes for all clients. + We don't want to set the MTU to something large for clients that + stream over UDP. + +2012-10-27 11:53:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-mp4.c: + * gst/rtsp-server/rtsp-media.c: + small fixes to docs and debug + +2012-10-26 17:29:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-stream.c: + stream: transports must already have been removed + +2012-10-26 17:28:10 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + stream: improve join and leave of the pipeline + simplify code + Do the cleanup properly + Add some docs + +2012-10-26 15:23:16 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: move unprepare below default implementation + Makes it easier to find the default implementation + +2012-10-26 15:21:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: signal unprepared when we actually finish + +2012-10-26 15:19:23 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: no need to unlock, unprepare does that when needed + +2012-10-26 12:33:21 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-sections.txt: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.h: + docs: update docs + +2012-10-26 12:04:02 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp: fix MTU setting + Fix setting of the MTU. There is no need for a vmethod. + +2012-10-26 11:02:43 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + docs: update docs + +2012-10-26 11:24:55 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: bump version number after refactoring + +2012-10-25 21:29:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-session-media.c: + * gst/rtsp-server/rtsp-session-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * gst/rtsp-server/rtsp-stream-transport.c: + * gst/rtsp-server/rtsp-stream-transport.h: + * gst/rtsp-server/rtsp-stream.c: + * gst/rtsp-server/rtsp-stream.h: + rtsp: massive refactoring + Make GObjects from the remaining simple structures. + Remove GstRTSPSessionStream, it's not needed. + Rename GstRTSPMediaStream -> GstRTSPStream: It is shorter + Rename GstRTSPMediaTrans -> GstRTSPStreamTransport: It describes how + a GstRTSPStream should be transported to a client. + Rename GstRTSPMediaFactory::get_element -> create_element because that + more accurately describes what it does. + Make nice methods instead of poking in the structures. + Move some methods inside the relevant object source code. + Use GPtrArray to store objects instead of plain arrays, it is more + natural and allows us to more easily clean up. + Move the allocation of udp ports to the Stream object. The Stream object + contains the elements needed to stream the media to a client. + Improve the prepare and unprepare methods. Unprepare should now undo + everything prepare did. Improve also async unprepare when doing EOS on + shutdown. Make sure we always unprepare correctly. + +2012-10-23 22:11:17 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: Unref server address clients connected to + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=686725 + +2012-10-22 16:09:24 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: don't ref server socket if it is NULL + Fixes test_bind_already_in_use unit test again after commit 6a497440. + https://bugzilla.gnome.org/show_bug.cgi?id=686644 + +2012-10-22 16:29:09 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * tests/check/Makefile.am: + tests: Add libgio link dependency + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=686647 + +2012-10-01 20:03:43 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + rtsp-media-mapping: rename find_media vfunc to find_factory + The virtual method and class method should have the same name + so it is correctly represented in GIR file + https://bugzilla.gnome.org/show_bug.cgi?id=680777 + +2012-10-01 19:46:15 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + rtsp-server: fixed comments and GIR annotations + https://bugzilla.gnome.org/show_bug.cgi?id=680777 + +2012-10-12 07:18:19 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media-mapping.c: + media-mapping: fix transfer mode for gst_rtsp_media_mapping_add_factory + +2012-10-12 07:08:57 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: allow binding on port 0 (binds on a random port) + +2012-10-12 06:21:24 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + rtsp-server: add bound-port property + bound-port can be used to retrieve the port number when the server is bound on + port 0, which binds on a random port. + +2012-10-12 06:11:36 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + rtsp-media-factory: make ::get_element overridable by GI bindings + The way to annotate vfuncs with GI seems to be to create an invoker (GI term) + for them and to annotate the invoker. Add gst_rtsp_media_factory_get_element() + as the invoker for ::get_element(), making it overridable by GI generated + bindings. + +2012-10-12 06:07:07 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-media-factory-uri: don't autoplug parsers in a loop + Stop autoplugging parsers if caps have parsed=true set. Fixes autoplugging + h264parse forever. + +2012-10-06 15:49:07 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/Makefile.am: + Explicitly link against gio. Fix link error on mac. + +2012-10-10 11:13:10 +0200 Ognyan Tonchev <ognyan.tonchev at axis.com> + + * gst/rtsp-server/rtsp-session.c: + session: add ttl to the transport header in SETUP + See https://bugzilla.gnome.org/show_bug.cgi?id=685561 + +2012-10-10 11:06:02 +0200 Ognyan Tonchev <ognyan.tonchev at axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + client: Use client transport settings for multicast if allowed. + This patch makes it possible for the client to send transport settings for + multicast (destination && ttl). Client settings must be explicitly allowed or + the server will use its own settings. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=685561 + +2012-10-06 15:02:27 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 6c0b52c to 6bb6951 + +2012-10-01 16:13:50 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: do not destroy the rtsp watch + Don't destroy the client watch while dispatching. The rtsp watch is + automatically destroyed after the rtsp watch function closed() has + been called. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=685220 + +2012-09-22 16:11:48 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 4f962f7 to 6c0b52c + +2012-09-10 16:25:57 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-media.c: + media: fix check for seekability + +2012-09-07 17:14:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use more GIO + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=681593 + +2012-09-07 17:14:10 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: remove obsolete includes + +2012-09-03 17:33:17 -0700 Aleix Conchillo Flaque <aleix@oblong.com> + + rtsp-media: also initialize transports in on_ssrc_active (bug #683304) + * gst/rtsp-server/rtsp-media.c: GstRTSPMediaStream transports might not + be available in "on_new_ssrc". The transports are added in + gst_rtsp_media_set_state when going to PLAYING state. However, + "on_new_ssrc" might be called before this happens. + https://bugzilla.gnome.org/show_bug.cgi?id=683304 + +2012-09-03 10:48:14 -0700 Aleix Conchillo Flaque <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: add signals for rtsp requests (fixes #683287) + +2012-08-30 12:03:27 -0700 Aleix Conchillo Flaque <aleix@oblong.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + add new-session signal to rtsp-client (fixes #683058) + +2012-08-22 13:34:55 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 668acee to 4f962f7 + +2012-08-15 15:54:32 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-server.c: + * tests/check/gst/rtspserver.c: + rtsp-server: fixed segfault in gst_rtsp_server_create_socket + Do not assume that *error is set in g_socket_address_enumerator_next. + Added test_bind_already_in_use unit-test. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=681914 + +2012-08-05 16:43:53 +0100 Tim-Philipp Müller <tim@centricular.net> + + * common: + Automatic update of common submodule + From 94ccf4c to 668acee + +2012-07-18 15:54:49 +0200 Patricia Muscalu <patricia@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + rtsp-client: make create_sdp virtual method + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=680173 + +2012-07-23 08:48:25 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 98e386f to 94ccf4c + +2012-07-10 11:39:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix docs + +2012-07-03 18:06:00 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + rtsp-server: use an existing socket to establish HTTP tunnel + Make it possible to transfer a socket from an HTTP server to be used as + an RTSP over HTTP tunnel. + +2012-07-03 13:26:30 +0200 Ognyan Tonchev <ognyan@axis.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp: Handle the blocksize parameter + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=679325 + +2012-06-25 14:28:10 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * tests/check/Makefile.am: + * tests/check/gst/rtspserver.c: + Have unit test get header from source dir, not installed dir + This makes compilation of unit tests work in a build directory other + than the source directory. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=678789 + +2012-06-23 15:06:11 +0100 Tim-Philipp Müller <tim@centricular.net> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: update for gst_element_make_from_uri() changes + +2012-06-19 15:25:36 +0200 David Svensson Fors <davidsf@axis.com> + + * configure.ac: + * tests/Makefile.am: + * tests/check/Makefile.am: + * tests/check/gst/rtspserver.c: + rtsp: add unit test + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=678076 + +2012-06-13 11:43:17 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-media.c: + rtsp-media: don't collect media stats when going to NULL + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=678015 + +2012-06-14 09:59:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: don't leak transports + +2012-06-12 14:45:39 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: free transport on no_stream in SETUP handler + +2012-06-12 14:33:35 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: changed session media iteration + In client_unlink_session: now don't iterate in session->medias + list where items are removed by gst_rtsp_session_release_media. + Instead, repeatedly remove the first item. + +2012-06-12 13:39:35 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: don't use g_object_unref on GstRTSPSessionMedia + GstRTSPSessionMedia is not a GObject type. When the + GstRTSPSession is freed, it will free the media. + +2012-06-12 13:36:57 +0200 David Svensson Fors <davidsf@axis.com> + + * gst/rtsp-server/rtsp-media-factory.c: + factory: plug pad leak in collect_streams + In gst_rtsp_media_factory_collect_streams: unref the srcpad that + was retrieved using gst_element_get_static_pad. gst_ghost_pad_new + will take one reference, and the other reference will otherwise + give a memory leak. + +2012-05-25 16:43:38 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * configure.ac: + configure: suppress some warnings when debug is disabled + Warnings about unused variables should be suppressed if core has the + debug system disabled. + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=676824 + +2012-06-09 17:41:05 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * docs/libs/Makefile.am: + docs: fix build in uninstalled setup + Include gst-plugins-base libs properly. + +2012-05-25 16:38:15 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * docs/libs/gst-rtsp-server.types: + docs: include headers defining rtsp-server object types + Fixes compiler warnings during docs build. + https://bugzilla.gnome.org/show_bug.cgi?id=676824 + +2012-05-25 17:11:53 +0200 Sebastian Rasmussen <sebrn@axis.com> + + * configure.ac: + configure: Add warning flags for compiler when configuring + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=676824 + +2012-06-08 15:07:06 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * common: + Automatic update of common submodule + From 03a0e57 to 98e386f + +2012-06-06 18:20:49 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * common: + Automatic update of common submodule + From 1fab359 to 03a0e57 + +2012-06-06 14:49:02 +0200 David Svensson Fors <davidsf at axis.com> + + * gst/rtsp-server/rtsp-client.c: + client: fix GSocketAddress leak in gst_rtsp_client_accept + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=677463 + +2012-06-01 10:30:58 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * common: + Automatic update of common submodule + From f1b5a96 to 1fab359 + +2012-05-31 13:11:43 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 92b7266 to f1b5a96 + +2012-05-30 12:48:51 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From ec1c4a8 to 92b7266 + +2012-05-30 11:27:31 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 3429ba6 to ec1c4a8 + +2012-05-22 15:37:25 +0200 David Svensson Fors <davidsf at axis.com> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-server.c: + rtsp: fix compiler warnings + Fixes https://bugzilla.gnome.org/show_bug.cgi?id=676500 + +2012-05-13 15:59:10 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From dc70203 to 3429ba6 + +2012-05-11 09:42:47 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + rtsp-server: port to new thread API + +2012-04-16 09:11:54 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 6db25be to dc70203 + +2012-04-13 15:27:22 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + rtsp-server: Fix compilation and compiler warnings + +2012-04-13 13:49:08 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * autogen.sh: + * configure.ac: + * gst/rtsp-server/Makefile.am: + configure: Modernize autotools setup a bit + Also we now only create tar.bz2 and tar.xz tarballs. + +2012-04-13 13:39:40 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 464fe15 to 6db25be + +2012-04-05 18:45:43 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 7fda524 to 464fe15 + +2012-04-04 14:45:55 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * configure.ac: + * docs/libs/Makefile.am: + * docs/version.entities.in: + * gst-rtsp.spec.in: + * gst/rtsp-server/Makefile.am: + * pkgconfig/Makefile.am: + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + * pkgconfig/gstreamer-rtsp-server.pc.in: + * tests/Makefile.am: + rtsp-server: Update versioning + +2012-03-29 15:12:21 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + Merge remote-tracking branch 'origin/0.10' + Conflicts: + gst/rtsp-server/rtsp-session-pool.c + +2012-03-27 10:13:20 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + rtsp-server: Don't use deprecated GLib API + +2012-03-26 12:23:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Replace master with 0.11 + +2012-03-26 12:22:05 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2012-03-26 12:20:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2012-03-19 10:48:09 +0000 Vincent Penquerc'h <vincent.penquerch@collabora.co.uk> + + * docs/README: + A couple minor typo fixes + +2012-03-13 18:10:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: fix state of the appqueue + +2012-03-13 16:06:50 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory: use videoconvert + +2012-03-13 16:02:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory: change to new style caps + +2012-03-07 15:03:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-pool.c: + rtsp-server: port to GIO + Port to GIO + +2012-03-07 15:03:24 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: fix build + +2012-02-29 15:56:06 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * docs/README: + docs: fix for gst_rtsp_server_set_port() -> _set_service() + https://bugzilla.gnome.org/show_bug.cgi?id=666548 + +2012-02-13 11:42:51 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + * examples/Makefile.am: + First rule of gst-rtsp-server club: don't talk about gst-phonon + +2012-02-13 11:40:44 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + * pkgconfig/Makefile.am: + * pkgconfig/gstreamer-rtsp-server-uninstalled.pc.in: + * pkgconfig/gstreamer-rtsp-server.pc.in: + pkg-config: rename gst-rtsp-server-0.11.pc to gstreamer-rtsp-server-0.11.pc + For consistency with all other modules. + +2012-02-13 11:06:33 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: update for new map API + +2012-02-13 10:37:37 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * .gitignore: + * bindings/Makefile.am: + * bindings/python/Makefile.am: + * bindings/python/arg-types.py: + * bindings/python/codegen/Makefile.am: + * bindings/python/codegen/__init__.py: + * bindings/python/codegen/argtypes.py: + * bindings/python/codegen/code-coverage.py: + * bindings/python/codegen/codegen.py: + * bindings/python/codegen/definitions.py: + * bindings/python/codegen/defsparser.py: + * bindings/python/codegen/docextract.py: + * bindings/python/codegen/docgen.py: + * bindings/python/codegen/fileprefix.override: + * bindings/python/codegen/fileprefixmodule.c: + * bindings/python/codegen/h2def.py: + * bindings/python/codegen/mergedefs.py: + * bindings/python/codegen/mkskel.py: + * bindings/python/codegen/override.py: + * bindings/python/codegen/reversewrapper.py: + * bindings/python/codegen/scmexpr.py: + * bindings/python/rtspserver-types.defs: + * bindings/python/rtspserver.defs: + * bindings/python/rtspserver.override: + * bindings/python/rtspservermodule.c: + * bindings/python/test.py: + * configure.ac: + python: remove pygst-based python bindings + pygi is the future, apparently. + +2012-01-25 14:12:41 +0100 Thomas Vander Stichele <thomas (at) apestaart (dot) org> + + * common: + Automatic update of common submodule + From c463bc0 to 7fda524 + +2012-01-25 11:40:59 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 2a59016 to c463bc0 + +2012-01-18 16:48:41 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 0807187 to 2a59016 + +2012-01-04 19:56:02 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * common: + Automatic update of common submodule + From 11f0cd5 to 0807187 + +2011-12-09 11:00:46 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-auth.c: + example: update for new caps + +2011-12-09 10:53:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp-server: port some more to 0.11 + Fix caps. + Remove bufferlist stuff + Update for new API. + Add queue before appsink now that preroll-queue-len is gone. + Update for request pad changes. + +2011-11-03 16:14:03 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-11-03 16:06:23 +0100 Fabian Deutsch <fabian.deutsch@gmx.de> + + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + bindings: Fix vala binding of gst_rtsp_media_mapping_add_factory to transfer ownership. + Signed-off-by: Fabian Deutsch <fabian.deutsch@gmx.de> + +2011-11-03 16:06:23 +0100 Fabian Deutsch <fabian.deutsch@gmx.de> + + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + bindings: Fix vala binding of gst_rtsp_media_mapping_add_factory to transfer ownership. + Signed-off-by: Fabian Deutsch <fabian.deutsch@gmx.de> + +2011-11-03 12:58:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-11-03 12:55:24 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add a seekable boolean + Maintain the seekable state with a new variable instead of reusing the + is_live variable. + +2011-09-16 11:31:17 -0400 Victor Gottardi <vgottardi@hotmail.com> + + * gst/rtsp-server/rtsp-media.c: + Disallow seek in live media + +2011-11-03 11:58:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-11-03 10:48:40 +0100 mat <matzepopatze@gmx.de> + + * gst/rtsp-server/rtsp-server.c: + #ifdef statements for windows socket creation were missing + +2011-09-06 21:53:46 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From a39eb83 to 11f0cd5 + +2011-09-06 16:07:18 +0200 Stefan Sauer <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 605cd9a to a39eb83 + +2011-08-16 16:39:26 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-08-16 16:07:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use method to access property + +2011-08-16 15:15:19 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add protocols property + Add a property to configure the allowed protocols in the media created from the + factory. + +2011-08-16 15:03:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add media-configure signal + Add signal to allow the application to configure the media after it was created + from the factory. + +2011-08-16 16:07:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use method to access property + +2011-08-16 15:15:19 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add protocols property + Add a property to configure the allowed protocols in the media created from the + factory. + +2011-08-16 15:03:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add media-configure signal + Add signal to allow the application to configure the media after it was created + from the factory. + +2011-08-16 14:50:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-08-16 13:43:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use media multicast group + +2011-08-16 13:37:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + retab some .h + +2011-08-16 13:31:52 +0200 Robert Krakora <rob.krakora at messagenetsystems.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.h: + sdp: copy and free the server ip address + Copy and free the server ip address to make memory management easier later. + +2011-08-16 13:27:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: configure multicast in media + +2011-08-16 13:25:16 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add property for multicast group + Add a property to configure the multicast group in the media. + Based on patches from Marc Leeman and Robert Krakora. + +2011-08-16 13:13:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add property for multicast group + Add a property to configure the multicast group in the media factory. + Based on patches from Marc Leeman and Robert Krakora. + +2011-08-16 12:51:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: do configuration of transport in one place + Move the configuration of the transport destination address to where we also + configure the other bits. + +2011-08-16 13:43:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use media multicast group + +2011-08-16 13:37:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + retab some .h + +2011-08-16 13:31:52 +0200 Robert Krakora <rob.krakora at messagenetsystems.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-sdp.h: + sdp: copy and free the server ip address + Copy and free the server ip address to make memory management easier later. + +2011-08-16 13:27:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: configure multicast in media + +2011-08-16 13:25:16 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add property for multicast group + Add a property to configure the multicast group in the media. + Based on patches from Marc Leeman and Robert Krakora. + +2011-08-16 13:13:36 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add property for multicast group + Add a property to configure the multicast group in the media factory. + Based on patches from Marc Leeman and Robert Krakora. + +2011-08-16 12:51:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: do configuration of transport in one place + Move the configuration of the transport destination address to where we also + configure the other bits. + +2011-08-16 12:11:59 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-08-16 12:09:48 +0200 Robert Krakora <rob.krakora at messagenetsystems.com> + + * gst/rtsp-server/rtsp-client.c: + client: destroy pipeline on client disconnect with no prior TEARDOWN. + The problem occurs when the client abruptly closes the connection without + issuing a TEARDOWN. The TEARDOWN handler in the rtsp-client.c file of the RTSP + server is where the pipeline gets torn down. Since this handler is not called, + the pipeline remains and is up and running. Subsequent clients get their own + pipelines and if the do not issue TEARDOWNs then those pipelines will also + remain up and running. This is a resource leak. + +2011-08-16 11:53:37 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-06-30 10:13:59 +0200 Emmanuel Pacaud <emmanuel@gnome.org> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add a "media-constructed" signal to GstRTSPMediaFactory + For example, it can be used to retrieve source elements like appsrc, in a more + convenient way than subclassing get_element. + +2011-08-16 11:12:33 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-08-11 18:07:08 -0700 David Schleef <ds@schleef.org> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: hold on to reference while using object + +2011-08-04 08:59:17 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: use new api + +2011-08-04 08:58:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: use unstable api + +2011-06-27 11:26:26 -0700 David Schleef <ds@schleef.org> + + * gst/rtsp-server/rtsp-client.c: + client: fix reference counting + +2011-07-20 17:16:42 +0200 Thijs Vermeir <thijsvermeir@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + fix compiler warnings about unused variables + +2011-07-19 16:10:39 +0200 Stefan Sauer <ensonic@google.com> + + * examples/test-launch.c: + * examples/test-readme.c: + * examples/test-uri.c: + * examples/test-video.c: + examples: tell rtsp uri when ready + +2011-06-23 11:30:14 -0700 David Schleef <ds@schleef.org> + + * common: + Automatic update of common submodule + From 69b981f to 605cd9a + +2011-06-13 19:05:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: update for buffer API change + +2011-06-07 10:54:26 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + Makefile.am: 0.10 => @GST_MAJORMINOR@ + +2011-06-07 10:59:16 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + rtsp-media-factory-uri: GST_PLUGIN_FEATURE_NAME is no longer + +2011-06-07 10:59:03 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * gst/rtsp-server/.gitignore: + .gitignore: 0.10 => 0.11 + +2011-06-07 10:54:26 +0200 Edward Hervey <edward.hervey@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + Makefile.am: 0.10 => @GST_MAJORMINOR@ + +2011-05-24 18:26:06 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-05-19 23:00:52 +0300 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 9e5bbd5 to 69b981f + +2011-05-18 16:14:10 +0300 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From fd35073 to 9e5bbd5 + +2011-05-18 12:27:35 +0300 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 46dfcea to fd35073 + +2011-05-17 09:48:13 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media.c: + media: port to new caps API + +2011-05-17 09:45:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + +2011-05-03 21:13:15 +0200 Fabian Deutsch <fabian.deutsch@gmx.de> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + Updated Vala bindings. + Signed-off-by: Fabian Deutsch <fabian.deutsch@gmx.de> + +2011-05-03 16:24:28 +0200 Fabian Deutsch <fabian.deutsch@gmx.de> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + Add a signal for newly connected clients. + Signed-off-by: Fabian Deutsch <fabian.deutsch@gmx.de> + +2011-05-08 13:15:19 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/rtspserver.override: + python: override gst_rtsp_media_mapping_add_factory to fix refcounting + +2011-04-26 19:22:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-funnel.c: + * gst/rtsp-server/rtsp-funnel.h: + * gst/rtsp-server/rtsp-media.c: + rtsp-server: port to 0.11 + +2011-04-26 19:14:18 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * common: + add common + +2011-04-26 19:07:13 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + Conflicts: + common + configure.ac + +2011-04-24 14:07:11 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * common: + Automatic update of common submodule + From c3cafe1 to 46dfcea + +2011-04-20 11:19:38 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/Makefile.am: + * bindings/python/rtspserver.defs: + python bindings: wrap GstRTSPMediaFactoryClass vfuncs + +2011-04-20 11:13:56 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/arg-types.py: + python bindings: add GstRTSPUrlParam + Needed to implement MediaFactory virtual proxies + +2011-04-20 10:19:46 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/arg-types.py: + python bindings: fix returning GstRTSPUrl types + +2011-04-20 10:17:07 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/arg-types.py: + python bindings: add arg type for GstRTSPUrl + +2011-04-20 10:16:08 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * bindings/python/rtspserver.defs: + python bindings: fix the definition of MediaFactory.collect_stream + +2011-04-04 15:59:50 +0300 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 1ccbe09 to c3cafe1 + +2011-03-25 22:38:06 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 193b717 to 1ccbe09 + +2011-03-25 14:58:34 +0200 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From b77e2bf to 193b717 + +2011-03-25 10:04:57 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * Makefile.am: + build: Include lcov.mak to allow test coverage report generation + +2011-03-25 09:35:15 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From d8814b6 to b77e2bf + +2011-03-25 09:11:40 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * common: + Automatic update of common submodule + From 6aaa286 to d8814b6 + +2011-03-24 18:51:37 +0200 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 6aec6b9 to 6aaa286 + +2011-03-18 19:34:57 +0100 Luis de Bethencourt <luis@debethencourt.com> + + * autogen.sh: + autogen: wingo signed comment + +2011-03-03 20:38:03 +0100 Miguel Angel Cabrera Moya <madmac2501@gmail.com> + + * gst/rtsp-server/rtsp-session-pool.c: + session: use full charset for RTSP session ID + As specified in RFC 2326 section 3.4 use full valid charset to make guessing + session ID more difficult. + https://bugzilla.gnome.org/show_bug.cgi?id=643812 + +2011-03-07 10:23:06 +0100 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + rtsp-server: Don't install the funnel header + +2011-02-28 18:35:03 +0100 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk> + + * common: + Automatic update of common submodule + From 1de7f6a to 6aec6b9 + +2011-02-26 19:58:02 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: require core/base 0.10.31 + Needed at least for gst_plugin_feature_rank_compare_func(). + +2011-02-14 12:56:29 +0200 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From f94d739 to 1de7f6a + +2011-02-02 15:37:03 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: remove more unused code + +2011-02-02 15:30:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: remove duplicate filtering + Remove the duplicate filtering code now that we have a released -good version. + Give a warning instead. + +2011-01-31 17:38:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + media: fix default buffer size + +2011-01-31 17:37:02 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add property to configure the buffer-size + Add a property to configure the kernel UDP buffer size. + +2011-01-31 17:28:22 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add property to configure kernel buffer sizes + Add a property to configure the kernel UDP buffer size. + +2011-01-26 15:52:54 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: set PYGOBJECT_REQ before using it + https://bugzilla.gnome.org/show_bug.cgi?id=640641 + +2011-01-24 11:59:22 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * docs/Makefile.am: + docs: recursive into sub-directories on 'make upload' + +2011-01-24 11:53:17 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/version.entities.in: + docs: mention full version these docs are for, not just major-minor + +2011-01-24 12:07:17 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.8 === + +2011-01-24 11:57:12 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + release 0.10.8 + +2011-01-19 15:29:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + rtsp-server: clarify docs a little + +2011-01-13 18:57:15 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: init debug category before starting thread + +2011-01-13 18:40:48 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: add realm to make it more spec compliant + +2011-01-12 18:57:41 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: add locking + +2011-01-12 18:33:49 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + example: improve example docs a little + +2011-01-12 18:26:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: ensure the watch has a ref to the server + +2011-01-12 18:24:44 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: simpify channel function + +2011-01-12 18:18:13 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: simplify management of channel and source + We don't need to keep around the channel and source objects. Let the mainloop + and the source manage the source and channel respectively. + +2011-01-12 18:17:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * Makefile.am: + * configure.ac: + build tests + +2011-01-12 18:16:46 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * tests/.gitignore: + * tests/Makefile.am: + * tests/test-cleanup.c: + tests: add tests directory and cleanup test + +2011-01-12 18:14:48 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + server: improve debugging in various objects + +2011-01-12 16:38:34 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: chain up to the parent finalize + +2010-09-21 17:04:02 -0300 André Dieb Martins <andre.dieb@gmail.com> + + * bindings/python/rtspserver-types.defs: + * bindings/python/rtspserver.defs: + * bindings/python/rtspserver.override: + * bindings/python/test.py: + gst-rtsp-server: update python bindings + +2011-01-12 15:37:39 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use the response from the clientstate + Create the response object only once and store in the client state. + Make all methods use the state response, + +2011-01-12 15:36:22 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: use signal to keep track of clients + Keep track of all the clients that the server creates and remove them when they + fire the 'closed' signal. + +2011-01-12 15:35:51 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: emit signal when closing + +2011-01-12 13:57:09 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-auth.c: + * examples/test-video.c: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.h: + media: enable per factory authorisations + Allow for adding a GstRTSPAuth on the factory and media level and check + permissions when accessing the factory. + Add hints to the auth methods for future more fine grained authorisation. + Add example application for per factory authentication. + +2011-01-12 13:16:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-params.h: + rtsp-server: Pass ClientState structure arround + Pass the collected information for the ongoing request in a GstRTSPClientState + structure that we can then pass around to simplify the method arguments. This + will also be handy when we implement logging functionality. + +2011-01-12 12:07:40 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: add methods to configure authorisation + +2011-01-12 12:07:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: unref auth in finalize + +2011-01-12 12:07:04 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: unref auth in finalize + +2011-01-12 11:07:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * docs/libs/gst-rtsp-server.types: + docs: add more docs + +2011-01-12 10:57:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: separate create and accept + Create separate create and accept methods so that subclasses can create custom + client object. + Configure the server in the client object and prepare for keeping track of + connected clients. + +2011-01-12 10:42:52 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + client: add support for setting the server. + Add support for keeping a ref to the server that started this client + connection. + +2011-01-12 10:41:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + auth: fix memleak and add some docs + Fix a memleak of the basic auth token. + Add docs for the helper function + +2011-01-12 00:35:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + client: delegate setup of auth to the manager + Delegate the configuration of the authentication tokens to the manager object + when configured. + +2011-01-12 00:17:54 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-auth.c: + * gst/rtsp-server/rtsp-auth.h: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + auth: add authentication object + Add an object that can check the authorization of requests. + Implement basic authentication. + Add example authentication to test-video + +2011-01-12 00:20:36 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: move includes back + the includes are needed for sockaddr_in. + +2011-01-11 22:41:12 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + rtsp: move network includes where they are needed + +2011-01-07 23:45:32 +0200 Sreerenj Balachandran <sreerenj.balachandran@nokia.com> + + * gst/rtsp-server/rtsp-media.h: + rtsp-media.h: Minor corrections in comments. + Fixes #638944 + +2011-01-11 15:52:44 +0200 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From e572c87 to f94d739 + +2011-01-11 13:01:44 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * .gitignore: + * docs/.gitignore: + * docs/libs/.gitignore: + * examples/.gitignore: + * gst/rtsp-server/.gitignore: + gitignore: updates + +2011-01-11 12:58:39 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * docs/libs/Makefile.am: + docs: We don't build ps/pdf for API reference docs + +2011-01-10 16:39:36 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * common: + Automatic update of common submodule + From ccbaa85 to e572c87 + +2011-01-10 14:56:39 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * common: + Automatic update of common submodule + From 46445ad to ccbaa85 + +2011-01-10 15:10:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-funnel.c: + * gst/rtsp-server/rtsp-funnel.h: + * gst/rtsp-server/rtsp-media.c: + funnel: rename fsfunnel to rtspfunnel + Rename the funnel to avoid conflicts with the farsight one. + +2011-01-10 13:41:43 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/fs-funnel.c: + * gst/rtsp-server/fs-funnel.h: + * gst/rtsp-server/rtsp-media.c: + rtsp-media: add and use fsfunnel + Add a copy of fsfunnel to the build because input-selector removed the (broken) + select-all property that we need. + +2011-01-08 01:58:44 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + gobject-introspection: use PKG_CONFIG_PATH specified at configure time + Use PKG_CONFIG_PATH specified at configure time (if any) as well + for the g-ir-compiler, rather than just assuming the env var has + been set. + +2011-01-08 01:55:06 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * .gitignore: + * Makefile.am: + * configure.ac: + * m4/Makefile.am: + * m4/codeset.m4: + build: make autotools put all .m4 cruft into m4/ rather than polluting common/m4 + +2011-01-08 01:15:35 +0000 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + * gst/rtsp-server/Makefile.am: + gobject-introspection: fix g-i build for uninstalled setup + Requires gst-plugins-base git (> 0.10.31.2). + +2011-01-07 11:27:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-uri.c: + examples: add some more options and comments + +2011-01-07 11:24:39 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory-uri: use right property type + +2011-01-05 12:07:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory-uri: attempt to configure buffer-lists + Attempt to configure buffer lists in the payloader for improved performance. + +2011-01-05 12:06:23 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: attempt to configure bigger UDP buffers + Attempt to configure bigger udp kernel send buffers to avoid overflowing the + send buffers with high bitrate streams. + +2011-01-05 11:26:30 +0100 Jonas Larsson <jonas at hallerud dot se> + + * gst/rtsp-server/rtsp-client.c: + client: use the socket length from getsockname + Use the length returned by getsockname to perform the getnameinfo call because + the size can depend on the socket type and platform. + Fixes #638723 + +2010-12-30 12:41:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + docs: add uri factory to the docs + +2010-12-30 12:41:31 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.h: + docs: improve docs + +2010-12-29 16:26:41 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp-server: add support for buffer lists + Add support for sending bufferlists received from appsink. + Fixes #635832 + +2010-12-28 18:35:01 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + media: make method to retrieve the play range + Make a method to retrieve the playback range so that we can conditionally create + a different range for the SDP and the PLAY requests. + +2010-12-28 18:34:10 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add signal to notify of state changes + +2010-12-28 18:31:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.h: + client: cleanup headers + +2010-12-28 12:18:41 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix typo + +2010-12-23 18:53:01 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + factory-uri: add support for gstpay + Add an option to prefer gstpay over decoder + raw payloader. + +2010-12-23 15:58:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + factory-uri: rework the autoplugger. + Rewrite the autoplugger a little so that it prefers to plug demuxers and parsers + before payloaders. + +2010-12-21 17:37:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + factory-uri: use better factory filter + Make better payloader filter based on autoplug rank and RTP use case. + +2010-12-20 17:48:41 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * common: + Automatic update of common submodule + From 169462a to 46445ad + +2010-12-18 11:24:48 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: set SO_REUSEADDR before bind + Set the SO_REUSEADDR _before_ bind() to make it actually work. + +2010-12-13 16:58:36 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: emit prepared signal when prepared + Make a 'prepared' signal and emit it when we successfully prepared the element. + This signal can be used to configure the media object after it has been prepared + for streaming. + +2010-12-15 14:58:00 +0200 Stefan Kost <ensonic@users.sf.net> + + * common: + Automatic update of common submodule + From 011bcc8 to 169462a + +2010-12-13 16:38:09 +0100 Andy Wingo <wingo@oblong.com> + + python an optional dependency + * configure.ac: Move up valgrind and g-i checks. Make the python + dependency optional, as it was before. + +2010-12-13 11:43:13 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' into 0.11 + Conflicts: + common + configure.ac + +2010-12-12 15:48:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: update range when active clients changed + When we changed the number of active clients, update the current range + information because we want the second client connecting to a shared resource + continue from where the stream currently. + +2010-12-12 04:06:41 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + factory-uri: add colorspace and fix pt + Rework the way we pass data to the autoplugger. + When we have raw caps, plug a converter element to make pluggin to raw + payloaders more successful. + Make sure all dynamically plugged payloaders have a unique payload types. + +2010-12-11 18:06:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-uri.c: + example: add example of the uri factory + +2010-12-11 18:01:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-media-factory-uri.c: + * gst/rtsp-server/rtsp-media-factory-uri.h: + * gst/rtsp-server/rtsp-server.h: + factory-uri: add a factory to stream any URI + Make a factory that uses uridecodebin to decode any uri and autoplug a payloader + when we have one. + +2010-12-11 17:31:44 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: ignore spurious ASYNC_DONE messages + When we are dynamically adding pads, the addition of the udpsrc elements will + trigger an ASYNC_DONE. We have to ignore this because we only want to react to + the real ASYNC_DONE when everything is prerolled. + +2010-12-11 13:41:24 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + media-factory: make lock macro + +2010-12-11 10:53:28 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp-server: Remove unused variable and dead assignment + +2010-12-11 10:49:30 +0100 Edward Hervey <bilboed@bilboed.com> + + * examples/test-launch.c: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-readme.c: + * examples/test-sdp.c: + * examples/test-video.c: + examples: Run gst-indent + +2010-12-11 10:48:42 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + rtsp-server: Run gst-indent + Since it wasn't using the upstream common previously, there was no + indentation check before commiting. + +2010-12-11 10:48:25 +0100 Edward Hervey <bilboed@bilboed.com> + + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp-server: Some more doc fixups + +2010-12-07 18:56:03 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * Makefile.am: + Makefile: Add cruft-cleaning support + +2010-12-07 18:52:15 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * Makefile.am: + * configure.ac: + * docs/Makefile.am: + * docs/libs/Makefile.am: + * docs/libs/gst-rtsp-server-docs.sgml: + * docs/libs/gst-rtsp-server-sections.txt: + * docs/libs/gst-rtsp-server.types: + * docs/version.entities.in: + docs: Add gtk-doc build system + +2010-12-07 18:14:39 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + Makefile.am: Use standard GIR make behaviour + +2010-12-07 18:14:22 +0100 Edward Hervey <edward.hervey@collabora.co.uk> + + * autogen.sh: + * configure.ac: + autogen/configure: Bring more in sync to standard gst module behaviour + +2010-12-06 19:29:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: warn and fail when gstrtpbin is not found + +2010-12-06 12:40:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: open 0.11 branch + +2010-12-01 20:00:22 +0100 Edward Hervey <bilboed@bilboed.com> + + * .gitmodules: + * common: + Add common submodule + +2010-12-01 19:58:49 +0100 Edward Hervey <bilboed@bilboed.com> + + * common/ChangeLog: + * common/Makefile.am: + * common/c-to-xml.py: + * common/check.mak: + * common/coverage/coverage-report-entry.pl: + * common/coverage/coverage-report.pl: + * common/coverage/coverage-report.xsl: + * common/coverage/lcov.mak: + * common/gettext.patch: + * common/glib-gen.mak: + * common/gst-autogen.sh: + * common/gst-xmlinspect.py: + * common/gst.supp: + * common/gstdoc-scangobj: + * common/gtk-doc-plugins.mak: + * common/gtk-doc.mak: + * common/m4/.gitignore: + * common/m4/Makefile.am: + * common/m4/README: + * common/m4/as-ac-expand.m4: + * common/m4/as-auto-alt.m4: + * common/m4/as-compiler-flag.m4: + * common/m4/as-compiler.m4: + * common/m4/as-docbook.m4: + * common/m4/as-libtool-tags.m4: + * common/m4/as-libtool.m4: + * common/m4/as-python.m4: + * common/m4/as-scrub-include.m4: + * common/m4/as-version.m4: + * common/m4/ax_create_stdint_h.m4: + * common/m4/check.m4: + * common/m4/glib-gettext.m4: + * common/m4/gst-arch.m4: + * common/m4/gst-args.m4: + * common/m4/gst-check.m4: + * common/m4/gst-debuginfo.m4: + * common/m4/gst-default.m4: + * common/m4/gst-doc.m4: + * common/m4/gst-error.m4: + * common/m4/gst-feature.m4: + * common/m4/gst-function.m4: + * common/m4/gst-gettext.m4: + * common/m4/gst-glib2.m4: + * common/m4/gst-libxml2.m4: + * common/m4/gst-plugindir.m4: + * common/m4/gst-valgrind.m4: + * common/m4/gtk-doc.m4: + * common/m4/introspection.m4: + * common/m4/pkg.m4: + * common/mangle-tmpl.py: + * common/plugins.xsl: + * common/po.mak: + * common/release.mak: + * common/scangobj-merge.py: + * common/upload.mak: + common: Remove static version + +2010-11-08 17:04:00 +0000 Bastien Nocera <hadess@hadess.net> + + * common/m4/introspection.m4: + Update introspection.m4 to match usage + +2010-10-30 13:26:12 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * README: + README: update + Remove old stuff from the README + +2010-10-11 11:12:11 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.7 === + +2010-10-11 11:05:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + release 0.10.7 + +2010-10-04 17:16:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-ogg.c: + test-ogg: remove parsers + Remove the parsers, they are not needed anymore as oggdemux now outputs normal + buffers with timestamps. Using the parsers also seems to break things. + +2010-09-23 12:44:18 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Updated Vala bindings + +2010-09-22 23:13:37 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * common/m4/introspection.m4: + * configure.ac: + * gst/rtsp-server/Makefile.am: + Added initial gobject-introspection support + +2010-09-23 11:32:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: don't use host for shared hash key + When we generate the key to share made between connections, don't include the + host used to connect so that we can share media even if between clients that + connected with localhost and ones with the ip address. + +2010-09-22 21:16:03 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * bindings/vala/Makefile.am: + build: fix distcheck + +2010-09-22 18:24:12 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Update Vala bindings + +2010-09-22 18:12:50 +0200 Sebastian Dröge <sebastian.droege@collabora.co.uk> + + * bindings/vala/Makefile.am: + * configure.ac: + Fix configure checks and installation location for Vala bindings + Fixes bug #628676. + +2010-09-22 16:32:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.6 === + +2010-09-22 16:22:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: release 0.10.6 + +2010-09-22 16:15:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: help the compiler a little + +2010-08-24 16:47:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + media: cleanup media transport before freeing + Cleanup the media transport data before freeing. In particular, remove the qdata + from the rtpsource object. + +2010-08-20 18:17:08 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media-factory: add eos-shutdown property + Add an eos-shutdown property that will send an EOS to the pipeline before + shutting it down. This allows for nice cleanup in case of a muxer. + Fixes #625597 + +2010-08-20 15:58:39 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: use multiudpsink send-duplicates when we can + If we have a new enough multiudpsink with the send-duplicates property, use this + instead of doing our own filtering. Our custom filtering code should eventually + be removed when we can depend on a released -good. + +2010-08-20 13:19:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: don't leak destinations + Refactor and cleanup the destinations array when the stream is destroyed. + +2010-08-20 13:09:12 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: don't add udp addresses multiple times + Keep track of the udp addresses we added to udpsink and never add the same udp + destination twice. This avoids duplicate packets when using multicast. + +2010-08-20 10:18:34 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: disable use of SO_LINGER + SO_LINGER cause the client to fail to receive a TEARDOWN message because the + server close()s the connection. + +2010-08-19 18:52:47 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: use 5 second linger period in SO_LINGER + Wait 5 seconds before clearing the send buffers and reseting the connection with + the client when we do a close. This should be enough time to get the message to + the client. + See #622757 + +2010-08-16 12:32:28 +0200 Robert Krakora <rob.krakora at messagenetsystems.com> + + * gst/rtsp-server/rtsp-server.c: + server: use SO_LINGER + SO_LINGER on the socket will make sure that any pending data on the socket is + flushed ASAP and that the socket connection is reset. This makes sure that the + socket can be reused immediately. + Fixes 622757 + +2010-08-16 12:24:50 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + README: add blurb about shared media factories + +2010-08-09 12:56:23 -0700 David Schleef <ds@schleef.org> + + * gst/rtsp-server/rtsp-media.c: + Add stdlib.h for atoi() + +2010-05-20 14:33:24 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * bindings/python/Makefile.am: + * bindings/vala/Makefile.am: + build: distcheck fixes + Fix 'make distcheck', somewhat (it still fails because it tries to + install files into /usr/share/vala/vapi/ irrespective of the + configured prefix). + +2010-05-20 14:09:18 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: bump core/base requirements to released version + Makes things less confusing for people. + +2010-04-25 16:35:30 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: fail if GStreamer core/base requirements are not met + +2010-04-06 17:08:40 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: improve client cleanups + Make sure the session does not timeout when using TCP. We need to do this + because quicktime player does not send RTCP for some reason in tunneled + mode. + Refactor some cleanup code. + Fixes #612915 + +2010-04-06 17:07:27 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + session: add support for prevent session timeouts + Add an atomix counter to prevent session timeouts when we are, for example, + streaming over TCP. + +2010-04-06 15:45:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix unlink on session timeouts + When our session times out, make sure we unlink all streams in this + session. + Remove the tunnelid when closing the connection. + +2010-04-06 15:44:45 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + session: small cleanups + +2010-04-06 11:13:51 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: handle lost_tunnel callbacks + Handle lost_tunnel callbacks and use it to store the tunnelid back into the + hashtable so that we can reuse it for when the client reopens the POST + socket. + Close the connection after a TEARDOWN. + Make sure or watchid is cleared when the watch is removed. + Fixes #612915 + +2010-03-19 18:03:40 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + rtsp-server: add more support for multicast + +2010-03-19 15:15:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: allow configuration of allowed lower transport + +2010-03-16 18:37:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-server.c: + rtsp: keep track of server ip and ipv6 + Keep track of how the client connected to the server and setup the udp ports + with the same protocol. + Copy the server ip address in the SDP so that clients can send RTCP back to + us. + +2010-03-16 18:34:43 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + session: indent + +2010-03-16 18:33:23 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use right size for malloc + +2010-03-10 11:45:30 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + server: comment ipv6 server listening address + +2010-03-10 11:45:06 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: allow for ipv6 sockets + +2010-03-09 13:49:00 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + server: rework server part + Allow setting a bind address, make sure we can deal with ipv6. + Remove the port property and change with the service property. + +2010-03-09 13:44:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.h: + media: update comments a little + +2010-03-09 13:43:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: make content-base better + Use the URI formatting functions to make a content-base. Also make sure that + there is a trailing / at the end. + +2010-03-09 13:42:50 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: guard against invalid paths + +2010-03-09 13:41:33 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + test: catch server bind errors + +2010-03-09 10:27:38 +0100 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-media.c: + rtspmedia: emit "unprepared" if _prepare fails. + Emit the unprepared signal if gst_rtsp_media_prepare fails so that the + media object is removed from its factory's cache. + +2010-03-05 19:08:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: collect media position when seek completes + +2010-03-05 18:37:17 +0100 Luca Ognibene <luca.ognibene at gmail.com> + + * gst/rtsp-server/rtsp-client.c: + client: call unlink_streams in client finalize + Fixes #599027 + +2010-03-05 18:23:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: limit the time to wait to something huge + Avoid waiting forever but limit the timeout to 20 seconds. + +2010-03-05 17:57:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: reindent and check for prepared status + +2010-03-05 17:51:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + media: avoid doing _get_state() for state changes + When preparing, use the ASYNC_DONE and ERROR messages in the bus handler to wait + until the media is prerolled or in error. This avoids doing a blocking call of + gst_element_get_state() that can cause lockups when there is an error. + Fixes #611899 + +2010-03-05 16:20:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: reindent + +2010-03-05 13:34:15 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + media-factory: better error handling + Improve the error handling a bit. + +2010-03-05 13:31:37 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: rework transport parsing + Rework the transport parsing code so that we can ignore transports we don't + support instead of just picking the first one we can parse. + Configure a (for now hardcoded) destination for multicast transports. + +2010-03-05 13:28:58 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: set multicast sink parameters + Disable loop and automatic multicast join on the udpsink elements. + Add some more debug info. + Reset some state variables in the right place. + Use the right port numbers for multicast. + +2010-03-05 13:27:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + session: handle transport setup correctly + Handle UDP, MCAST and TCP transport negotiation more correctly. + Store the server session SSRC in the transport. + +2010-01-27 18:38:27 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + rtsp-client: implement error_full + Implement error_full to avoid some segfaults when the rtspconnection calls it. + See #608245 + +2009-12-25 18:24:10 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-server.c: + docs: update docs and comments + +2009-12-25 15:22:23 +0100 Nikolay Ivanov <ivnik@mail.ru> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: make server work better when behind a proxy + +2009-11-21 01:17:25 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-client.c: + client: dump rtsp message only if debug threshold is higher than GST_LEVEL_LOG + +2009-11-21 19:20:23 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + Use GStreamer's debugging subsystem + +2009-11-21 01:00:39 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media-factory.c: + server: Set ghost pad active in gst_rtsp_media_factory_collect_streams + +2009-11-05 11:22:44 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.5 === + +2009-11-05 11:20:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + release 0.10.5 + +2009-10-14 12:11:31 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + configure: bump required versions + +2009-10-11 13:57:54 +0200 Luca Ognibene <luca.ognibene@gmail.com> + + * gst/rtsp-server/rtsp-client.c: + client: call weak-unref on client->sessions from finalize + Fixes bug #596305 + +2009-10-09 23:08:18 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + media: Fixed crasher where caps got unref'ed too often + +2009-10-09 16:26:30 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * configure.ac: + * pkgconfig/.gitignore: + * pkgconfig/Makefile.am: + * pkgconfig/gst-rtsp-server-uninstalled.pc.in: + Added pkg-config file to use gst-rtsp-server uninstalled + +2009-09-11 13:52:27 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: add some docs + +2009-08-24 13:27:00 +0200 Peter Kjellerstedt <pkj@axis.com> + + * gst/rtsp-server/rtsp-client.c: + rtsp: Use gst_rtsp_watch_send_message(). + Use gst_rtsp_watch_send_message() since the old API which used + gst_rtsp_watch_queue_message() has been deprecated. + +2009-08-05 11:53:56 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.4 === + +2009-08-05 11:44:49 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + Release 0.10.4 + +2009-07-27 19:42:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp: allocate channels in TCP mode + When the client does not provide us with channels in TCP mode, allocate channels + ourselves. + +2009-07-24 12:49:41 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: don't crash when tunnelid is missing + When a clients tries to open an HTTP tunnel but fails to provide a tunnelid, + don't crash but return an error response to the client. + Fixes #589489 + +2009-07-13 11:31:23 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + bindings: update vala bindings with new method + +2009-06-30 21:27:53 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + sessionpool: add function to filter sessions + Add generic function to retrieve/remove sessions. + +2009-06-22 18:57:25 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * configure.ac: + configure: bump core/base requirements to release + +2009-06-18 16:05:18 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: fix indentation + +2009-06-14 23:12:13 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + Unref pipeline and set it to NULL. Set stream's caps to NULL, otherwise we unref it too often. + +2009-06-13 16:05:02 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + set state and remove elements of media in for loop + +2009-06-13 14:38:39 +0200 Sebastian <sebastian@ubuntu.(none)> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + Added gst_rtsp_media_remove_elements function to Vala bindings + +2009-06-13 14:38:20 +0200 Sebastian <sebastian@ubuntu.(none)> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Added gst_rtsp_media_remove_elements function + +2009-06-12 22:22:40 +0200 Sebastian <sebastian@ubuntu.(none)> + + * gst/rtsp-server/rtsp-media.c: + Don't use name for gstrtpbin so we can add multiple instances to the pipeline + +2009-06-12 19:28:04 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Updated Vala bindings + +2009-06-12 18:05:30 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Added vmethod unprepare to GstRTSPMedia + The default implementation sets the state of the pipeline to GST_STATE_NULL + +2009-06-12 17:51:44 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + Made collect_streams function public + +2009-06-12 17:45:29 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + Added vmethod create_pipeline to GstRTSPMediaFactory + The pipeline is created in this method and the GstRTSPMedia's element is added to it + +2009-06-11 11:27:47 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: use g_source_destroy() + We need to use g_source_destroy() because we might have added the source to a + different main context than the default one. + +2009-06-10 00:01:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-params.c: + * gst/rtsp-server/rtsp-params.h: + rtsp: prepare for handling GET/SET_PARAMETER + Add helper functions to handle GET/SET_PARAMETER. Reply with an error when there + is a body now. + Fix return codes of handlers. + +2009-06-04 19:20:26 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: don't leak session pads + +2009-06-04 18:32:15 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: clean up the messages a bit + +2009-06-03 12:13:21 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: warn and skip streams without media + +2009-05-30 14:38:34 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + vala: Fixed typo in header file of RTSPMediaStream + +2009-05-27 11:15:22 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: fix message + Fix a debug message + Make dumping RTCP stats configurable + +2009-05-26 19:20:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: be less verbose and leak less + +2009-05-26 19:05:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: don't leak the destination address + +2009-05-26 19:01:10 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + rtsp: use RTCP to keep the session alive + Use the RTCP rtcp-from stats field to find the associated session and use this + to keep the session alive. + +2009-05-26 17:27:07 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + session: add 5sec to the real session timeout + Allow the session to live 5sec longer before really timing out. This should give + clients some extra time to keep the session active. + +2009-05-26 17:25:59 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: replay OK to GET/SET_PARAMETER + Some clients (vlc) use GET/SET_PARAMETER to keep the TCP session open. Make it + so that we return OK for those requests. + +2009-05-26 11:42:41 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: keep track of active transports + Keep track of which transport is active to avoid closing the connection too + soon. + Remove the destination transport also when going to NULL. + Print some stats about the SDES and other RTCP messages we receive from the + clients. + +2009-05-24 20:00:19 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/.gitignore: + * examples/Makefile.am: + * examples/test-sdp.c: + example: add SDP relay example + +2009-05-24 19:56:45 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: also count active TCP connections + +2009-05-24 19:34:52 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + rtsp: add support for dynamic elements + Add support for dynamic elements. + Don't set live pipelines back to paused. + +2009-05-24 19:33:22 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-sdp.c: + sdp: don't add encoding name when absent in caps + +2009-05-23 16:30:55 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: warn when we can't do RTP-Info + +2009-05-23 16:18:04 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + factory: factor out the stream construction + +2009-05-23 16:17:02 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: only add RTP-Info when we have the info + Only add RTP-Info for a stream when we can get the seqnum and timestamp from the + depayloader. + +2009-05-17 14:04:31 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.3 === + +2009-05-17 13:59:10 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + release: 0.10.3 + - Fixes a bug where it put the wrong verion in pkgconfig + - Link RTP and RTCP sources + +2009-05-15 17:58:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: link the RTP udpsrc to the session manager + Link the RTP udpsrc and the appsrc to the session manager so that they don't + shut down when the client sends a packet to open firewalls. + +2009-05-15 17:10:44 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * pkgconfig/gst-rtsp-server.pc.in: + Don't use hard-coded version number in pkg-config file + +2009-05-11 10:51:47 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + back to development + +=== release 0.10.2 === + +2009-05-11 10:50:31 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + release 0.10.2 + +2009-05-11 10:38:44 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * .gitignore: + * common/m4/.gitignore: + * examples/.gitignore: + * pkgconfig/.gitignore: + add some .gitignore files + +2009-04-29 17:24:46 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: seek to key frames + +2009-04-21 22:44:05 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + media: emit the unprepared signal by id + Emit the unprepared signal by id instead of name and set the media as + reused. + +2009-04-21 22:23:54 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-media.c: + Set pipeline's state to NULL no matter if the media is reusable and emit unprepared signal in gst_rtsp_media_unprepare + +2009-04-18 16:10:59 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * gst/rtsp-server/rtsp-server.c: + Added finalize function to GstRTPSPServer to unref session pool and media mapping + +2009-04-17 21:13:07 +0200 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Updated vala bindings + +2009-04-14 23:38:58 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + server: use appsink and appsrc with the API + Use the appsink/appsrc API instead of the signals for higher + performance. + +2009-04-14 23:38:15 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-ogg.c: + tests: set the payload type correctly + +2009-04-03 22:46:22 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + factory: connect to the unprepare signal + Connect to the unprepare signal for non-reusable media so that we can remove + them from the cache. + +2009-04-03 22:45:57 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: add signal to notify of unprepare + +2009-04-03 22:22:30 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + media: more work on making the media shared + Add a reusable flag to medias, indicating that they can be reused after a state + change to NULL. + Small cleanups. + +2009-04-03 19:47:38 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-readme.c: + examples: mark the example as shared for testing + +2009-04-03 19:44:37 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + client: support shared media + Always perform the state actions even if the target state of the pipeline is + already correct, we still want to add/remove the transports when we are dealing + with shared media. + Keep a counter of the number of active transports for a media so that we can use + this to perform a state change when needed. + Perform a state change of the pipeline only when the first transport was added + or when there are no active transports. + +2009-04-03 09:03:59 +0200 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + client: fix refcounting crasher + Don't need to remove the weak refs in the finalize methods, they are already + removed in the dispose. + Don't register the callback with a DestroyNofity. + +2009-04-01 01:01:46 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Fix rtsp client refcount management in TCP mode. + Don't unref a client ref we never had. Fixes an unref + of an already-free client object after a client + teardown request for me. + +2009-04-01 00:45:17 +0100 Tim-Philipp Müller <tim.muller@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + docs: fix typo in API docs + +2009-03-13 15:57:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + More seeking fixes. + Keep the udp sources in playing even if we go to paused. unlock the sources when + we shut down. + Add some more debug info. + Only seek when we need to. + Keep track of the position when we go to paused. + +2009-03-12 20:32:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add beginnings of seeking. + Parse the Range header and perform a seek on the pipeline for the requested + position. It's disabled currently until I figure out what's going wrong. + +2009-03-12 20:31:22 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + allow pause requests for now. + -- + +2009-03-11 20:03:06 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Remove weak ref on the session in teardown + We need to remove our weakref from the session when we do a teardown because + else we close the TCP connection prematurely. + +2009-03-11 19:38:06 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-session-pool.c: + Do some more session cleanup + Make session timeout kill the TCP connection that currently watches the + session. + Remove the client timeout property. + +2009-03-11 16:45:12 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Add TCP transports + Use appsrc and appsink to send and receive RTP/RTCP packets in the TCP + connection. + +2009-03-11 16:39:20 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-launch.c: + Add example server that takes launch lines + Add an example server that streams any -launch line. + +2009-03-06 19:34:14 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-readme.c: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add support for live streams + Add support for live streams and ranges + Start on handling TCP data transfer. + +2009-03-04 16:33:59 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + Free the pipeline before other things + --- + +2009-03-04 16:33:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Only free the pending tunnel if there is one + -- + +2009-03-04 12:44:01 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + rtsp-server: Add support for tunneling + Add support for tunneling over HTTP. + Use new connection methods to retrieve the url. + Dispatch messages based on the message type instead of blindly + assuming it's always a request. + Keep track of the watch id so that we can remove it later. + Set the media pipeline to NULL before unreffing the pipeline. + +2009-02-19 15:53:50 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Fix for channel -> watch rename in gstreamer + Rename the RTSPChannel to RTSPWatch and remove an unused variable. + +2009-02-18 18:57:31 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Use ASYNC RTSP io + Use the async RTSP channels instead of spawning a new thread for each client. + If a sessionid is specified in a request, fail if we don't have the session. + +2009-02-18 17:49:03 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + Add better debug info + Add some better debug info. + +2009-02-13 20:00:34 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/test-video.c: + Time out sessions + Add support for session timeouts in the example. + +2009-02-13 19:58:17 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + Pass GTimeVal around for performance reasons + Get the current time only once and pass it around so that sessions don't have to + get the current time anymore. + Add experimental support for a GSource that dispatches when the session needs to + be cleaned up. + +2009-02-13 19:56:01 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Add better support for session timeouts + Add a method to request the number of milliseconds when a session will timeout. + +2009-02-13 19:54:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add suport for RTP manager monitoring + Add the first stage in monitoring the rtp manager. + Make sure we don't update the state to something we don't want. + +2009-02-13 19:52:05 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Add support for session keepalive + Get and update the session timeout for all requests. get the session as early as + possible. + +2009-02-13 16:39:36 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Handle media bus messages + Handle media bus messages in a custom mainloop and dispatch them to the + RTSPMedia objects. Let the default implementation handle some common messages. + +2009-02-13 12:57:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + Some more session timeout handling + Move the session header setting code to a central place so that we always add + the timeout parameter too. + Handle timeouts by running the session cleanup code. + Stop media before cleaning up. + +2009-02-10 16:24:13 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Add timeout property + Add a timeout property ot the client and make the other properties into GObject + properties. + +2009-02-10 16:21:17 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + Use getters and setters in property code + Use the getters and setters for the timeout property instead of locking + ourselves. + +2009-02-04 20:13:32 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + Merge branch 'master' of git+ssh://git.collabora.co.uk/git/gst-rtsp-server + +2009-02-04 20:10:39 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Add more timeout stuff + Add method to check if a session is expired. + Add method to perform cleanup on a session pool. + +2009-02-04 19:52:50 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Add beginnings of session timeouts and limits + Add the timeout value to the Session header for unusual timeout values. + Allow us to configure a limit to the amount of active sessions in a pool. Set a + limit on the amount of retry we do after a sessionid collision. + Add properties to the sessionid and the timeout of a session. Keep track of + creation time and last access time for sessions. + +2009-02-04 17:00:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Cleanup of sessions and more + Fix the refcounting of media and sessions in the client. Properly clean up the + session data when the client performs a teardown. + Add Server header to responses. + Allow for multiple uri setups in one session. + Add Range header to the PLAY response and add the range attribute to the SDP + message. + Fix the session pool remove method, it used the wrong key in the hashtable. Also + give the ownership of the sessionid to the session object. + +2009-02-04 09:57:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + Rename a variable + Rename the 'server_port' variable to simply 'port'. + +2009-02-03 19:32:38 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Rework the way we handle transports for streams + Make the media accept an array of transports for the streams that we have + configured for the play/pause requests. + Implement server states for a client and its media. + Require 0.10.22.1 (git HEAD) of gstreamer. + +2009-01-31 19:50:33 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + Drop const from functions dealing with urls + Drop const from GstRTSPUrl stuff because the .h files in gst-plugins-base don't + have the right const in them. + +2009-01-30 17:06:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-sdp.c: + Fix various leaks + Fix some leaks. + +2009-01-30 16:24:10 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + More cleanups + Don't keep a reference to the GstRTSPMedia in the stream. + Free more things when freeing the GstRTSPMedia. + +2009-01-30 14:53:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + More docs and small cleanups + Add some more docs and update the README + Cleanup some method names. + Remove an unneeded idx field in the GstRTSPMediaStream + +2009-01-30 13:24:04 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * docs/README: + * examples/Makefile.am: + * examples/test-readme.c: + Add a README and more example code + Add a README file that contains a small introduction on how to use the server + along with the example code explained in the readme. + +2009-01-30 11:06:31 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-server.c: + Fix some leaks and change default port + Fix some memory leaks by setting the udpsrc elements to the unlocked state after + we finished the initial preroll. If we keep them locked, setting the pipeline to + NULL will not stop and clean up the sources correctly. + Change the default RTSP port to 8554 aka the official alternative RTSP port. + +2009-01-29 18:55:22 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Cleanups to the session object + Remove some unneeded variables in the session state of a stream such as the + owner media and the server transport. + Get the configuration of a media stream in a session based on the media_stream + in the original object instead of our cached index. + Free more data in the finalize method. + +2009-01-29 18:51:02 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Cleanups and reuse media from DESCRIBE + Handle thread create errors. + Rename some internal methods to better match what they actually do. + Handle misconfiguration of session_pool and media_mapping gracefully. + Cache the DESCRIBE media and uri in the client connection and reuse them when + we receive a SETUP request in the same connection for the same uri. + Cleanup the client connection object. + +2009-01-29 17:20:27 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add shared properties to media and factory + Add the shared property to media. + Implement some simple caching in the factory depending on if the media is shared + or not. + +2009-01-29 17:19:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Add a little comment + Add some comment about the content-base header. + +2009-01-29 13:31:27 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * examples/test-mp4.c: + * examples/test-ogg.c: + * examples/test-video.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-sdp.c: + * gst/rtsp-server/rtsp-sdp.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Reorganize things, prepare for media sharing + Added various other test server examples + Move the SDP message generation to a separate helper. + Refactor common code for finding the session. + Add content-base for realplayer compatibility + Clean up request uris before processing for better vlc compatibility. + Move prerolling and pipeline construction to the RTSPMedia object. + Use multiudpsink for future pipeline reuse. + +2009-01-30 11:23:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + Back to development + Back to 0.10.1.1 + +=== release 0.10.1 === + +2009-01-30 11:20:18 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * configure.ac: + Make 0.10.1 release + Release 0.10.1 + +2009-01-29 15:19:01 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * bindings/vala/Makefile.am: + Fix make dist + Add more directories and files to the dist. + +2009-01-24 14:34:35 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/python/Makefile.am: + * bindings/python/rtspserver.override: + Fixed compile error of python bindings + +2009-01-23 21:03:53 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Marked values as nullable accordingly + +2009-01-23 20:31:11 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.excludes: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + Updated Vala bindings + +2009-01-22 18:35:17 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session-pool.h: + Cleanups and doc updates + Add some more documentation and do some minor cleanups here and there. + +2009-01-22 17:58:19 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + More improvements + Rename GstRTSPMediaBin to GstRTSPMedia + Parse the request url into a GstRTSPUri object and pass this object to the + various handlers and methods that require the uri. + +2009-01-22 16:54:07 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/main.c: + Update example + Add some more docs and remove some old code from the example. + +2009-01-22 16:53:16 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + Handle state change failures better + Handle state change failures better when changing the state of the pipeline to + determine the SDP. + +2009-01-22 16:51:08 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + Make element creation more extendible + Add get_element vmethod to the default MediaFactory so that subclasses can just + override that method and still use the default logic for making a MediaBin from + that. + +2009-01-22 15:33:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/main.c: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + * gst/rtsp-server/rtsp-media-mapping.c: + * gst/rtsp-server/rtsp-media-mapping.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + Make the server handle arbitrary pipelines + Make GstMediaFactory an object that can instantiate GstMediaBin objects. + The GstMediaBin object has a handle to a bin with elements and to a list of + GstMediaStream objects that this bin produces. + Add GstMediaMapper that can map url mountpoints to GstMediaFactory objects along + with methods to register and remove those mappings. + Add methods and a property to GstRTSPServer to manage the GstMediaMapper object + used by the server instance. + Modify the example application so that it shows how to create custom pipelines + attached to a specific mount point. + Various misc cleanps. + +2009-01-20 19:47:07 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + Allow setting a custom media factory for a server + +2009-01-20 19:46:21 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Allow setting a custom media factory for a client. + +2009-01-20 19:45:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + Add Makefile entry for the media factory + +2009-01-20 19:44:45 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media-factory.c: + * gst/rtsp-server/rtsp-media-factory.h: + Add media factory to map urls to media pipeline objects. + +2009-01-20 19:43:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + Add comments. Remove unused field + +2009-01-20 19:41:53 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + Allow custom session pools to override the session id allocation algorithms Add some comments. + +2009-01-20 19:40:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session.h: + Add some comments. + +2009-01-20 13:57:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Move the connection code in one place Add some comments + +2009-01-20 13:19:36 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + Make vmethod to create and accept new clients. Add some docs. + +2009-01-19 19:36:23 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + Make more properties configurable in the server. Expose the GIOChannel and GSource better to allow for more customisations. + +2009-01-19 19:34:29 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + Name the parameters more appropriately. + +2009-01-19 19:32:28 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-session-pool.c: + Do some more cleanup of the session pool. + +2009-01-08 16:28:24 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + Check if return value of gst_rtsp_session_get_media is not NULL + +2009-01-08 15:02:42 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/Makefile.am: + Install rtsp-session and rtsp-session-pool headers + +2009-01-08 14:57:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * .gitignore: + * Makefile.am: + * acinclude.m4: + * bindings/python/Makefile.am: + * bindings/python/arg-types.py: + * bindings/python/codegen/Makefile.am: + * bindings/python/codegen/__init__.py: + * bindings/python/codegen/argtypes.py: + * bindings/python/codegen/code-coverage.py: + * bindings/python/codegen/codegen.py: + * bindings/python/codegen/definitions.py: + * bindings/python/codegen/defsparser.py: + * bindings/python/codegen/docextract.py: + * bindings/python/codegen/docgen.py: + * bindings/python/codegen/fileprefix.override: + * bindings/python/codegen/fileprefixmodule.c: + * bindings/python/codegen/h2def.py: + * bindings/python/codegen/mergedefs.py: + * bindings/python/codegen/mkskel.py: + * bindings/python/codegen/override.py: + * bindings/python/codegen/reversewrapper.py: + * bindings/python/codegen/scmexpr.py: + * bindings/python/rtspserver-types.defs: + * bindings/python/rtspserver.defs: + * bindings/python/rtspserver.override: + * bindings/python/rtspservermodule.c: + * configure.ac: + Add python bindings. + +2009-01-08 14:53:47 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * bindings/Makefile.am: + * configure.ac: + Don't go into python dir when requirements for python bindings are missing + +2009-01-08 14:49:57 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * bindings/Makefile.am: + * bindings/vala/Makefile.am: + * configure.ac: + Install Vala bindings if vala is available + +2008-12-12 16:22:02 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server-0.10.deps: + * bindings/vala/gst-rtsp-server-0.10.vapi: + * bindings/vala/packages/gst-rtsp-server-0.10.deps: + * bindings/vala/packages/gst-rtsp-server-0.10.excludes: + * bindings/vala/packages/gst-rtsp-server-0.10.files: + * bindings/vala/packages/gst-rtsp-server-0.10.gi: + * bindings/vala/packages/gst-rtsp-server-0.10.metadata: + * bindings/vala/packages/gst-rtsp-server-0.10.namespace: + Regenerated Vala bindings + +2008-12-08 13:19:40 +0100 Sebastian Pölsterl <sebp@k-d-w.org> + + * bindings/vala/gst-rtsp-server.vapi: + * bindings/vala/packages/gst-rtsp-server.metadata: + Fixed typo in included headers for vala bindings + +2009-01-08 14:42:10 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * Makefile.am: + * configure.ac: + * pkgconfig/Makefile.am: + * pkgconfig/gst-rtsp-server.pc.in: + Added pkgconfig file + +2008-11-30 23:57:26 +0100 Sebastian Pölsterl <marduk@k-d-w.org> + + * bindings/vala/gst-rtsp-server.vapi: + * bindings/vala/packages/gst-rtsp-server.excludes: + * bindings/vala/packages/gst-rtsp-server.gi: + * bindings/vala/packages/gst-rtsp-server.metadata: + Adjusted included headersfor Vala bindings. Ignore rtsp-url-compat.h + +2008-11-30 23:41:20 +0100 Sebastian Pölsterl <marduk@k-d-w.org> + + * bindings/vala/gst-rtsp-server.vapi: + * bindings/vala/packages/gst-rtsp-server.deps: + * bindings/vala/packages/gst-rtsp-server.files: + * bindings/vala/packages/gst-rtsp-server.gi: + * bindings/vala/packages/gst-rtsp-server.metadata: + * bindings/vala/packages/gst-rtsp-server.namespace: + Added Vala bindings + +2008-10-25 23:36:16 +0200 Alessandro Decina <alessandro.d@gmail.com> + + * gst/rtsp-server/rtsp-session.c: + Change an obviously wrong return FALSE to return NULL; (cherry picked from commit 56d4fb48030db3ae45f3f0e60b29b36f3134322b) + +2008-11-13 19:43:10 +0100 Sebastian Pölsterl <sebp@ubuntu.(none)> + + * examples/Makefile.am: + * gst/rtsp-server/Makefile.am: + Put GStreamer version in library name + +2009-01-08 13:51:26 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * examples/Makefile.am: + * gst/rtsp-server/Makefile.am: + Fix some issues to pass distcheck + +2009-01-08 13:41:33 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * gst/rtsp-server/rtsp-server.c: + Added port property to GstRTSPServer class. + +2009-01-08 13:18:55 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * Makefile.am: + * autogen.sh: + * configure.ac: + * examples/Makefile.am: + * examples/main.c: + * gst/Makefile.am: + * gst/rtsp-server/Makefile.am: + * gst/rtsp-server/rtsp-client.c: + * gst/rtsp-server/rtsp-client.h: + * gst/rtsp-server/rtsp-media.c: + * gst/rtsp-server/rtsp-media.h: + * gst/rtsp-server/rtsp-server.c: + * gst/rtsp-server/rtsp-server.h: + * gst/rtsp-server/rtsp-session-pool.c: + * gst/rtsp-server/rtsp-session-pool.h: + * gst/rtsp-server/rtsp-session.c: + * gst/rtsp-server/rtsp-session.h: + * src/Makefile.am: + Split in library and example program + +2008-11-10 20:59:35 +0100 Sebastian Pölsterl <sebp@ubuntu.(none)> + + * src/rtsp-client.h: + Removed obsolete variable + +2008-11-10 21:03:15 +0100 Sebastian Pölsterl <sebp@ubuntu.(none)> + + * src/rtsp-client.c: + * src/rtsp-client.h: + Removed pipeline variable GstRTSPClient, because it's only used in one function + +2009-01-08 11:22:58 +0100 Wim Taymans <wim.taymans@collabora.co.uk> + + * src/rtsp-media.c: + Set the payload types for the different payloaders. Maybe this shoulde be done automatically instead. + +2008-10-23 12:23:27 +0200 Wim Taymans <wim@metal.(none)> + + * src/rtsp-session.c: + Initialize some more vars. + +2008-10-23 12:14:55 +0200 Wim Taymans <wim@metal.(none)> + + * src/rtsp-session.c: + Initialize variable to avoid compiler warning. + +2008-10-09 13:30:47 +0100 Simon McVittie <simon.mcvittie@collabora.co.uk> + + * .gitignore: + Add a reasonable generic .gitignore + diff --git a/subprojects/gst-rtsp-server/NEWS b/subprojects/gst-rtsp-server/NEWS new file mode 100644 index 0000000000..0e581c39b8 --- /dev/null +++ b/subprojects/gst-rtsp-server/NEWS @@ -0,0 +1,299 @@ +GStreamer 1.20 Release Notes + +GStreamer 1.20 has not been released yet. It is scheduled for release +around October/November 2021. + +1.19.x is the unstable development version that is being developed in +the git main branch and which will eventually result in 1.20, and 1.19.2 +is the current development release in that series + +It is expected that feature freeze will be in early October 2021, +followed by one or two 1.19.9x pre-releases and the new 1.20 stable +release around October/November 2021. + +1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12, +1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series. + +See https://gstreamer.freedesktop.org/releases/1.20/ for the latest +version of this document. + +Last updated: Wednesday 22 September 2021, 18:00 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 + +- this section will be completed in due course + +Major new features and changes + +Noteworthy new features and API + +- this section will be filled in in due course + +New elements + +- this section will be filled in in due course + +New element features and additions + +- this section will be filled in in due course + +Plugin and library moves + +- this section will be filled in in due course + +- There were no plugin moves or library moves in this cycle. + +Plugin removals + +The following elements or plugins have been removed: + +- this section will be filled in in due course + +Miscellaneous API additions + +- this section will be filled in in due course + +Miscellaneous performance, latency and memory optimisations + +- this section will be filled in in due course + +Miscellaneous other changes and enhancements + +- this section will be filled in in due course + +Tracing framework and debugging improvements + +- this section will be filled in in due course + +Tools + +- this section will be filled in in due course + +GStreamer RTSP server + +- this section will be filled in in due course + +GStreamer VAAPI + +- this section will be filled in in due course + +GStreamer OMX + +- this section will be filled in in due course + +GStreamer Editing Services and NLE + +- this section will be filled in in due course + +GStreamer validate + +- this section will be filled in in due course + +GStreamer Python Bindings + +- this section will be filled in in due course + +GStreamer C# Bindings + +- this section will be filled in in due course + +GStreamer Rust Bindings and Rust Plugins + +The GStreamer Rust bindings are released separately with a different +release cadence that’s tied to gtk-rs, but the latest release has +already been updated for the upcoming new GStreamer 1.20 API. + +gst-plugins-rs, the module containing GStreamer plugins written in Rust, +has also seen lots of activity with many new elements and plugins. + +What follows is a list of elements and plugins available in +gst-plugins-rs, so people don’t miss out on all those potentially useful +elements that have no C equivalent. + +- FIXME: add new elements + +Rust audio plugins + +- audiornnoise: New element for audio denoising which implements the + noise removal algorithm of the Xiph RNNoise library, in Rust +- rsaudioecho: Port of the audioecho element from gst-plugins-good + rsaudioloudnorm: Live audio loudness normalization element based on + the FFmpeg af_loudnorm filter +- claxondec: FLAC lossless audio codec decoder element based on the + pure-Rust claxon implementation +- csoundfilter: Audio filter that can use any filter defined via the + Csound audio programming language +- lewtondec: Vorbis audio decoder element based on the pure-Rust + lewton implementation + +Rust video plugins + +- cdgdec/cdgparse: Decoder and parser for the CD+G video codec based + on a pure-Rust CD+G implementation, used for example by karaoke CDs +- cea608overlay: CEA-608 Closed Captions overlay element +- cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT + subtitles) converter +- tttocea608: CEA-608 Closed Captions from timed-text converter +- mccenc/mccparse: MacCaption Closed Caption format encoder and parser +- sccenc/sccparse: Scenarist Closed Caption format encoder and parser +- dav1dec: AV1 video decoder based on the dav1d decoder implementation + by the VLC project +- rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e + encoder implementation +- rsflvdemux: Alternative to the flvdemux FLV demuxer element from + gst-plugins-good, not feature-equivalent yet +- rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust + implementations by the image-rs project + +Rust text plugins + +- textwrap: Element for line-wrapping timed text (e.g. subtitles) for + better screen-fitting, including hyphenation support for some + languages + +Rust network plugins + +- reqwesthttpsrc: HTTP(S) source element based on the Rust + reqwest/hyper HTTP implementations and almost feature-equivalent + with the main GStreamer HTTP source souphttpsrc +- s3src/s3sink: Source/sink element for the Amazon S3 cloud storage +- awstranscriber: Live audio to timed text transcription element using + the Amazon AWS Transcribe API + +Generic Rust plugins + +- sodiumencrypter/sodiumdecrypter: Encryption/decryption element based + on libsodium/NaCl +- togglerecord: Recording element that allows to pause/resume + recordings easily and considers keyframe boundaries +- fallbackswitch/fallbacksrc: Elements for handling potentially + failing (network) sources, restarting them on errors/timeout and + showing a fallback stream instead +- threadshare: Set of elements that provide alternatives for various + existing GStreamer elements but allow to share the streaming threads + between each other to reduce the number of threads +- rsfilesrc/rsfilesink: File source/sink elements as replacements for + the existing filesrc/filesink elements + +Build and Dependencies + +- this section will be filled in in due course + +gst-build + +- this section will be filled in in due course + +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 + +- this section will be filled in in due course + +macOS / iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Windows MSI installer + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Android + +- this section will be filled in in due course + +Platform-specific changes and improvements + +Android + +- this section will be filled in in due course + +macOS and iOS + +- this section will be filled in in due course + +Windows + +- this section will be filled in in due course + +Linux + +- this section will be filled in in due course + +Documentation improvements + +- this section will be filled in in due course + +Possibly Breaking Changes + +- this section will be filled in in due course +- MPEG-TS SCTE-35 API changes (FIXME: flesh out) +- gst_parse_launch() and friends now error out on non-existing + properties on top-level bins where they would silently fail and + ignore those before. + +Known Issues + +- this section will be filled in in due course + +- There are a couple of known WebRTC-related regressions/blockers: + + - webrtc: DTLS setup with Chrome is broken + - webrtcbin: First keyframe is usually lost + +Contributors + +- this section will be filled in in due course + +… and many others who have contributed bug reports, translations, sent +suggestions or helped testing. + +Stable 1.20 branch + +After the 1.20.0 release there will be several 1.20.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.20.x bug-fix releases will be made from +the git 1.20 branch, which will be a stable branch. + +1.20.0 + +1.20.0 is scheduled to be released around October/November 2021. + +Schedule for 1.22 + +Our next major feature release will be 1.22, and 1.21 will be the +unstable development version leading up to the stable 1.22 release. The +development of 1.21/1.22 will happen in the git main branch. + +The plan for the 1.22 development cycle is yet to be confirmed. + +1.22 will be backwards-compatible to the stable 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 … + +License: CC BY-SA 4.0 diff --git a/subprojects/gst-rtsp-server/README b/subprojects/gst-rtsp-server/README new file mode 100644 index 0000000000..2d5f7d06da --- /dev/null +++ b/subprojects/gst-rtsp-server/README @@ -0,0 +1,4 @@ +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. diff --git a/subprojects/gst-rtsp-server/RELEASE b/subprojects/gst-rtsp-server/RELEASE new file mode 100644 index 0000000000..97a4fa313e --- /dev/null +++ b/subprojects/gst-rtsp-server/RELEASE @@ -0,0 +1,96 @@ +This is GStreamer gst-rtsp-server 1.19.2. + +GStreamer 1.19 is the development branch leading up to the next major +stable version which will be 1.20. + +The 1.19 development series adds new features on top of the 1.18 series and is +part of the API and ABI-stable 1.x release series of the GStreamer multimedia +framework. + +Full release notes will one day be found at: + + https://gstreamer.freedesktop.org/releases/1.20/ + +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. + + - gst-omx: hardware-accelerated video decoding and encoding, primarily for + embedded Linux systems that provide an OpenMax + implementation layer such as the Raspberry Pi. + + - gst-rtsp-server: library to serve files or streaming pipelines via RTSP + + - gst-editing-services: library an plugins for non-linear editing + +==== 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/ + +==== Homepage ==== + +The project's website is https://gstreamer.freedesktop.org/ + +==== Support and Bugs ==== + +We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org +for bug reports and feature requests: + + https://gitlab.freedesktop.org/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 subscribe to and send questions to the +gstreamer-devel mailing list (see below for details). + +There is also a #gstreamer IRC channel on the Freenode IRC network. + +==== Developers ==== + +GStreamer source code repositories can be found on GitLab on freedesktop.org: + + https://gitlab.freedesktop.org/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 +subscribe to the gstreamer-devel list: + + https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel diff --git a/subprojects/gst-rtsp-server/REQUIREMENTS b/subprojects/gst-rtsp-server/REQUIREMENTS new file mode 100644 index 0000000000..14e4172e5e --- /dev/null +++ b/subprojects/gst-rtsp-server/REQUIREMENTS @@ -0,0 +1,3 @@ +You need to have GStreamer. You can use an installed version of +GStreamer or from its build dir. + diff --git a/subprojects/gst-rtsp-server/TODO b/subprojects/gst-rtsp-server/TODO new file mode 100644 index 0000000000..53d7d1c0f5 --- /dev/null +++ b/subprojects/gst-rtsp-server/TODO @@ -0,0 +1,3 @@ + + - use a config file to configure the server + - error recovery diff --git a/subprojects/gst-rtsp-server/docs/README b/subprojects/gst-rtsp-server/docs/README new file mode 100644 index 0000000000..b4f0c358d8 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/README @@ -0,0 +1,498 @@ +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. + diff --git a/subprojects/gst-rtsp-server/docs/design/gst-rtp-server-design b/subprojects/gst-rtsp-server/docs/design/gst-rtp-server-design new file mode 100644 index 0000000000..7da8283078 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/design/gst-rtp-server-design @@ -0,0 +1,35 @@ +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. diff --git a/subprojects/gst-rtsp-server/docs/gst_plugins_cache.json b/subprojects/gst-rtsp-server/docs/gst_plugins_cache.json new file mode 100644 index 0000000000..bbeb067a9b --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/gst_plugins_cache.json @@ -0,0 +1,503 @@ +{ + "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 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 + }, + "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/1.19.2", + "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" + } +}
\ No newline at end of file diff --git a/subprojects/gst-rtsp-server/docs/index.md b/subprojects/gst-rtsp-server/docs/index.md new file mode 100644 index 0000000000..b38638be78 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/index.md @@ -0,0 +1 @@ +# GStreamer RTSP Server diff --git a/subprojects/gst-rtsp-server/docs/meson.build b/subprojects/gst-rtsp-server/docs/meson.build new file mode 100644 index 0000000000..a9953d854c --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/meson.build @@ -0,0 +1,99 @@ +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 + +required_hotdoc_extensions = ['gi-extension', 'gst-extension'] +if gst_dep.type_name() == 'internal' + gst_proj = subproject('gstreamer') + plugins_cache_generator = gst_proj.get_variable('plugins_cache_generator') +else + required_hotdoc_extensions += ['gst-extension'] + plugins_cache_generator = find_program(join_paths(gst_dep.get_pkgconfig_variable('libexecdir'), 'gstreamer-' + api_version, 'gst-plugins-doc-cache-generator'), + required: false) +endif + +plugins_cache = join_paths(meson.current_source_dir(), 'gst_plugins_cache.json') +if plugins.length() == 0 + message('All rtsp-server plugins have been disabled') +elif plugins_cache_generator.found() + 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 + +hotdoc_p = find_program('hotdoc', required: get_option('doc')) +if not hotdoc_p.found() + message('Hotdoc not found, not building the documentation') + subdir_done() +endif + +hotdoc_req = '>= 0.11.0' +hotdoc_version = run_command(hotdoc_p, '--version').stdout() +if not hotdoc_version.version_compare(hotdoc_req) + if get_option('doc').enabled() + error('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version)) + else + message('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version)) + subdir_done() + endif +endif + +hotdoc = import('hotdoc') +foreach extension: required_hotdoc_extensions + if not hotdoc.has_extensions(extension) + if get_option('doc').enabled() + error('Documentation enabled but @0@ missing'.format(extension)) + endif + + message('@0@ extension not found, not building documentation'.format(extension)) + subdir_done() + endif +endforeach + +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 + +build_hotdoc = true +hotdoc = import('hotdoc') + +libs_doc = [hotdoc.generate_doc('gst-rtsp-server', + project_version: api_version, + gi_c_sources: ['../gst/rtsp-server/*.[hc]'], + gi_sources: rtsp_server_gir[0].full_path(), + sitemap: 'sitemap.txt', + index: 'index.md', + gi_index: 'index.md', + gi_smart_index: true, + gi_order_generated_subpages: true, +)] + +plugins_doc = [hotdoc.generate_doc('rtspclientsink', + project_version: api_version, + sitemap: 'plugin-sitemap.txt', + index: 'plugin-index.md', + gst_index: 'plugin-index.md', + gst_c_sources: ['../gst/rtsp-sink/*.[ch]'], + gst_dl_sources: [rtspsink.full_path()], + gst_smart_index: true, + dependencies: gst_rtsp_server_deps + [rtspsink], + gst_cache_file: plugins_cache, + gst_plugin_name: 'rtspclientsink', +)] +doc = libs_doc[0] diff --git a/subprojects/gst-rtsp-server/docs/plugin-index.md b/subprojects/gst-rtsp-server/docs/plugin-index.md new file mode 100644 index 0000000000..091e6bd049 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/plugin-index.md @@ -0,0 +1 @@ +# rtspclientsink diff --git a/subprojects/gst-rtsp-server/docs/plugin-sitemap.txt b/subprojects/gst-rtsp-server/docs/plugin-sitemap.txt new file mode 100644 index 0000000000..058a2713a4 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/plugin-sitemap.txt @@ -0,0 +1 @@ +gst-index diff --git a/subprojects/gst-rtsp-server/docs/sitemap.md b/subprojects/gst-rtsp-server/docs/sitemap.md new file mode 100644 index 0000000000..09c1f9c157 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/sitemap.md @@ -0,0 +1,2 @@ +gi-index + gst-index diff --git a/subprojects/gst-rtsp-server/docs/sitemap.txt b/subprojects/gst-rtsp-server/docs/sitemap.txt new file mode 100644 index 0000000000..4f91fcd8a3 --- /dev/null +++ b/subprojects/gst-rtsp-server/docs/sitemap.txt @@ -0,0 +1 @@ +gi-index diff --git a/subprojects/gst-rtsp-server/examples/meson.build b/subprojects/gst-rtsp-server/examples/meson.build new file mode 100644 index 0000000000..9352f4ea58 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/meson.build @@ -0,0 +1,40 @@ +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-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 : [glib_dep, 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 : [glib_dep, gst_dep, gstnet_dep, gst_rtsp_server_dep, cgroup_dep], + install: false) +endif diff --git a/subprojects/gst-rtsp-server/examples/test-appsrc.c b/subprojects/gst-rtsp-server/examples/test-appsrc.c new file mode 100644 index 0000000000..c8c2d0b813 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-appsrc.c @@ -0,0 +1,140 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-appsrc2.c b/subprojects/gst-rtsp-server/examples/test-appsrc2.c new file mode 100644 index 0000000000..da2513ae8f --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-appsrc2.c @@ -0,0 +1,196 @@ +/* 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); + } + + /* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-auth-digest.c b/subprojects/gst-rtsp-server/examples/test-auth-digest.c new file mode 100644 index 0000000000..e0e8915cdc --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-auth-digest.c @@ -0,0 +1,229 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-auth.c b/subprojects/gst-rtsp-server/examples/test-auth.c new file mode 100644 index 0000000000..0087d8a81a --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-auth.c @@ -0,0 +1,190 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-cgroups.c b/subprojects/gst-rtsp-server/examples/test-cgroups.c new file mode 100644 index 0000000000..600fa3c16f --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-cgroups.c @@ -0,0 +1,276 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-launch.c b/subprojects/gst-rtsp-server/examples/test-launch.c new file mode 100644 index 0000000000..b21df8ed4c --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-launch.c @@ -0,0 +1,93 @@ +/* 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_DISABLE_RTCP FALSE + +static char *port = (char *) DEFAULT_RTSP_PORT; +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"}, + {"disable-rtcp", '\0', 0, G_OPTION_ARG_NONE, &disable_rtcp, + "Whether RTCP should be disabled (default false)", NULL}, + {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: \"( 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); + + /* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-mp4.c b/subprojects/gst-rtsp-server/examples/test-mp4.c new file mode 100644 index 0000000000..2ebe98c3ad --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-mp4.c @@ -0,0 +1,177 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-multicast.c b/subprojects/gst-rtsp-server/examples/test-multicast.c new file mode 100644 index 0000000000..8ac821eb15 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-multicast.c @@ -0,0 +1,104 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-multicast2.c b/subprojects/gst-rtsp-server/examples/test-multicast2.c new file mode 100644 index 0000000000..ef9e3d6cc8 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-multicast2.c @@ -0,0 +1,125 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-netclock-client.c b/subprojects/gst-rtsp-server/examples/test-netclock-client.c new file mode 100644 index 0000000000..e862ca9b00 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-netclock-client.c @@ -0,0 +1,147 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-netclock.c b/subprojects/gst-rtsp-server/examples/test-netclock.c new file mode 100644 index 0000000000..c1bca63bae --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-netclock.c @@ -0,0 +1,123 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-ogg.c b/subprojects/gst-rtsp-server/examples/test-ogg.c new file mode 100644 index 0000000000..dc052b42bb --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-ogg.c @@ -0,0 +1,93 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-onvif-backchannel.c b/subprojects/gst-rtsp-server/examples/test-onvif-backchannel.c new file mode 100644 index 0000000000..906c10bbee --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-onvif-backchannel.c @@ -0,0 +1,71 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-onvif-client.c b/subprojects/gst-rtsp-server/examples/test-onvif-client.c new file mode 100644 index 0000000000..3a1b7003b9 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-onvif-client.c @@ -0,0 +1,729 @@ +/* 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> + +typedef struct +{ + gchar *range; + gdouble speed; + gchar *frames; + gchar *rate_control; + gboolean reverse; +} SeekParameters; + +typedef struct +{ + GstElement *src; + 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 +pad_added_cb (GstElement * src, GstPad * srcpad, GstElement * peer) +{ + GstPad *sinkpad = gst_element_get_static_pad (peer, "sink"); + + gst_pad_link (srcpad, sinkpad); + + gst_object_unref (sinkpad); +} + +static gboolean +setup (Context * ctx) +{ + GstElement *onvifparse, *queue, *vdepay, *vdec, *vconv, *toverlay, *tee, + *vqueue; + gboolean ret = FALSE; + + MAKE_AND_ADD (ctx->src, ctx->pipe, "rtspsrc", done, NULL); + MAKE_AND_ADD (queue, ctx->pipe, "queue", done, NULL); + MAKE_AND_ADD (onvifparse, ctx->pipe, "rtponvifparse", done, NULL); + MAKE_AND_ADD (vdepay, ctx->pipe, "rtph264depay", done, NULL); + MAKE_AND_ADD (vdec, ctx->pipe, "avdec_h264", done, NULL); + MAKE_AND_ADD (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, "xvimagesink", 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), queue); + + if (!gst_element_link_many (queue, onvifparse, vdepay, vdec, 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; +} + +static GstClockTime +get_current_position (Context * ctx, gboolean reverse) +{ + GstSample *sample; + GstBuffer *buffer; + GstClockTime ret; + + g_object_get (ctx->sink, "last-sample", &sample, NULL); + + 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-onvif-server.c b/subprojects/gst-rtsp-server/examples/test-onvif-server.c new file mode 100644 index 0000000000..bcd48afdfb --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-onvif-server.c @@ -0,0 +1,654 @@ +/* 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 ("GstNtpOffset", + "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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-onvif-server.h b/subprojects/gst-rtsp-server/examples/test-onvif-server.h new file mode 100644 index 0000000000..a3c98d9aec --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-onvif-server.h @@ -0,0 +1,32 @@ +/* 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 diff --git a/subprojects/gst-rtsp-server/examples/test-readme.c b/subprojects/gst-rtsp-server/examples/test-readme.c new file mode 100644 index 0000000000..2e2caa6766 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-readme.c @@ -0,0 +1,67 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-record-auth.c b/subprojects/gst-rtsp-server/examples/test-record-auth.c new file mode 100644 index 0000000000..8c6511763d --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-record-auth.c @@ -0,0 +1,179 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-record.c b/subprojects/gst-rtsp-server/examples/test-record.c new file mode 100644 index 0000000000..47b8fd92c3 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-record.c @@ -0,0 +1,101 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-replay-server.c b/subprojects/gst-rtsp-server/examples/test-replay-server.c new file mode 100644 index 0000000000..69f1afe582 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-replay-server.c @@ -0,0 +1,931 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-replay-server.h b/subprojects/gst-rtsp-server/examples/test-replay-server.h new file mode 100644 index 0000000000..1204775fbe --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-replay-server.h @@ -0,0 +1,36 @@ +/* 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 diff --git a/subprojects/gst-rtsp-server/examples/test-sdp.c b/subprojects/gst-rtsp-server/examples/test-sdp.c new file mode 100644 index 0000000000..894d9bd9d3 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-sdp.c @@ -0,0 +1,98 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/examples/test-uri.c b/subprojects/gst-rtsp-server/examples/test-uri.c new file mode 100644 index 0000000000..784bf9ad6b --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-uri.c @@ -0,0 +1,157 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-video-disconnect.c b/subprojects/gst-rtsp-server/examples/test-video-disconnect.c new file mode 100644 index 0000000000..809a363882 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-video-disconnect.c @@ -0,0 +1,222 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-video-rtx.c b/subprojects/gst-rtsp-server/examples/test-video-rtx.c new file mode 100644 index 0000000000..f804b95c68 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-video-rtx.c @@ -0,0 +1,100 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/examples/test-video.c b/subprojects/gst-rtsp-server/examples/test-video.c new file mode 100644 index 0000000000..087da08ce2 --- /dev/null +++ b/subprojects/gst-rtsp-server/examples/test-video.c @@ -0,0 +1,177 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/gst-rtsp-server.doap b/subprojects/gst-rtsp-server/gst-rtsp-server.doap new file mode 100644 index 0000000000..6d323ac90d --- /dev/null +++ b/subprojects/gst-rtsp-server/gst-rtsp-server.doap @@ -0,0 +1,500 @@ +<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.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> diff --git a/subprojects/gst-rtsp-server/gst/meson.build b/subprojects/gst-rtsp-server/gst/meson.build new file mode 100644 index 0000000000..59cb3e4750 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/meson.build @@ -0,0 +1,5 @@ +subdir('rtsp-server') + +if not get_option('rtspclientsink').disabled() + subdir('rtsp-sink') +endif diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build b/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build new file mode 100644 index 0000000000..24d7c39adb --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/meson.build @@ -0,0 +1,99 @@ +rtsp_server_sources = [ + '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 = [ + '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_rtsp_server = library('gstrtspserver-@0@'.format(api_version), + rtsp_server_sources, + include_directories : rtspserver_incs, + c_args: rtspserver_args + ['-DBUILDING_GST_RTSP_SERVER'], + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + install : true, + dependencies : gst_rtsp_server_deps) + +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'] + rtsp_server_gir = gnome.generate_gir(gst_rtsp_server, + sources : rtsp_server_headers + rtsp_server_sources, + namespace : 'GstRtspServer', + nsversion : api_version, + identifier_prefix : 'Gst', + symbol_prefix : 'gst', + export_packages : 'gstreamer-rtsp-server-' + api_version, + install : true, + extra_args : gst_gir_extra_args, + includes : ['Gst-1.0', 'GstRtsp-1.0', 'GstNet-1.0'], + dependencies : gst_rtsp_server_deps, + ) + rtsp_server_gen_sources += [rtsp_server_gir] +endif + +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]) + +meson.override_dependency('gstreamer-rtsp-server-1.0', gst_rtsp_server_dep) diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.c new file mode 100644 index 0000000000..da3e82b40c --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.c @@ -0,0 +1,753 @@ +/* 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_slice_dup (GstRTSPAddress, addr); + /* 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_slice_free (GstRTSPAddress, 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_slice_free (AddrRange, 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_slice_new0 (AddrRange); + + 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_slice_free (AddrRange, 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_slice_dup (AddrRange, range); + 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_slice_dup (AddrRange, range); + /* 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_slice_dup (AddrRange, range); + 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_slice_dup (AddrRange, range); + 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_slice_new0 (GstRTSPAddress); + 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_slice_new0 (GstRTSPAddress); + 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.h new file mode 100644 index 0000000000..997cfd1d77 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-address-pool.h @@ -0,0 +1,205 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.c new file mode 100644 index 0000000000..b6286e173c --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.c @@ -0,0 +1,1264 @@ +/* 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: + * + * 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): 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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.h new file mode 100644 index 0000000000..05a3e5a455 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-auth.h @@ -0,0 +1,230 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.c new file mode 100644 index 0000000000..e5a62c0cd9 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.c @@ -0,0 +1,5404 @@ +/* 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_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; + 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); + + 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, ctx->uri))) + 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; + ctx->media = media; + GST_INFO ("reusing cached media %p for path %s", media, priv->path); + } + + 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_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_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + g_object_unref (factory); + ctx->factory = NULL; + return NULL; + } +no_thread: + { + GST_ERROR ("client %p: can't create thread", client); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + 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_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + 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_get_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); + + /* 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_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +not_found: + { + GST_ERROR ("client %p: no media for uri", client); + send_generic_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_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_response (client, sig_result, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + 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_response (client, sig_result, ctx); + return FALSE; + } +bad_request: + { + GST_ERROR ("client %p: bad request", client); + send_generic_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_response (client, sig_result, ctx); + return FALSE; + } +bad_request: + { + GST_ERROR ("client %p: bad request", client); + send_generic_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_get_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_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_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +not_found: + { + GST_ERROR ("client %p: no media for uri", client); + send_generic_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_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_response (client, sig_result, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +invalid_state: + { + GST_ERROR ("client %p: not PLAYING or RECORDING", client); + send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, + ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +not_supported: + { + GST_ERROR ("client %p: pausing not supported", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + gst_rtsp_media_unlock (media); + 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_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_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_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_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +not_found: + { + GST_ERROR ("client %p: media not found", client); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + return FALSE; + } +no_aggregate: + { + GST_ERROR ("client %p: no aggregate path %s", client, path); + send_generic_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_response (client, sig_result, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +invalid_state: + { + GST_ERROR ("client %p: not PLAYING or READY", client); + send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, + ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +pipeline_error: + { + GST_ERROR ("client %p: failed to configure the pipeline", client); + send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, + ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +unsuspend_failed: + { + GST_ERROR ("client %p: unsuspend failed", client); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +invalid_mode: + { + GST_ERROR ("client %p: seek failed", client); + send_generic_response (client, code, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +unsupported_mode: + { + GST_ERROR ("client %p: media does not support PLAY", client); + send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +get_rates_error: + { + GST_ERROR ("client %p: failed obtaining rate and applied_rate", client); + send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +adjust_play_response_failed: + { + GST_ERROR ("client %p: failed to adjust play response", client); + send_generic_response (client, code, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + return FALSE; + } +rtp_info_error: + { + GST_ERROR ("client %p: failed to add RTP-Info", client); + send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx); + gst_rtsp_media_unlock (media); + g_object_unref (media); + 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_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_lock (media); + 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_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +no_transport: + { + GST_ERROR ("client %p: no transport", client); + send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); + goto cleanup_path; + } +no_pool: + { + GST_ERROR ("client %p: no session pool configured", client); + send_generic_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_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_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_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_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_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_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_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); + goto cleanup_transport; + } +unsupported_client_transport: + { + GST_ERROR ("client %p: unsupported client transport", client); + send_generic_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_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_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx); + goto cleanup_transport; + } +keymgmt_error: + { + GST_ERROR ("client %p: keymgmt error", client); + send_generic_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; + + gst_rtsp_media_lock (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_response (client, sig_result, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +no_mount_points: + { + GST_ERROR ("client %p: no mount points configured", client); + send_generic_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_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_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_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; + gst_rtsp_media_lock (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_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +no_mount_points: + { + GST_ERROR ("client %p: no mount points configured", client); + send_generic_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_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_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +no_message: + { + GST_ERROR ("client %p: can't find SDP message", client); + send_generic_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_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_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_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_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_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +not_found: + { + GST_ERROR ("client %p: media not found", client); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + return FALSE; + } +no_aggregate: + { + GST_ERROR ("client %p: no aggregate path %s", client, path); + send_generic_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_response (client, sig_result, ctx); + return FALSE; + } +unsupported_mode: + { + GST_ERROR ("client %p: media does not support RECORD", client); + send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx); + return FALSE; + } +invalid_state: + { + GST_ERROR ("client %p: not PLAYING or READY", client); + send_generic_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_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_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_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_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_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + goto done; + } +bad_request: + { + GST_ERROR ("client %p: bad request", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + goto done; + } +no_pool: + { + GST_ERROR ("client %p: no pool configured", client); + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + goto done; + } +session_not_found: + { + GST_ERROR ("client %p: session not found", client); + send_generic_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_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): a #GstRTSPClientSendFunc + * @user_data: (closure): 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): a #GstRTSPClientSendMessagesFunc + * @user_data: (closure): 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_response (client, GST_RTSP_STS_REQUEST_ENTITY_TOO_LARGE, ctx); + goto done; + } + if (result == GST_RTSP_EPARSE) { + send_generic_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); + + 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. */ + g_mutex_lock (&priv->lock); + + /* 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.h new file mode 100644 index 0000000000..604a042399 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.h @@ -0,0 +1,294 @@ +/* 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); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE-18]; +}; + +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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.c new file mode 100644 index 0000000000..7c88153d68 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.c @@ -0,0 +1,95 @@ +/* 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 (¤t_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 (¤t_context); + l = g_slist_prepend (l, ctx); + g_private_set (¤t_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 (¤t_context); + + g_return_if_fail (l != NULL); + g_return_if_fail (l->data == ctx); + + l = g_slist_delete_link (l, l); + g_private_set (¤t_context, l); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.h new file mode 100644 index 0000000000..c4567f9b09 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-context.h @@ -0,0 +1,97 @@ +/* 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); + + +G_END_DECLS + +#endif /* __GST_RTSP_CONTEXT_H__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.c new file mode 100644 index 0000000000..c297ab63ee --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.c @@ -0,0 +1,352 @@ +/* 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)); + + 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: 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.h new file mode 100644 index 0000000000..455e7c5713 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-latency-bin.h @@ -0,0 +1,59 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.c new file mode 100644 index 0000000000..50089fc9cb --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.c @@ -0,0 +1,646 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.h new file mode 100644 index 0000000000..2980670cd5 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.h @@ -0,0 +1,91 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c new file mode 100644 index 0000000000..5dd9dc0a1b --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.c @@ -0,0 +1,2058 @@ +/* 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; + 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_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_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)); + + 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->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_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_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_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_slice_new (GWeakRef); + + g_weak_ref_init (ref, obj); + return ref; +} + +static void +weak_ref_free (GWeakRef * ref) +{ + g_weak_ref_clear (ref); + g_slice_free (GWeakRef, 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 (). + * + * Returns: (transfer full): 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; + 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); + } else + media = NULL; + + if (media == NULL) { + /* 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); + } else + media = NULL; + + if (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): 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_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; + 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; + 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_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): 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h new file mode 100644 index 0000000000..8e847fda33 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory.h @@ -0,0 +1,284 @@ +/* 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_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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c new file mode 100644 index 0000000000..f2d498ac2f --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c @@ -0,0 +1,5195 @@ +/* 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> + +#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; + 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_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_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_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)); + + 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_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); + + 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->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); + + 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_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_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; + + 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. + * + * 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_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; +} + +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_unlock (&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; + + appsink = gst_element_factory_make ("appsink", NULL); + appsrc = gst_element_factory_make ("appsrc", NULL); + + if (GST_PAD_IS_SINK (pad)) { + srcpad = gst_element_get_static_pad (appsrc, "src"); + + gst_bin_add (GST_BIN (priv->element), appsrc); + + gst_pad_link (srcpad, pad); + gst_object_unref (srcpad); + + streampad = gst_element_get_static_pad (appsink, "sink"); + + priv->pending_pipeline_elements = + g_list_prepend (priv->pending_pipeline_elements, appsink); + } else { + sinkpad = gst_element_get_static_pad (appsink, "sink"); + + gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + + streampad = gst_element_get_static_pad (appsrc, "src"); + + 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_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; +} + +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) { + /* diffrent rate or applied_rate, weird */ + g_assert (FALSE); + result = FALSE; + break; + } + } + } else { + /* complete stream withot rate and applied_rate, weird */ + g_assert (FALSE); + 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) +{ + 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 +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, ¤t_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) +{ + *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_active_sender (GstRTSPStream * stream, guint * active_streams) +{ + if (gst_rtsp_stream_is_complete (stream) + && gst_rtsp_stream_is_sender (stream)) + (*active_streams)++; +} + +static guint +nbr_active_sender_streams (GstRTSPMedia * media) +{ + guint ret = 0; + + g_ptr_array_foreach (media->priv->streams, + (GFunc) stream_collect_active_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 n_active_sender_streams; + guint expected_nbr_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); + n_active_sender_streams = nbr_active_sender_streams (media); + expected_nbr_blocking_msg = n_active_sender_streams; + GST_DEBUG_OBJECT (media, "media received blocking message," + " n_active_sender_streams = %d, is_complete = %d", + n_active_sender_streams, is_complete); + + if (n_active_sender_streams == 0 || is_complete) + priv->blocking_msg_received++; + + if (n_active_sender_streams == 0) + expected_nbr_blocking_msg = priv->streams->len; + + if (priv->blocked && media_streams_blocking (media) && + priv->no_more_pads_pending == 0 && + priv->blocking_msg_received == expected_nbr_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; + GstRTSPMediaClass *klass; + gboolean ret; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + + g_rec_mutex_lock (&priv->state_lock); + if (klass->handle_message) + ret = klass->handle_message (media, message); + else + ret = FALSE; + g_rec_mutex_unlock (&priv->state_lock); + + return ret; +} + +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_slice_new (DynPaySignalHandlers); + + 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_slice_free (DynPaySignalHandlers, 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) { + GST_DEBUG ("sending EOS for shutdown"); + /* ref so that we don't disappear */ + gst_element_send_event (priv->pipeline, gst_event_new_eos ()); + /* 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. */ + set_state (media, GST_STATE_PLAYING); + } 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; + + GST_INFO ("unprepare media %p", media); + set_target_state (media, GST_STATE_NULL, FALSE); + success = TRUE; + + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) { + 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_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): 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); + } +} + +/* 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; + + 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 +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)) + break; + if (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; + } + default: + break; + } + + return TRUE; + + /* ERRORS */ +start_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +preroll_failed: + { + GST_WARNING ("failed 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) + goto error_status; + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && + priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) + 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); + if (state == GST_STATE_NULL) { + 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; + + gst_rtsp_stream_transport_set_active (trans, FALSE); + } + priv->n_active = 0; + } + 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h new file mode 100644 index 0000000000..9c2494a64e --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h @@ -0,0 +1,449 @@ +/* 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 +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_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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.c new file mode 100644 index 0000000000..145c5ac7bf --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.c @@ -0,0 +1,392 @@ +/* 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_slice_alloc (sizeof (DataItem)); + 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_slice_free1 (sizeof (DataItem), 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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.h new file mode 100644 index 0000000000..200620dcdd --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-mount-points.h @@ -0,0 +1,105 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.c new file mode 100644 index 0000000000..36b7ee3c75 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.c @@ -0,0 +1,219 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.h new file mode 100644 index 0000000000..8230f23c59 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-client.h @@ -0,0 +1,65 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c new file mode 100644 index 0000000000..dbd461a5ab --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.c @@ -0,0 +1,545 @@ +/* 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; + gboolean got_backchannel_stream; + 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); + + /* this adds the non-backchannel streams */ + gst_rtsp_media_collect_streams (media); + + /* this adds the backchannel stream */ + got_backchannel_stream = + gst_rtsp_onvif_media_collect_backchannel (GST_RTSP_ONVIF_MEDIA (media)); + /* FIXME: This should not happen! We checked for that before */ + if (gst_rtsp_onvif_media_factory_requires_backchannel (factory, ctx) && + !got_backchannel_stream) { + 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_element_add_pad (backchannel_bin, 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 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; +} + +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: 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): 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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.h new file mode 100644 index 0000000000..7ff9dc3469 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media-factory.h @@ -0,0 +1,95 @@ +/* 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" + +#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); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.c new file mode 100644 index 0000000000..0b29f898df --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.c @@ -0,0 +1,358 @@ +/* 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" + +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); + 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; +} + +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; + + backchannel_bin = + gst_bin_get_by_name (GST_BIN (element), "onvif-backchannel"); + if (!backchannel_bin) + 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_rtsp_media_create_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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.h new file mode 100644 index 0000000000..95418c073a --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-media.h @@ -0,0 +1,71 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.c new file mode 100644 index 0000000000..704d8b284b --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.c @@ -0,0 +1,101 @@ +/* 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) +{ +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.h new file mode 100644 index 0000000000..b04c9b4d5c --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-onvif-server.h @@ -0,0 +1,71 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.c new file mode 100644 index 0000000000..5fa27afbc6 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.c @@ -0,0 +1,80 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.h new file mode 100644 index 0000000000..f2863169d4 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-params.h @@ -0,0 +1,41 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.c new file mode 100644 index 0000000000..eb125489f6 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.c @@ -0,0 +1,369 @@ +/* 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_slice_free1 (sizeof (GstRTSPPermissionsImpl), 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, + ©->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_slice_new0 (GstRTSPPermissionsImpl); + 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.h new file mode 100644 index 0000000000..fac55e400d --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-permissions.h @@ -0,0 +1,122 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.c new file mode 100644 index 0000000000..29f480447c --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.c @@ -0,0 +1,624 @@ +/* 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); + } + } + } + + 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.h new file mode 100644 index 0000000000..20d2ac8c6b --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-sdp.h @@ -0,0 +1,49 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h new file mode 100644 index 0000000000..b5aaefffc7 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-internal.h @@ -0,0 +1,66 @@ +/* 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_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); + +G_END_DECLS + +#endif /* __GST_RTSP_SERVER_INTERNAL_H__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-object.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-object.h new file mode 100644 index 0000000000..4f44f3a500 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-object.h @@ -0,0 +1,211 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-prelude.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-prelude.h new file mode 100644 index 0000000000..8aff8c4934 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-prelude.h @@ -0,0 +1,44 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.c new file mode 100644 index 0000000000..9d3cb584dc --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.c @@ -0,0 +1,1520 @@ +/* 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) (nullable): 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: (out): 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_slice_free (ClientContext, 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_slice_new0 (ClientContext); + 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); + 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: (allow-none): a #GCancellable or %NULL. + * @error: (out): 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: (allow-none): 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) (allow-none): 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.h new file mode 100644 index 0000000000..1dd1a23242 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.h @@ -0,0 +1,56 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.c new file mode 100644 index 0000000000..8fdb7e211d --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.c @@ -0,0 +1,544 @@ +/* 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.h new file mode 100644 index 0000000000..a20946606d --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-media.h @@ -0,0 +1,123 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.c new file mode 100644 index 0000000000..e55c49fdff --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.c @@ -0,0 +1,766 @@ +/* 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): a callback + * @user_data: (closure): 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.h new file mode 100644 index 0000000000..aeb375c3cb --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session-pool.h @@ -0,0 +1,169 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.c new file mode 100644 index 0000000000..b21d615e46 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.c @@ -0,0 +1,807 @@ +/* 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; +} + +/** + * gst_rtsp_session_get_media: + * @sess: a #GstRTSPSession + * @path: the path for the media + * @matched: (out): the amount of matched characters + * + * Get 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) +{ + 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; + } + } + } + g_mutex_unlock (&priv->lock); + + *matched = best; + + return result; +} + +/** + * gst_rtsp_session_filter: + * @sess: a #GstRTSPSession + * @func: (scope call) (allow-none): a callback + * @user_data: (closure): 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) + priv->medias = g_list_remove (priv->medias, media); + else + priv->medias = g_list_delete_link (priv->medias, walk); + cookie = ++priv->medias_cookie; + g_object_unref (media); + 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 diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.h new file mode 100644 index 0000000000..56063f41a2 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-session.h @@ -0,0 +1,181 @@ +/* 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); + +/** + * 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.c new file mode 100644 index 0000000000..d293a95138 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.c @@ -0,0 +1,984 @@ +/* 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; + GstQueueArray *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_queue_array_new_for_struct (sizeof (BackLogItem), 0); + trans->priv->first_rtp_timestamp = GST_CLOCK_TIME_NONE; + gst_queue_array_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_queue_array_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): a callback called when RTP should be sent + * @send_rtcp: (scope notified): a callback called when RTCP should be sent + * @user_data: (closure): 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): a callback called when RTP should be sent + * @send_rtcp_list: (scope notified): a callback called when RTCP should be sent + * @user_data: (closure): 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): a callback called when the receiver is active + * @user_data: (closure): 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): a callback called when a message has been sent + * @user_data: (closure): 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): a callback called when a message has been sent + * @user_data: (closure): 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_queue_array_get_length (priv->items); + + for (i = 0; i < l; i++) { + BackLogItem *item = (BackLogItem *) + gst_queue_array_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_queue_array_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_queue_array_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_queue_array_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_is_empty (GstRTSPStreamTransport * trans) +{ + return gst_queue_array_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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.h new file mode 100644 index 0000000000..d8516c027e --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream-transport.h @@ -0,0 +1,229 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c new file mode 100644 index 0000000000..92ef358797 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c @@ -0,0 +1,6366 @@ +/* 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; +}; + +#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; +} + +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) + 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; + 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_slice_new0 (GstRTSPAddress); + 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): 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): 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; + + 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; + guint n_messages = 0; + 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 */ + if (buffer) + n_messages += 1; + if (buffer_list) + n_messages += 1; + + 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; + + 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 | + * '-----' '---------' + */ + g_assert (gst_pad_unlink (tee_pad, sink_pad)); + + /* 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; + + 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) { + 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 (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): a callback + * @user_data: (closure): 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 +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); + } 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); + } 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; + g_mutex_unlock (&priv->lock); + 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); + } + + priv->blocking = TRUE; + + GST_DEBUG_OBJECT (pad, "Now blocking"); + + GST_DEBUG_OBJECT (stream, "position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->position)); + + g_mutex_unlock (&priv->lock); + + 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: + return ret; +} + +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; + 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, 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_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 postion: 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 postion: 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 postion: 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 sucessfully 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 sucsessfully 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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.h new file mode 100644 index 0000000000..5e6ff2151a --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.h @@ -0,0 +1,406 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.c new file mode 100644 index 0000000000..2921464f89 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.c @@ -0,0 +1,565 @@ +/* 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_slice_free1 (sizeof (GstRTSPThreadImpl), impl); +} + +static GstRTSPThread * +_gst_rtsp_thread_copy (GstRTSPThreadImpl * impl) +{ + GstRTSPThreadImpl *copy; + + GST_DEBUG ("copy thread %p", impl); + + copy = g_slice_new0 (GstRTSPThreadImpl); + 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_slice_new0 (GstRTSPThreadImpl); + + 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); +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.h new file mode 100644 index 0000000000..01ca3ac711 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-thread-pool.h @@ -0,0 +1,191 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.c b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.c new file mode 100644 index 0000000000..4062d30c06 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.c @@ -0,0 +1,302 @@ +/* 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_slice_free1 (sizeof (GstRTSPTokenImpl), token); +} + +static GstRTSPToken * +_gst_rtsp_token_copy (GstRTSPTokenImpl * token) +{ + GstRTSPTokenImpl *copy; + GstStructure *structure; + + structure = gst_structure_copy (token->structure); + + copy = g_slice_new0 (GstRTSPTokenImpl); + 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_slice_new0 (GstRTSPTokenImpl); + 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: The #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 checks if + * @token is writable and 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.h b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.h new file mode 100644 index 0000000000..27e18fbc48 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-token.h @@ -0,0 +1,113 @@ +/* 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.c b/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.c new file mode 100644 index 0000000000..3573e2088b --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.c @@ -0,0 +1,5251 @@ +/* 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" + +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/" PACKAGE_VERSION +#define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP +#define DEFAULT_RTX_TIME_MS 500 + +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 +}; + +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 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 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. + * + */ + 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::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->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); + + 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); + +#if 0 + if (priv->pool) + gst_rtsp_stream_set_address_pool (stream, priv->pool); +#endif + + 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: + 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; + 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; + 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) + gst_rtsp_message_add_header (msg, GST_RTSP_HDR_USER_AGENT, + sink->user_agent); + + 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."); + 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, then fallthrough and error out */ + 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)); + + 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_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_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 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 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); + + /* 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; + /* 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); + 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; +} diff --git a/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.h b/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.h new file mode 100644 index 0000000000..e736b68f46 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.h @@ -0,0 +1,258 @@ +/* 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; + + /* state */ + GstRTSPState state; + gchar *content_base; + GstRTSPLowerTrans cur_protocols; + gboolean tried_url_auth; + gchar *addr; + 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__ */ diff --git a/subprojects/gst-rtsp-server/gst/rtsp-sink/meson.build b/subprojects/gst-rtsp-server/gst/rtsp-sink/meson.build new file mode 100644 index 0000000000..c67d168269 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-sink/meson.build @@ -0,0 +1,14 @@ +rtspsink_sources = [ + 'gstrtspclientsink.c', + 'plugin.c', +] + +rtspsink = library('gstrtspclientsink', + rtspsink_sources, + c_args : rtspserver_args, + include_directories : rtspserver_incs, + dependencies : [gstrtsp_dep, gstsdp_dep, gst_rtsp_server_dep], + install : true, + install_dir : plugins_install_dir) +pkgconfig.generate(rtspsink, install_dir : plugins_pkgconfig_install_dir) +plugins += [rtspsink] diff --git a/subprojects/gst-rtsp-server/gst/rtsp-sink/plugin.c b/subprojects/gst-rtsp-server/gst/rtsp-sink/plugin.c new file mode 100644 index 0000000000..0580823521 --- /dev/null +++ b/subprojects/gst-rtsp-server/gst/rtsp-sink/plugin.c @@ -0,0 +1,26 @@ +#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) diff --git a/subprojects/gst-rtsp-server/hooks/pre-commit.hook b/subprojects/gst-rtsp-server/hooks/pre-commit.hook new file mode 100755 index 0000000000..6f177402b3 --- /dev/null +++ b/subprojects/gst-rtsp-server/hooks/pre-commit.hook @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Check that the code follows a consistent code style +# + +# Check for existence of indent, and error out if not present. +# On some *bsd systems the binary seems to be called gnunindent, +# so check for that first. + +version=`gnuindent --version 2>/dev/null` +if test "x$version" = "x"; then + version=`gindent --version 2>/dev/null` + if test "x$version" = "x"; then + version=`indent --version 2>/dev/null` + if test "x$version" = "x"; then + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + exit 1 + else + INDENT=indent + fi + else + INDENT=gindent + fi +else + INDENT=gnuindent +fi + +case `$INDENT --version` in + GNU*) + ;; + default) + echo "GStreamer git pre-commit hook:" + echo "Did not find GNU indent, please install it before continuing." + echo "(Found $INDENT, but it doesn't seem to be GNU indent)" + exit 1 + ;; +esac + +INDENT_PARAMETERS="--braces-on-if-line \ + --case-brace-indentation0 \ + --case-indentation2 \ + --braces-after-struct-decl-line \ + --line-length80 \ + --no-tabs \ + --cuddle-else \ + --dont-line-up-parentheses \ + --continuation-indentation4 \ + --honour-newlines \ + --tab-size8 \ + --indent-level2 \ + --leave-preprocessor-space" + +echo "--Checking style--" +for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do + # nf is the temporary checkout. This makes sure we check against the + # revision in the index (and not the checked out version). + nf=`git checkout-index --temp ${file} | cut -f 1` + newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1 + $INDENT ${INDENT_PARAMETERS} \ + $nf -o $newfile 2>> /dev/null + # FIXME: Call indent twice as it tends to do line-breaks + # different for every second call. + $INDENT ${INDENT_PARAMETERS} \ + $newfile 2>> /dev/null + diff -u -p "${nf}" "${newfile}" + r=$? + rm "${newfile}" + rm "${nf}" + if [ $r != 0 ] ; then +echo "=================================================================================================" +echo " Code style error in: $file " +echo " " +echo " Please fix before committing. Don't forget to run git add before trying to commit again. " +echo " If the whole file is to be committed, this should work (run from the top-level directory): " +echo " " +echo " gst-indent $file; git add $file; git commit" +echo " " +echo "=================================================================================================" + exit 1 + fi +done +echo "--Checking style pass--" diff --git a/subprojects/gst-rtsp-server/meson.build b/subprojects/gst-rtsp-server/meson.build new file mode 100644 index 0000000000..0e55878b33 --- /dev/null +++ b/subprojects/gst-rtsp-server/meson.build @@ -0,0 +1,217 @@ +project('gst-rtsp-server', 'c', + version : '1.19.2', + meson_version : '>= 0.54', + 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_dev = gst_version_minor % 2 == 1 and gst_version_micro < 90 + +glib_req = '>= 2.56.0' +gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor) + +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 + +# Symbol visibility +if cc.get_id() == 'msvc' + export_define = '__declspec(dllexport) extern' +elif cc.has_argument('-fvisibility=hidden') + add_project_arguments('-fvisibility=hidden', language: 'c') + export_define = 'extern __attribute__ ((visibility ("default")))' +else + export_define = 'extern' +endif + +# Passing this through the command line would be too messy +cdata.set('GST_API_EXPORT', export_define) + +# 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 + +cast_checks = get_option('gobject-cast-checks') +if cast_checks.disabled() or (cast_checks.auto() and not gst_version_is_dev) + message('Disabling GLib cast checks') + add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: 'c') +endif + +glib_asserts = get_option('glib-asserts') +if glib_asserts.disabled() or (glib_asserts.auto() and not gst_version_is_dev) + message('Disabling GLib asserts') + add_project_arguments('-DG_DISABLE_ASSERT', language: 'c') +endif + +glib_checks = get_option('glib-checks') +if glib_checks.disabled() or (glib_checks.auto() and not gst_version_is_dev) + 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'] + +warning_flags = [ + '-Wmissing-declarations', + '-Wmissing-prototypes', + '-Wredundant-decls', + '-Wundef', + '-Wwrite-strings', + '-Wformat', + '-Wformat-nonliteral', + '-Wformat-security', + '-Wold-style-definition', + '-Waggregate-return', + '-Winit-self', + '-Wmissing-include-dirs', + '-Waddress', + '-Wno-multichar', + '-Wdeclaration-after-statement', + '-Wvla', + '-Wpointer-arith', +] + +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', '.') + +glib_dep = dependency('glib-2.0', version : glib_req, + fallback: ['glib', 'libglib_dep']) +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']) +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);' + \ + '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 = [] +pkgconfig_subdirs = ['gstreamer-1.0'] + +subdir('gst') +if not get_option('tests').disabled() + subdir('tests') +endif +if not get_option('examples').disabled() + subdir('examples') +endif +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')) + if run_result.returncode() == 0 + release_date = run_result.stdout().strip() + cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', release_date) + message('Package release date: ' + release_date) + else + # Error out if our release can't be found in the .doap file + error(run_result.stderr()) + endif +endif + +configure_file(output: 'config.h', configuration: cdata) + +python3 = import('python').find_installation() +run_command(python3, '-c', 'import shutil; shutil.copy("hooks/pre-commit.hook", ".git/hooks/pre-commit")') diff --git a/subprojects/gst-rtsp-server/meson_options.txt b/subprojects/gst-rtsp-server/meson_options.txt new file mode 100644 index 0000000000..4bd238dc32 --- /dev/null +++ b/subprojects/gst-rtsp-server/meson_options.txt @@ -0,0 +1,25 @@ +# 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') +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)') +option('glib-asserts', type : 'feature', value : 'enabled', yield : true, + description: 'Enable GLib assertion (auto = enabled for development, disabled for stable releases)') +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)') + +# 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.') diff --git a/subprojects/gst-rtsp-server/scripts/extract-release-date-from-doap-file.py b/subprojects/gst-rtsp-server/scripts/extract-release-date-from-doap-file.py new file mode 100755 index 0000000000..f09b60e9d0 --- /dev/null +++ b/subprojects/gst-rtsp-server/scripts/extract-release-date-from-doap-file.py @@ -0,0 +1,45 @@ +#!/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)) diff --git a/subprojects/gst-rtsp-server/tests/check/gst/addresspool.c b/subprojects/gst-rtsp-server/tests/check/gst/addresspool.c new file mode 100644 index 0000000000..9a0ff54f14 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/addresspool.c @@ -0,0 +1,286 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/client.c b/subprojects/gst-rtsp-server/tests/check/gst/client.c new file mode 100644 index 0000000000..c65ae01ae4 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/client.c @@ -0,0 +1,2195 @@ +/* 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 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); + + return s; +} + +GST_CHECK_MAIN (rtspclient); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/media.c b/subprojects/gst-rtsp-server/tests/check/gst/media.c new file mode 100644 index 0000000000..0284753d7f --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/media.c @@ -0,0 +1,900 @@ +/* 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)); + g_object_unref (media); + + gst_rtsp_url_free (url); + g_object_unref (factory); + + g_object_unref (pool); + + gst_rtsp_thread_pool_cleanup (); +} + +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)); + g_object_unref (media); + + gst_rtsp_url_free (url); + g_object_unref (factory); + + g_object_unref (pool); + + gst_rtsp_thread_pool_cleanup (); +} + +/* 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); + g_object_unref (media); + + gst_rtsp_url_free (url); + g_object_unref (factory); + + g_object_unref (pool); + + gst_rtsp_thread_pool_cleanup (); +} + +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)); + + 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)); + + g_object_unref (media); + gst_rtsp_url_free (url); + g_object_unref (factory); + + g_object_unref (pool); + gst_rtsp_thread_pool_cleanup (); +} + +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)); + g_object_unref (media); + gst_rtsp_url_free (url); + g_object_unref (factory); + g_object_unref (pool); + gst_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +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)); + + 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)); + 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)); + g_object_unref (media); + + gst_rtsp_url_free (url); + g_object_unref (factory); + g_object_unref (pool); + + gst_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +GST_END_TEST; + + +static Suite * +rtspmedia_suite (void) +{ + Suite *s = suite_create ("rtspmedia"); + TCase *tc = tcase_create ("general"); + gboolean has_avidemux; + + suite_add_tcase (s, tc); + 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); + + return s; +} + +GST_CHECK_MAIN (rtspmedia); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/mediafactory.c b/subprojects/gst-rtsp-server/tests/check/gst/mediafactory.c new file mode 100644 index 0000000000..2fc453cfdc --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/mediafactory.c @@ -0,0 +1,444 @@ +/* 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)); + + media2 = gst_rtsp_media_factory_construct (factory, url); + fail_unless (GST_IS_RTSP_MEDIA (media2)); + fail_if (media == 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)); + + media2 = gst_rtsp_media_factory_construct (factory, url); + fail_unless (GST_IS_RTSP_MEDIA (media2)); + fail_unless (media == 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); + + + 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); + 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); + 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); + 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); + + 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); + + 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/mountpoints.c b/subprojects/gst-rtsp-server/tests/check/gst/mountpoints.c new file mode 100644 index 0000000000..6a35bd2333 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/mountpoints.c @@ -0,0 +1,158 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/onvif.c b/subprojects/gst-rtsp-server/tests/check/gst/onvif.c new file mode 100644 index 0000000000..087a2f7983 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/onvif.c @@ -0,0 +1,1354 @@ +/* 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 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; + } + + *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 ("GstNtpOffset", + "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); + } + + 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 ("GstNtpOffset", + "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; +} + +/* + * 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, 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) +{ + 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); + } + + 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 +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; + gboolean is_custom_event = FALSE; + + fail_unless (gst_rtsp_message_get_body (response, &body, + &body_size) == GST_RTSP_OK); + + buf = gst_rtp_buffer_new_copy_data (body, body_size); + + switch (body_size) { + case 115: /* Ignore our serialized custom events */ + is_custom_event = TRUE; + break; + case 56: + expected_interval = check->expected_i_frame_ts_interval; + check->n_i_frames += 1; + break; + case 46: + expected_interval = check->expected_ts_interval; + check->n_p_frames += 1; + break; + case 41: + expected_interval = check->expected_ts_interval; + check->n_b_frames += 1; + break; + default: + fail ("Invalid body size %u", body_size); + } + + if (!is_custom_event) { + fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp)); + + 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; + + 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); + + gst_rtp_buffer_unmap (&rtp); + + 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_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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/permissions.c b/subprojects/gst-rtsp-server/tests/check/gst/permissions.c new file mode 100644 index 0000000000..6f5ccf0d2f --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/permissions.c @@ -0,0 +1,140 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/rtspclientsink.c b/subprojects/gst-rtsp-server/tests/check/gst/rtspclientsink.c new file mode 100644 index 0000000000..63e799e965 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/rtspclientsink.c @@ -0,0 +1,305 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/rtspserver.c b/subprojects/gst-rtsp-server/tests/check/gst/rtspserver.c new file mode 100644 index 0000000000..ed2cce233f --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/rtspserver.c @@ -0,0 +1,2751 @@ +/* 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_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); +} + +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_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 (); +} + +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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/sessionmedia.c b/subprojects/gst-rtsp-server/tests/check/gst/sessionmedia.c new file mode 100644 index 0000000000..13445b488c --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/sessionmedia.c @@ -0,0 +1,399 @@ +/* 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); + + 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); + + 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); + + 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); + + 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); + + 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/sessionpool.c b/subprojects/gst-rtsp-server/tests/check/gst/sessionpool.c new file mode 100644 index 0000000000..ebccad686e --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/sessionpool.c @@ -0,0 +1,203 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/stream.c b/subprojects/gst-rtsp-server/tests/check/gst/stream.c new file mode 100644 index 0000000000..3da4f9b25c --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/stream.c @@ -0,0 +1,706 @@ +/* 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_transport_free (transport) == GST_RTSP_OK); + fail_unless (gst_rtsp_stream_leave_bin (stream, bin, rtpbin)); + 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/threadpool.c b/subprojects/gst-rtsp-server/tests/check/gst/threadpool.c new file mode 100644 index 0000000000..c92e64f61c --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/threadpool.c @@ -0,0 +1,236 @@ +/* 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_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +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_rtsp_thread_pool_cleanup (); +} + +GST_END_TEST; + +static Suite * +rtspthreadpool_suite (void) +{ + Suite *s = suite_create ("rtspthreadpool"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + 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); diff --git a/subprojects/gst-rtsp-server/tests/check/gst/token.c b/subprojects/gst-rtsp-server/tests/check/gst/token.c new file mode 100644 index 0000000000..3b21c9794e --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/gst/token.c @@ -0,0 +1,110 @@ +/* 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); diff --git a/subprojects/gst-rtsp-server/tests/check/meson.build b/subprojects/gst-rtsp-server/tests/check/meson.build new file mode 100644 index 0000000000..860774af5d --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/check/meson.build @@ -0,0 +1,66 @@ +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_pkgconfig_variable('pluginsdir'), + pbase.get_pkgconfig_variable('pluginsdir'), + pbad.get_pkgconfig_variable('pluginsdir')] + + gst_plugin_scanner_dir = gst_dep.get_pkgconfig_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') + +test_c_args = [ + '-UG_DISABLE_ASSERT', + '-UG_DISABLE_CAST_CHECKS', + '-DGST_CHECK_TEST_ENVIRONMENT_BEACON="GST_PLUGIN_LOADING_WHITELIST"', + '-DGST_TEST_FILES_PATH="' + 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.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.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] + ) + test(test_name, exe, + env : env, + timeout : 120, + is_parallel: false + ) +endforeach diff --git a/subprojects/gst-rtsp-server/tests/files/test.avi b/subprojects/gst-rtsp-server/tests/files/test.avi Binary files differnew file mode 100644 index 0000000000..8c83239cd9 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/files/test.avi diff --git a/subprojects/gst-rtsp-server/tests/meson.build b/subprojects/gst-rtsp-server/tests/meson.build new file mode 100644 index 0000000000..5374bcba45 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/meson.build @@ -0,0 +1,13 @@ +# FIXME: make check work on windows +if host_machine.system() != 'windows' and 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) diff --git a/subprojects/gst-rtsp-server/tests/test-cleanup.c b/subprojects/gst-rtsp-server/tests/test-cleanup.c new file mode 100644 index 0000000000..e0bf023f88 --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/test-cleanup.c @@ -0,0 +1,71 @@ +/* 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; + } +} diff --git a/subprojects/gst-rtsp-server/tests/test-reuse.c b/subprojects/gst-rtsp-server/tests/test-reuse.c new file mode 100644 index 0000000000..e29f5561cc --- /dev/null +++ b/subprojects/gst-rtsp-server/tests/test-reuse.c @@ -0,0 +1,90 @@ +/* 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 (); + + /* 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_print ("failed to attach the server\n"); + return -1; + } +} |