diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..be37554031 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +/built_doc +.vscode +build/ +/hotdoc-private* +/.hotdoc.d +/*.stamp +Debug +Release +ipch +*.user +*.sdf +*.suo +*.opensdf +vs/2010/libs +bin +gen +libs +obj +.classpath +.project +.settings +.libs +.cproject +gst-build +project.properties +gst_sdk +.DS_Store +xcuserdata +/plugins-introspection/cache/ +/hotdoc_env/ +*.html +*~ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..1fa28f4eab --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1 @@ +include: 'https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml' diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e69de29bb2 diff --git a/LICENSE.BSD b/LICENSE.BSD new file mode 100644 index 0000000000..52a1372bc1 --- /dev/null +++ b/LICENSE.BSD @@ -0,0 +1,25 @@ +Copyright (C) GStreamer developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.CC-BY-SA-4.0 b/LICENSE.CC-BY-SA-4.0 new file mode 100644 index 0000000000..1513d41863 --- /dev/null +++ b/LICENSE.CC-BY-SA-4.0 @@ -0,0 +1,348 @@ +Creative Commons Attribution-ShareAlike 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. diff --git a/LICENSE.LGPL-2.1 b/LICENSE.LGPL-2.1 new file mode 100644 index 0000000000..e5ab03e123 --- /dev/null +++ b/LICENSE.LGPL-2.1 @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSE.MIT b/LICENSE.MIT new file mode 100644 index 0000000000..3f269e7fee --- /dev/null +++ b/LICENSE.MIT @@ -0,0 +1,19 @@ +Copyright (C) GStreamer developers + +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. diff --git a/LICENSE.OPL b/LICENSE.OPL new file mode 100644 index 0000000000..503f85110f --- /dev/null +++ b/LICENSE.OPL @@ -0,0 +1,137 @@ +Open Publication License +v1.0, 8 June 1999 + + +I. REQUIREMENTS ON BOTH UNMODIFIED AND MODIFIED VERSIONS + +The Open Publication works may be reproduced and distributed in whole or in +part, in any medium physical or electronic, provided that the terms of this +license are adhered to, and that this license or an incorporation of it by +reference (with any options elected by the author(s) and/or publisher) is +displayed in the reproduction. + +Proper form for an incorporation by reference is as follows: + + Copyright (c) by . This material + may be distributed only subject to the terms and conditions set + forth in the Open Publication License, vX.Y or later (the latest + version is presently available at http://www.opencontent.org/openpub/). + +The reference must be immediately followed with any options elected by the +author(s) and/or publisher of the document (see section VI). + +Commercial redistribution of Open Publication-licensed material is permitted. + +Any publication in standard (paper) book form shall require the citation of +the original publisher and author. The publisher and author's names shall +appear on all outer surfaces of the book. On all outer surfaces of the book +the original publisher's name shall be as large as the title of the work +and cited as possessive with respect to the title. + + +II. COPYRIGHT + +The copyright to each Open Publication is owned by its author(s) or designee. + + +III. SCOPE OF LICENSE + +The following license terms apply to all Open Publication works, unless +otherwise explicitly stated in the document. + +Mere aggregation of Open Publication works or a portion of an Open Publication +work with other works or programs on the same media shall not cause this +license to apply to those other works. The aggregate work shall contain a +notice specifying the inclusion of the Open Publication material and +appropriate copyright notice. + +SEVERABILITY. If any part of this license is found to be unenforceable in any +jurisdiction, the remaining portions of the license remain in force. + +NO WARRANTY. Open Publication works are licensed and provided "as is" without +warranty of any kind, express or implied, including, but not limited to, the +implied warranties of merchantability and fitness for a particular purpose or +a warranty of non-infringement. + + +IV. REQUIREMENTS ON MODIFIED WORKS + +All modified versions of documents covered by this license, including +translations, anthologies, compilations and partial documents, must meet +the following requirements: + + 1. The modified version must be labeled as such. + + 2. The person making the modifications must be identified and the + modifications dated. + + 3. Acknowledgement of the original author and publisher if applicable + must be retained according to normal academic citation practices. + + 4. The location of the original unmodified document must be identified. + + 5. The original author's (or authors') name(s) may not be used to assert + or imply endorsement of the resulting document without the original + author's (or authors') permission. + + +V. GOOD-PRACTICE RECOMMENDATIONS + +In addition to the requirements of this license, it is requested from and +strongly recommended of redistributors that: + + 1. If you are distributing Open Publication works on hardcopy or CD-ROM, + you provide email notification to the authors of your intent to + redistribute at least thirty days before your manuscript or media freeze, + to give the authors time to provide updated documents. This notification + should describe modifications, if any, made to the document. + + 2. All substantive modifications (including deletions) be either clearly + marked up in the document or else described in an attachment to the + document. + + 3. Finally, while it is not mandatory under this license, it is considered + good form to offer a free copy of any hardcopy and CD-ROM expression of + an Open Publication-licensed work to its author(s). + + +VI. LICENSE OPTIONS + +The author(s) and/or publisher of an Open Publication-licensed document may +elect certain options by appending language to the reference to or copy of the +license. These options are considered part of the license instance and must be +included with the license (or its incorporation by reference) in derived works. + +A. To prohibit distribution of substantively modified versions without the +explicit permission of the author(s). "Substantive modification" is defined +as a change to the semantic content of the document, and excludes mere changes +in format or typographical corrections. + +To accomplish this, add the phrase `Distribution of substantively modified +versions of this document is prohibited without the explicit permission of +the copyright holder.' to the license reference or copy. + +B. To prohibit any publication of this work or derivative works in whole or +in part in standard (paper) book form for commercial purposes is prohibited +unless prior permission is obtained from the copyright holder. + +To accomplish this, add the phrase 'Distribution of the work or derivative of +the work in any standard (paper) book form is prohibited unless prior +permission is obtained from the copyright holder.' to the license reference +or copy. + + +OPEN PUBLICATION POLICY APPENDIX: + +(This is not considered part of the license.) + +Open Publication works are available in source format via the Open Publication +home page at http://works.opencontent.org/. + +Open Publication authors who want to include their own license on Open +Publication works may do so, as long as their terms are not more restrictive +than the Open Publication license. + +If you have questions about the Open Publication License, please contact +David Wiley, and/or the Open Publication Authors' List at opal@opencontent.org, +via email. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..8b5c879db5 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# Introduction + +This is a collection of design documents, formerly maintained in various +different locations and formats, now grouped together and converted +to commonmark. + +# Contributing + +## Style + +We will follow the commonmark specification. + +We *should* try to follow this +[style guide](http://www.cirosantilli.com/markdown-style-guide/#about), +but are still [evaluating solutions](https://github.com/jgm/cmark/issues/131) +for *stable* automatic formatting. + +80 columns line width is thus not yet enforced, but strongly suggested. + +# Build the documentation + +## Install dependencies + +* Follow [hotdoc's installation guide](https://hotdoc.github.io/installing.html), + preferably in a virtualenv. + +* We *experimentally* use the hotdoc C extension to include functions by + name, follow the steps outlined [here](https://github.com/hotdoc/hotdoc_c_extension) + +## Build the portal without the API documentation + +``` +meson build +ninja -C build/ GStreamer-doc +``` + +And browse it: + +``` +gio open build/GStreamer-doc/html/index.html +``` + +## API documentation + +Building the API documentation in the portal implies using +[gst-build](https://gitlab.freedesktop.org/gstreamer/gst-build/) which allows us +to aggregate the documentation from all GStreamer modules using the hotdoc subproject +feature. + +From `gst-build`: + +``` +meson build/ +ninja -C build subprojects/gst-docs/GStreamer-doc +``` + +And browse the doc: + +``` +gio open build/subprojects/gst-docs/GStreamer-doc/html/index.html +``` + +You can also generate a release tarball of the portal with: + +``` +ninja -C build gst-docs@@release +``` + +### Adding a newly written plugin to the documentation + +To add a plugin to the documentation you need to add the given +meson target to the `plugins` list present in each GStreamer module for +example: + +``` meson +gst_elements = library('gstcoreelements', + gst_elements_sources, + c_args : gst_c_args, + include_directories : [configinc], + dependencies : [gobject_dep, glib_dep, gst_dep, gst_base_dep], + install : true, + install_dir : plugins_install_dir, +) +plugins += [gst_elements] +``` + +Then you need to regenerate the `gst_plugins_cache.json` file by running +the target manually, if building from the module itself: + +``` +ninja -C docs/gst_plugins_cache.json +``` + +if you use `gst-build` you can run the target that will rebuild all cache files: + +``` +ninja -C plugins_doc_caches` +``` + +NOTE: the newly generated cache should be commited to the git repos. + +The plugins documentation is generated based on that file to +avoid needing to have built all plugins to get the documentation generated. + +NOTE: When moving plugins from one module to another, the `gst_plugins_cache.json` +from the module where the plugin has been removed should be manually edited +to reflect the removal. + +## Licensing + +The content of this module comes from a number of different sources and is +licensed in different ways: + +### Tutorial source code + +All tutorial code is licensed under any of the following licenses (your choice): + + - 2-clause BSD license ("simplified BSD license") (`LICENSE.BSD`) + - MIT license (`LICENSE.MIT`) + - LGPL v2.1 (`LICENSE.LGPL-2.1`), or (at your option) any later version + +This means developers have maximum flexibility and can pick the right license +for any derivative work. + +### Application Developer Manual and Plugin Writer's Guide + +These are licensed under the [Open Publication License v1.0][op-license] +(`LICENSE.OPL`), for historical reasons. + +[op-license]: http://www.opencontent.org/openpub/ + +### Documentation + +#### Tutorials + +The tutorials are licensed under the [Creative Commons CC-BY-SA-4.0 license][cc-by-sa-4.0] +(`LICENSE.CC-BY-SA-4.0`). + +[cc-by-sa-4.0]: https://creativecommons.org/licenses/by-sa/4.0/ + +#### API Reference and Design Documentation + +The remaining documentation, including the API reference and Design Documentation, +is licensed under the LGPL v2.1 (`LICENSE.LGPL-2.1`), or (at your option) any later +version. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..21bef1bc66 --- /dev/null +++ b/TODO.md @@ -0,0 +1,41 @@ +# Todo + +For-later pages: + - tutorials/qt-tutorials.md [tpm: this should all be rewritten from scratch with qmlglsink; QtGStreamer is outdated and unmaintained, we should not promote it] + - basic-media-player.md + - qt-gstreamer-vs-c-gstreamer.md + - using-appsink-appsrc-in-qt.md + +Miscellaneous: + - faq/git.md should be renamed building.md and turned into general + building Q+A or (better) point to a page we have for all that by then + - faq/legal.md needs an overhaul, and be made more relevant + - licensing.md and legal-information.md need to be reviewed + merged, + possibly with FAQ section. + +old sitemap: + + Installing the SDK + Installing on Linux + Installing on Mac OS X + Installing on Windows + Installing for Android development + Installing for iOS development + Building from source using Cerbero + Tutorials + Basic tutorials + Playback tutorials + Android tutorials + iOS tutorials + Table of Concepts + Upcoming tutorials + Deploying your application + Mac OS X deployment + Windows deployment + Multiplatform deployment using Cerbero + GStreamer reference + gst-inspect + gst-launch + Legal information + Frequently Asked Questions + Contact diff --git a/examples/bus_example.c b/examples/bus_example.c new file mode 100644 index 0000000000..e89bef423d --- /dev/null +++ b/examples/bus_example.c @@ -0,0 +1,79 @@ +#include + +static GMainLoop *loop; + +static gboolean +my_bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message)); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *debug; + + gst_message_parse_error (message, &err, &debug); + g_print ("Error: %s\n", err->message); + g_error_free (err); + g_free (debug); + + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + /* end-of-stream */ + g_main_loop_quit (loop); + break; + default: + /* unhandled message */ + break; + } + + /* we want to be notified again the next time there is a message + * on the bus, so returning TRUE (FALSE means we want to stop watching + * for messages on the bus and our callback should not be called again) + */ + return TRUE; +} + +gint +main (gint argc, gchar * argv[]) +{ + GstElement *pipeline; + GstBus *bus; + guint bus_watch_id; + + /* init */ + gst_init (&argc, &argv); + + /* create pipeline, add handler */ + pipeline = gst_pipeline_new ("my_pipeline"); + + /* adds a watch for new message on our pipeline's message bus to + * the default GLib main context, which is the main context that our + * GLib main loop is attached to below + */ + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + bus_watch_id = gst_bus_add_watch (bus, my_bus_callback, NULL); + gst_object_unref (bus); + + /* [...] */ + + /* create a mainloop that runs/iterates the default GLib main context + * (context NULL), in other words: makes the context check if anything + * it watches for has happened. When a message has been posted on the + * bus, the default main context will automatically call our + * my_bus_callback() function to notify us of that message. + * The main loop will be run until someone calls g_main_loop_quit() + */ + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + /* clean up */ + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + g_source_remove (bus_watch_id); + g_main_loop_unref (loop); + + return 0; +} diff --git a/examples/snippets.c b/examples/snippets.c new file mode 100644 index 0000000000..dd155236a9 --- /dev/null +++ b/examples/snippets.c @@ -0,0 +1,37 @@ +#include + +static void +link_to_multiplexer (GstPad * tolink_pad, GstElement * mux) +{ + GstPad *pad; + gchar *srcname, *sinkname; + + srcname = gst_pad_get_name (tolink_pad); + pad = gst_element_get_compatible_pad (mux, tolink_pad, NULL); + gst_pad_link (tolink_pad, pad); + sinkname = gst_pad_get_name (pad); + gst_object_unref (GST_OBJECT (pad)); + + g_print ("A new pad %s was created and linked to %s\n", sinkname, srcname); + g_free (sinkname); + g_free (srcname); +} + +static void +some_function (GstElement * tee) +{ + GstPad *pad; + gchar *name; + + pad = gst_element_request_pad_simple (tee, "src%d"); + name = gst_pad_get_name (pad); + g_print ("A new pad %s was created\n", name); + g_free (name); + + /* here, you would link the pad */ + + /* [..] */ + + /* and, after doing that, free our reference */ + gst_object_unref (GST_OBJECT (pad)); +} diff --git a/examples/tutorials/android/.gitignore b/examples/tutorials/android/.gitignore new file mode 100644 index 0000000000..7f6823bcc0 --- /dev/null +++ b/examples/tutorials/android/.gitignore @@ -0,0 +1,2 @@ +.gradle +build/ diff --git a/examples/tutorials/android/android-tutorial-1/.gitignore b/examples/tutorials/android/android-tutorial-1/.gitignore new file mode 100644 index 0000000000..a97ee90b61 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/.gitignore @@ -0,0 +1,4 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/org/freedesktop/gstreamer/GStreamer.java diff --git a/examples/tutorials/android/android-tutorial-1/AndroidManifest.xml b/examples/tutorials/android/android-tutorial-1/AndroidManifest.xml new file mode 100644 index 0000000000..cc6f8f4b2c --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-1/build.gradle b/examples/tutorials/android/android-tutorial-1/build.gradle new file mode 100644 index 0000000000..1b38d3ca08 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.tutorials.tutorial_1" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "tutorial-1" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/examples/tutorials/android/android-tutorial-1/jni/Android.mk b/examples/tutorials/android/android-tutorial-1/jni/Android.mk new file mode 100644 index 0000000000..ceda28cd2d --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/jni/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := tutorial-1 +LOCAL_SRC_FILES := tutorial-1.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +GSTREAMER_PLUGINS := coreelements +GSTREAMER_EXTRA_LIBS := -liconv +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/examples/tutorials/android/android-tutorial-1/jni/Application.mk b/examples/tutorials/android/android-tutorial-1/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-1/jni/dummy.cpp b/examples/tutorials/android/android-tutorial-1/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/android/android-tutorial-1/jni/tutorial-1.c b/examples/tutorials/android/android-tutorial-1/jni/tutorial-1.c new file mode 100644 index 0000000000..bc1065911b --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/jni/tutorial-1.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +/* + * Java Bindings + */ +static jstring +gst_native_get_gstreamer_info (JNIEnv * env, jobject thiz) +{ + char *version_utf8 = gst_version_string (); + jstring *version_jstring = (*env)->NewStringUTF (env, version_utf8); + g_free (version_utf8); + return version_jstring; +} + +static JNINativeMethod native_methods[] = { + {"nativeGetGStreamerInfo", "()Ljava/lang/String;", + (void *) gst_native_get_gstreamer_info} +}; + +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "tutorial-1", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + return JNI_VERSION_1_4; +} diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-hdpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-hdpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..933899d55f Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-hdpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-ldpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-ldpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..3e47dcbf7d Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-ldpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-mdpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-mdpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..fac2b7d671 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-mdpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-xhdpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-xhdpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..c9ab14ab8e Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-xhdpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-xxhdpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-xxhdpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..e721e560fc Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-xxhdpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/drawable-xxxhdpi/gstreamer_logo_1.png b/examples/tutorials/android/android-tutorial-1/res/drawable-xxxhdpi/gstreamer_logo_1.png new file mode 100644 index 0000000000..55e80490a2 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-1/res/drawable-xxxhdpi/gstreamer_logo_1.png differ diff --git a/examples/tutorials/android/android-tutorial-1/res/layout/main.xml b/examples/tutorials/android/android-tutorial-1/res/layout/main.xml new file mode 100644 index 0000000000..a7dc6da547 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/res/layout/main.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-1/res/values/strings.xml b/examples/tutorials/android/android-tutorial-1/res/values/strings.xml new file mode 100644 index 0000000000..82e956d0e9 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/res/values/strings.xml @@ -0,0 +1,4 @@ + + + GStreamer tutorial 1 + diff --git a/examples/tutorials/android/android-tutorial-1/src/org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1.java b/examples/tutorials/android/android-tutorial-1/src/org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1.java new file mode 100644 index 0000000000..34c36187c4 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-1/src/org/freedesktop/gstreamer/tutorials/tutorial_1/Tutorial1.java @@ -0,0 +1,38 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_1; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.GStreamer; + +public class Tutorial1 extends Activity { + private native String nativeGetGStreamerInfo(); + + // Called when the activity is first created. + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + try { + GStreamer.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + TextView tv = (TextView)findViewById(R.id.textview_info); + tv.setText("Welcome to " + nativeGetGStreamerInfo() + " !"); + } + + static { + System.loadLibrary("gstreamer_android"); + System.loadLibrary("tutorial-1"); + } + +} diff --git a/examples/tutorials/android/android-tutorial-2/.gitignore b/examples/tutorials/android/android-tutorial-2/.gitignore new file mode 100644 index 0000000000..a97ee90b61 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/.gitignore @@ -0,0 +1,4 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/org/freedesktop/gstreamer/GStreamer.java diff --git a/examples/tutorials/android/android-tutorial-2/AndroidManifest.xml b/examples/tutorials/android/android-tutorial-2/AndroidManifest.xml new file mode 100644 index 0000000000..cddedbf627 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-2/build.gradle b/examples/tutorials/android/android-tutorial-2/build.gradle new file mode 100644 index 0000000000..e2caed9a96 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.tutorials.tutorial_2" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "tutorial-2" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/examples/tutorials/android/android-tutorial-2/jni/Android.mk b/examples/tutorials/android/android-tutorial-2/jni/Android.mk new file mode 100644 index 0000000000..e5a08f38d3 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/jni/Android.mk @@ -0,0 +1,33 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := tutorial-2 +LOCAL_SRC_FILES := tutorial-2.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_SYS) +GSTREAMER_EXTRA_LIBS := -liconv +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/examples/tutorials/android/android-tutorial-2/jni/Application.mk b/examples/tutorials/android/android-tutorial-2/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-2/jni/dummy.cpp b/examples/tutorials/android/android-tutorial-2/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/android/android-tutorial-2/jni/tutorial-2.c b/examples/tutorials/android/android-tutorial-2/jni/tutorial-2.c new file mode 100644 index 0000000000..eba72f5f60 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/jni/tutorial-2.c @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +/* Structure to contain all our information, so we can pass it to callbacks */ +typedef struct _CustomData +{ + jobject app; /* Application instance, used to call its methods. A global reference is kept. */ + GstElement *pipeline; /* The running pipeline */ + GMainContext *context; /* GLib context used to run the main loop */ + GMainLoop *main_loop; /* GLib main loop */ + gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ +} CustomData; + +/* These global variables cache values which are not changing during execution */ +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID custom_data_field_id; +static jmethodID set_message_method_id; +static jmethodID on_gstreamer_initialized_method_id; + +/* + * Private methods + */ + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* Change the content of the UI's TextView */ +static void +set_ui_message (const gchar * message, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GST_DEBUG ("Setting message to: %s", message); + jstring jmessage = (*env)->NewStringUTF (env, message); + (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + (*env)->DeleteLocalRef (env, jmessage); +} + +/* Retrieve errors from the bus and show them on the UI */ +static void +error_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GError *err; + gchar *debug_info; + gchar *message_string; + + gst_message_parse_error (msg, &err, &debug_info); + message_string = + g_strdup_printf ("Error received from element %s: %s", + GST_OBJECT_NAME (msg->src), err->message); + g_clear_error (&err); + g_free (debug_info); + set_ui_message (message_string, data); + g_free (message_string); + gst_element_set_state (data->pipeline, GST_STATE_NULL); +} + +/* Notify UI about pipeline state changes */ +static void +state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + /* Only pay attention to messages coming from the pipeline, not its children */ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { + gchar *message = g_strdup_printf ("State changed to %s", + gst_element_state_get_name (new_state)); + set_ui_message (message, data); + g_free (message); + } +} + +/* Check if all conditions are met to report GStreamer as initialized. + * These conditions will change depending on the application */ +static void +check_initialization_complete (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + if (!data->initialized && data->main_loop) { + GST_DEBUG ("Initialization complete, notifying application. main_loop:%p", + data->main_loop); + (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + data->initialized = TRUE; + } +} + +/* Main method for the native code. This is executed on its own thread. */ +static void * +app_function (void *userdata) +{ + JavaVMAttachArgs args; + GstBus *bus; + CustomData *data = (CustomData *) userdata; + GSource *bus_source; + GError *error = NULL; + + GST_DEBUG ("Creating pipeline in CustomData at %p", data); + + /* Create our own GLib Main Context and make it the default one */ + data->context = g_main_context_new (); + g_main_context_push_thread_default (data->context); + + /* Build pipeline */ + data->pipeline = + gst_parse_launch + ("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error); + if (error) { + gchar *message = + g_strdup_printf ("Unable to build pipeline: %s", error->message); + g_clear_error (&error); + set_ui_message (message, data); + g_free (message); + return NULL; + } + + /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ + bus = gst_element_get_bus (data->pipeline); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, data->context); + g_source_unref (bus_source); + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + data); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, data); + gst_object_unref (bus); + + /* Create a GLib Main Loop and set it to run */ + GST_DEBUG ("Entering main loop... (CustomData:%p)", data); + data->main_loop = g_main_loop_new (data->context, FALSE); + check_initialization_complete (data); + g_main_loop_run (data->main_loop); + GST_DEBUG ("Exited main loop"); + g_main_loop_unref (data->main_loop); + data->main_loop = NULL; + + /* Free resources */ + g_main_context_pop_thread_default (data->context); + g_main_context_unref (data->context); + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->pipeline); + + return NULL; +} + +/* + * Java Bindings + */ + +/* Instruct the native code to create its internal data structure, pipeline and thread */ +static void +gst_native_init (JNIEnv * env, jobject thiz) +{ + CustomData *data = g_new0 (CustomData, 1); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); + GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, + "Android tutorial 2"); + gst_debug_set_threshold_for_name ("tutorial-2", GST_LEVEL_DEBUG); + GST_DEBUG ("Created CustomData at %p", data); + data->app = (*env)->NewGlobalRef (env, thiz); + GST_DEBUG ("Created GlobalRef for app object at %p", data->app); + pthread_create (&gst_app_thread, NULL, &app_function, data); +} + +/* Quit the main loop, remove the native thread and free resources */ +static void +gst_native_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Quitting main loop..."); + g_main_loop_quit (data->main_loop); + GST_DEBUG ("Waiting for thread to finish..."); + pthread_join (gst_app_thread, NULL); + GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); + (*env)->DeleteGlobalRef (env, data->app); + GST_DEBUG ("Freeing CustomData at %p", data); + g_free (data); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); + GST_DEBUG ("Done finalizing"); +} + +/* Set pipeline to PLAYING state */ +static void +gst_native_play (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PLAYING"); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); +} + +/* Set pipeline to PAUSED state */ +static void +gst_native_pause (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PAUSED"); + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); +} + +/* Static class initializer: retrieve method and field IDs */ +static jboolean +gst_native_class_init (JNIEnv * env, jclass klass) +{ + custom_data_field_id = + (*env)->GetFieldID (env, klass, "native_custom_data", "J"); + set_message_method_id = + (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); + on_gstreamer_initialized_method_id = + (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); + + if (!custom_data_field_id || !set_message_method_id + || !on_gstreamer_initialized_method_id) { + /* We emit this message through the Android log instead of the GStreamer log because the later + * has not been initialized yet. + */ + __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", + "The calling class does not implement all necessary interface methods"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeInit", "()V", (void *) gst_native_init}, + {"nativeFinalize", "()V", (void *) gst_native_finalize}, + {"nativePlay", "()V", (void *) gst_native_play}, + {"nativePause", "()V", (void *) gst_native_pause}, + {"nativeClassInit", "()Z", (void *) gst_native_class_init} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "tutorial-2", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-hdpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-hdpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..a390d0920d Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-hdpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-ldpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-ldpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..7386cb957b Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-ldpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-mdpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-mdpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..59e97ea53d Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-mdpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-xhdpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-xhdpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..e383899d8f Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-xhdpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-xxhdpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-xxhdpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..d3ea6f0b31 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-xxhdpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/drawable-xxxhdpi/gstreamer_logo_2.png b/examples/tutorials/android/android-tutorial-2/res/drawable-xxxhdpi/gstreamer_logo_2.png new file mode 100644 index 0000000000..9493e7518c Binary files /dev/null and b/examples/tutorials/android/android-tutorial-2/res/drawable-xxxhdpi/gstreamer_logo_2.png differ diff --git a/examples/tutorials/android/android-tutorial-2/res/layout/main.xml b/examples/tutorials/android/android-tutorial-2/res/layout/main.xml new file mode 100644 index 0000000000..7129dfcea0 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/res/layout/main.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-2/res/values/strings.xml b/examples/tutorials/android/android-tutorial-2/res/values/strings.xml new file mode 100644 index 0000000000..25216d173e --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/res/values/strings.xml @@ -0,0 +1,6 @@ + + + GStreamer tutorial 2 + Play + Stop + diff --git a/examples/tutorials/android/android-tutorial-2/src/org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2.java b/examples/tutorials/android/android-tutorial-2/src/org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2.java new file mode 100644 index 0000000000..5bc880bc06 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-2/src/org/freedesktop/gstreamer/tutorials/tutorial_2/Tutorial2.java @@ -0,0 +1,119 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_2; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.GStreamer; + +public class Tutorial2 extends Activity { + private native void nativeInit(); // Initialize native code, build pipeline, etc + private native void nativeFinalize(); // Destroy pipeline and shutdown native code + private native void nativePlay(); // Set pipeline to PLAYING + private native void nativePause(); // Set pipeline to PAUSED + private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks + private long native_custom_data; // Native code will use this to keep private data + + private boolean is_playing_desired; // Whether the user asked to go to PLAYING + + // Called when the activity is first created. + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Initialize GStreamer and warn if it fails + try { + GStreamer.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + ImageButton play = (ImageButton) this.findViewById(R.id.button_play); + play.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = true; + nativePlay(); + } + }); + + ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop); + pause.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = false; + nativePause(); + } + }); + + if (savedInstanceState != null) { + is_playing_desired = savedInstanceState.getBoolean("playing"); + Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired); + } else { + is_playing_desired = false; + Log.i ("GStreamer", "Activity created. There is no saved state, playing: false"); + } + + // Start with disabled buttons, until native code is initialized + this.findViewById(R.id.button_play).setEnabled(false); + this.findViewById(R.id.button_stop).setEnabled(false); + + nativeInit(); + } + + protected void onSaveInstanceState (Bundle outState) { + Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired); + outState.putBoolean("playing", is_playing_desired); + } + + protected void onDestroy() { + nativeFinalize(); + super.onDestroy(); + } + + // Called from native code. This sets the content of the TextView from the UI thread. + private void setMessage(final String message) { + final TextView tv = (TextView) this.findViewById(R.id.textview_message); + runOnUiThread (new Runnable() { + public void run() { + tv.setText(message); + } + }); + } + + // Called from native code. Native code calls this once it has created its pipeline and + // the main loop is running, so it is ready to accept commands. + private void onGStreamerInitialized () { + Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired); + // Restore previous playing state + if (is_playing_desired) { + nativePlay(); + } else { + nativePause(); + } + + // Re-enable buttons, now that GStreamer is initialized + final Activity activity = this; + runOnUiThread(new Runnable() { + public void run() { + activity.findViewById(R.id.button_play).setEnabled(true); + activity.findViewById(R.id.button_stop).setEnabled(true); + } + }); + } + + static { + System.loadLibrary("gstreamer_android"); + System.loadLibrary("tutorial-2"); + nativeClassInit(); + } + +} diff --git a/examples/tutorials/android/android-tutorial-3/.gitignore b/examples/tutorials/android/android-tutorial-3/.gitignore new file mode 100644 index 0000000000..a97ee90b61 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/.gitignore @@ -0,0 +1,4 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/org/freedesktop/gstreamer/GStreamer.java diff --git a/examples/tutorials/android/android-tutorial-3/AndroidManifest.xml b/examples/tutorials/android/android-tutorial-3/AndroidManifest.xml new file mode 100644 index 0000000000..1ac1cba8e3 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-3/build.gradle b/examples/tutorials/android/android-tutorial-3/build.gradle new file mode 100644 index 0000000000..e959dd1861 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.tutorials.tutorial_3" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "tutorial-3" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/examples/tutorials/android/android-tutorial-3/jni/Android.mk b/examples/tutorials/android/android-tutorial-3/jni/Android.mk new file mode 100644 index 0000000000..316495fcff --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := tutorial-3 +LOCAL_SRC_FILES := tutorial-3.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog -landroid +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_EFFECTS) +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 gobject-2.0 +GSTREAMER_EXTRA_LIBS := -liconv +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/examples/tutorials/android/android-tutorial-3/jni/Application.mk b/examples/tutorials/android/android-tutorial-3/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-3/jni/dummy.cpp b/examples/tutorials/android/android-tutorial-3/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/android/android-tutorial-3/jni/tutorial-3.c b/examples/tutorials/android/android-tutorial-3/jni/tutorial-3.c new file mode 100644 index 0000000000..6d00b12360 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/jni/tutorial-3.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +/* Structure to contain all our information, so we can pass it to callbacks */ +typedef struct _CustomData +{ + jobject app; /* Application instance, used to call its methods. A global reference is kept. */ + GstElement *pipeline; /* The running pipeline */ + GMainContext *context; /* GLib context used to run the main loop */ + GMainLoop *main_loop; /* GLib main loop */ + gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ + GstElement *video_sink; /* The video sink element which receives XOverlay commands */ + ANativeWindow *native_window; /* The Android native window where video will be rendered */ +} CustomData; + +/* These global variables cache values which are not changing during execution */ +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID custom_data_field_id; +static jmethodID set_message_method_id; +static jmethodID on_gstreamer_initialized_method_id; + +/* + * Private methods + */ + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* Change the content of the UI's TextView */ +static void +set_ui_message (const gchar * message, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GST_DEBUG ("Setting message to: %s", message); + jstring jmessage = (*env)->NewStringUTF (env, message); + (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + (*env)->DeleteLocalRef (env, jmessage); +} + +/* Retrieve errors from the bus and show them on the UI */ +static void +error_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GError *err; + gchar *debug_info; + gchar *message_string; + + gst_message_parse_error (msg, &err, &debug_info); + message_string = + g_strdup_printf ("Error received from element %s: %s", + GST_OBJECT_NAME (msg->src), err->message); + g_clear_error (&err); + g_free (debug_info); + set_ui_message (message_string, data); + g_free (message_string); + gst_element_set_state (data->pipeline, GST_STATE_NULL); +} + +/* Notify UI about pipeline state changes */ +static void +state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + /* Only pay attention to messages coming from the pipeline, not its children */ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { + gchar *message = g_strdup_printf ("State changed to %s", + gst_element_state_get_name (new_state)); + set_ui_message (message, data); + g_free (message); + } +} + +/* Check if all conditions are met to report GStreamer as initialized. + * These conditions will change depending on the application */ +static void +check_initialization_complete (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + if (!data->initialized && data->native_window && data->main_loop) { + GST_DEBUG + ("Initialization complete, notifying application. native_window:%p main_loop:%p", + data->native_window, data->main_loop); + + /* The main loop is running and we received a native window, inform the sink about it */ + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), + (guintptr) data->native_window); + + (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + data->initialized = TRUE; + } +} + +/* Main method for the native code. This is executed on its own thread. */ +static void * +app_function (void *userdata) +{ + JavaVMAttachArgs args; + GstBus *bus; + CustomData *data = (CustomData *) userdata; + GSource *bus_source; + GError *error = NULL; + + GST_DEBUG ("Creating pipeline in CustomData at %p", data); + + /* Create our own GLib Main Context and make it the default one */ + data->context = g_main_context_new (); + g_main_context_push_thread_default (data->context); + + /* Build pipeline */ + data->pipeline = + gst_parse_launch ("videotestsrc ! warptv ! videoconvert ! autovideosink", + &error); + if (error) { + gchar *message = + g_strdup_printf ("Unable to build pipeline: %s", error->message); + g_clear_error (&error); + set_ui_message (message, data); + g_free (message); + return NULL; + } + + /* Set the pipeline to READY, so it can already accept a window handle, if we have one */ + gst_element_set_state (data->pipeline, GST_STATE_READY); + + data->video_sink = + gst_bin_get_by_interface (GST_BIN (data->pipeline), + GST_TYPE_VIDEO_OVERLAY); + if (!data->video_sink) { + GST_ERROR ("Could not retrieve video sink"); + return NULL; + } + + /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ + bus = gst_element_get_bus (data->pipeline); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, data->context); + g_source_unref (bus_source); + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + data); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, data); + gst_object_unref (bus); + + /* Create a GLib Main Loop and set it to run */ + GST_DEBUG ("Entering main loop... (CustomData:%p)", data); + data->main_loop = g_main_loop_new (data->context, FALSE); + check_initialization_complete (data); + g_main_loop_run (data->main_loop); + GST_DEBUG ("Exited main loop"); + g_main_loop_unref (data->main_loop); + data->main_loop = NULL; + + /* Free resources */ + g_main_context_pop_thread_default (data->context); + g_main_context_unref (data->context); + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->video_sink); + gst_object_unref (data->pipeline); + + return NULL; +} + +/* + * Java Bindings + */ + +/* Instruct the native code to create its internal data structure, pipeline and thread */ +static void +gst_native_init (JNIEnv * env, jobject thiz) +{ + CustomData *data = g_new0 (CustomData, 1); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); + GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-3", 0, + "Android tutorial 3"); + gst_debug_set_threshold_for_name ("tutorial-3", GST_LEVEL_DEBUG); + GST_DEBUG ("Created CustomData at %p", data); + data->app = (*env)->NewGlobalRef (env, thiz); + GST_DEBUG ("Created GlobalRef for app object at %p", data->app); + pthread_create (&gst_app_thread, NULL, &app_function, data); +} + +/* Quit the main loop, remove the native thread and free resources */ +static void +gst_native_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Quitting main loop..."); + g_main_loop_quit (data->main_loop); + GST_DEBUG ("Waiting for thread to finish..."); + pthread_join (gst_app_thread, NULL); + GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); + (*env)->DeleteGlobalRef (env, data->app); + GST_DEBUG ("Freeing CustomData at %p", data); + g_free (data); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); + GST_DEBUG ("Done finalizing"); +} + +/* Set pipeline to PLAYING state */ +static void +gst_native_play (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PLAYING"); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); +} + +/* Set pipeline to PAUSED state */ +static void +gst_native_pause (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PAUSED"); + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); +} + +/* Static class initializer: retrieve method and field IDs */ +static jboolean +gst_native_class_init (JNIEnv * env, jclass klass) +{ + custom_data_field_id = + (*env)->GetFieldID (env, klass, "native_custom_data", "J"); + set_message_method_id = + (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); + on_gstreamer_initialized_method_id = + (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); + + if (!custom_data_field_id || !set_message_method_id + || !on_gstreamer_initialized_method_id) { + /* We emit this message through the Android log instead of the GStreamer log because the later + * has not been initialized yet. + */ + __android_log_print (ANDROID_LOG_ERROR, "tutorial-3", + "The calling class does not implement all necessary interface methods"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +static void +gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface); + GST_DEBUG ("Received surface %p (native window %p)", surface, + new_native_window); + + if (data->native_window) { + ANativeWindow_release (data->native_window); + if (data->native_window == new_native_window) { + GST_DEBUG ("New native window is the same as the previous one %p", + data->native_window); + if (data->video_sink) { + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink)); + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink)); + } + return; + } else { + GST_DEBUG ("Released previous native window %p", data->native_window); + data->initialized = FALSE; + } + } + data->native_window = new_native_window; + + check_initialization_complete (data); +} + +static void +gst_native_surface_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Releasing Native Window %p", data->native_window); + + if (data->video_sink) { + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink), + (guintptr) NULL); + gst_element_set_state (data->pipeline, GST_STATE_READY); + } + + ANativeWindow_release (data->native_window); + data->native_window = NULL; + data->initialized = FALSE; +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeInit", "()V", (void *) gst_native_init}, + {"nativeFinalize", "()V", (void *) gst_native_finalize}, + {"nativePlay", "()V", (void *) gst_native_play}, + {"nativePause", "()V", (void *) gst_native_pause}, + {"nativeSurfaceInit", "(Ljava/lang/Object;)V", + (void *) gst_native_surface_init}, + {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, + {"nativeClassInit", "()Z", (void *) gst_native_class_init} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "tutorial-3", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-hdpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-hdpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..47d9a80758 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-hdpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-ldpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-ldpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..fd2c41b739 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-ldpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-mdpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-mdpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..652d0f714e Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-mdpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-xhdpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-xhdpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..7dac7d9047 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-xhdpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-xxhdpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-xxhdpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..b51ecc56a3 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-xxhdpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/drawable-xxxhdpi/gstreamer_logo_3.png b/examples/tutorials/android/android-tutorial-3/res/drawable-xxxhdpi/gstreamer_logo_3.png new file mode 100644 index 0000000000..6cc26c0a62 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-3/res/drawable-xxxhdpi/gstreamer_logo_3.png differ diff --git a/examples/tutorials/android/android-tutorial-3/res/layout/main.xml b/examples/tutorials/android/android-tutorial-3/res/layout/main.xml new file mode 100644 index 0000000000..f70922614c --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/res/layout/main.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-3/res/values/strings.xml b/examples/tutorials/android/android-tutorial-3/res/values/strings.xml new file mode 100644 index 0000000000..9f5f9cd3a8 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/res/values/strings.xml @@ -0,0 +1,6 @@ + + + GStreamer tutorial 3 + Play + Stop + diff --git a/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/GStreamerSurfaceView.java b/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/GStreamerSurfaceView.java new file mode 100644 index 0000000000..9af8aba2da --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/GStreamerSurfaceView.java @@ -0,0 +1,85 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_3; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; + +// A simple SurfaceView whose width and height can be set from the outside +public class GStreamerSurfaceView extends SurfaceView { + public int media_width = 320; + public int media_height = 240; + + // Mandatory constructors, they do not do much + public GStreamerSurfaceView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public GStreamerSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GStreamerSurfaceView (Context context) { + super(context); + } + + // Called by the layout manager to find out our size and give us some rules. + // We will try to maximize our size, and preserve the media's aspect ratio if + // we are given the freedom to do so. + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0, height = 0; + int wmode = View.MeasureSpec.getMode(widthMeasureSpec); + int hmode = View.MeasureSpec.getMode(heightMeasureSpec); + int wsize = View.MeasureSpec.getSize(widthMeasureSpec); + int hsize = View.MeasureSpec.getSize(heightMeasureSpec); + + Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height); + // Obey width rules + switch (wmode) { + case View.MeasureSpec.AT_MOST: + if (hmode == View.MeasureSpec.EXACTLY) { + width = Math.min(hsize * media_width / media_height, wsize); + break; + } + case View.MeasureSpec.EXACTLY: + width = wsize; + break; + case View.MeasureSpec.UNSPECIFIED: + width = media_width; + } + + // Obey height rules + switch (hmode) { + case View.MeasureSpec.AT_MOST: + if (wmode == View.MeasureSpec.EXACTLY) { + height = Math.min(wsize * media_height / media_width, hsize); + break; + } + case View.MeasureSpec.EXACTLY: + height = hsize; + break; + case View.MeasureSpec.UNSPECIFIED: + height = media_height; + } + + // Finally, calculate best size when both axis are free + if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) { + int correct_height = width * media_height / media_width; + int correct_width = height * media_width / media_height; + + if (correct_height < height) + height = correct_height; + else + width = correct_width; + } + + // Obey minimum size + width = Math.max (getSuggestedMinimumWidth(), width); + height = Math.max (getSuggestedMinimumHeight(), height); + setMeasuredDimension(width, height); + } + +} diff --git a/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3.java b/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3.java new file mode 100644 index 0000000000..cc610172f0 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-3/src/org/freedesktop/gstreamer/tutorials/tutorial_3/Tutorial3.java @@ -0,0 +1,143 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_3; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.GStreamer; + +public class Tutorial3 extends Activity implements SurfaceHolder.Callback { + private native void nativeInit(); // Initialize native code, build pipeline, etc + private native void nativeFinalize(); // Destroy pipeline and shutdown native code + private native void nativePlay(); // Set pipeline to PLAYING + private native void nativePause(); // Set pipeline to PAUSED + private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks + private native void nativeSurfaceInit(Object surface); + private native void nativeSurfaceFinalize(); + private long native_custom_data; // Native code will use this to keep private data + + private boolean is_playing_desired; // Whether the user asked to go to PLAYING + + // Called when the activity is first created. + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Initialize GStreamer and warn if it fails + try { + GStreamer.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + ImageButton play = (ImageButton) this.findViewById(R.id.button_play); + play.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = true; + nativePlay(); + } + }); + + ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop); + pause.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = false; + nativePause(); + } + }); + + SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); + SurfaceHolder sh = sv.getHolder(); + sh.addCallback(this); + + if (savedInstanceState != null) { + is_playing_desired = savedInstanceState.getBoolean("playing"); + Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired); + } else { + is_playing_desired = false; + Log.i ("GStreamer", "Activity created. There is no saved state, playing: false"); + } + + // Start with disabled buttons, until native code is initialized + this.findViewById(R.id.button_play).setEnabled(false); + this.findViewById(R.id.button_stop).setEnabled(false); + + nativeInit(); + } + + protected void onSaveInstanceState (Bundle outState) { + Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired); + outState.putBoolean("playing", is_playing_desired); + } + + protected void onDestroy() { + nativeFinalize(); + super.onDestroy(); + } + + // Called from native code. This sets the content of the TextView from the UI thread. + private void setMessage(final String message) { + final TextView tv = (TextView) this.findViewById(R.id.textview_message); + runOnUiThread (new Runnable() { + public void run() { + tv.setText(message); + } + }); + } + + // Called from native code. Native code calls this once it has created its pipeline and + // the main loop is running, so it is ready to accept commands. + private void onGStreamerInitialized () { + Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired); + // Restore previous playing state + if (is_playing_desired) { + nativePlay(); + } else { + nativePause(); + } + + // Re-enable buttons, now that GStreamer is initialized + final Activity activity = this; + runOnUiThread(new Runnable() { + public void run() { + activity.findViewById(R.id.button_play).setEnabled(true); + activity.findViewById(R.id.button_stop).setEnabled(true); + } + }); + } + + static { + System.loadLibrary("gstreamer_android"); + System.loadLibrary("tutorial-3"); + nativeClassInit(); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + + width + " height " + height); + nativeSurfaceInit (holder.getSurface()); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.d("GStreamer", "Surface created: " + holder.getSurface()); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d("GStreamer", "Surface destroyed"); + nativeSurfaceFinalize (); + } + +} diff --git a/examples/tutorials/android/android-tutorial-4/.gitignore b/examples/tutorials/android/android-tutorial-4/.gitignore new file mode 100644 index 0000000000..f34cf307c0 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/.gitignore @@ -0,0 +1,4 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/org/ diff --git a/examples/tutorials/android/android-tutorial-4/AndroidManifest.xml b/examples/tutorials/android/android-tutorial-4/AndroidManifest.xml new file mode 100644 index 0000000000..aa06aab8a2 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-4/build.gradle b/examples/tutorials/android/android-tutorial-4/build.gradle new file mode 100644 index 0000000000..32d885661f --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.tutorials.tutorial_4" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "tutorial-4" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/examples/tutorials/android/android-tutorial-4/jni/Android.mk b/examples/tutorials/android/android-tutorial-4/jni/Android.mk new file mode 100644 index 0000000000..b0514a6572 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := tutorial-4 +LOCAL_SRC_FILES := tutorial-4.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog -landroid +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) +G_IO_MODULES := openssl +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/examples/tutorials/android/android-tutorial-4/jni/Application.mk b/examples/tutorials/android/android-tutorial-4/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-4/jni/dummy.cpp b/examples/tutorials/android/android-tutorial-4/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/android/android-tutorial-4/jni/tutorial-4.c b/examples/tutorials/android/android-tutorial-4/jni/tutorial-4.c new file mode 100644 index 0000000000..525bfb8cd9 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/jni/tutorial-4.c @@ -0,0 +1,673 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +/* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably + * confuse some demuxers. */ +#define SEEK_MIN_DELAY (500 * GST_MSECOND) + +/* Structure to contain all our information, so we can pass it to callbacks */ +typedef struct _CustomData +{ + jobject app; /* Application instance, used to call its methods. A global reference is kept. */ + GstElement *pipeline; /* The running pipeline */ + GMainContext *context; /* GLib context used to run the main loop */ + GMainLoop *main_loop; /* GLib main loop */ + gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ + ANativeWindow *native_window; /* The Android native window where video will be rendered */ + GstState state; /* Current pipeline state */ + GstState target_state; /* Desired pipeline state, to be set once buffering is complete */ + gint64 duration; /* Cached clip duration */ + gint64 desired_position; /* Position to seek to, once the pipeline is running */ + GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */ + gboolean is_live; /* Live streams do not use buffering */ +} CustomData; + +/* playbin flags */ +typedef enum +{ + GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ +} GstPlayFlags; + +/* These global variables cache values which are not changing during execution */ +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID custom_data_field_id; +static jmethodID set_message_method_id; +static jmethodID set_current_position_method_id; +static jmethodID on_gstreamer_initialized_method_id; +static jmethodID on_media_size_changed_method_id; + +/* + * Private methods + */ + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* Change the content of the UI's TextView */ +static void +set_ui_message (const gchar * message, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GST_DEBUG ("Setting message to: %s", message); + jstring jmessage = (*env)->NewStringUTF (env, message); + (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + (*env)->DeleteLocalRef (env, jmessage); +} + +/* Tell the application what is the current position and clip duration */ +static void +set_current_ui_position (gint position, gint duration, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + (*env)->CallVoidMethod (env, data->app, set_current_position_method_id, + position, duration); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } +} + +/* If we have pipeline and it is running, query the current position and clip duration and inform + * the application */ +static gboolean +refresh_ui (CustomData * data) +{ + gint64 current = -1; + gint64 position; + + /* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */ + if (!data || !data->pipeline || data->state < GST_STATE_PAUSED) + return TRUE; + + /* If we didn't know it yet, query the stream duration */ + if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { + if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, + &data->duration)) { + GST_WARNING ("Could not query current duration"); + } + } + + if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) { + /* Java expects these values in milliseconds, and GStreamer provides nanoseconds */ + set_current_ui_position (position / GST_MSECOND, + data->duration / GST_MSECOND, data); + } + return TRUE; +} + +/* Forward declaration for the delayed seek callback */ +static gboolean delayed_seek_cb (CustomData * data); + +/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for + * some time in the future. */ +static void +execute_seek (gint64 desired_position, CustomData * data) +{ + gint64 diff; + + if (desired_position == GST_CLOCK_TIME_NONE) + return; + + diff = gst_util_get_timestamp () - data->last_seek_time; + + if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) { + /* The previous seek was too close, delay this one */ + GSource *timeout_source; + + if (data->desired_position == GST_CLOCK_TIME_NONE) { + /* There was no previous seek scheduled. Setup a timer for some time in the future */ + timeout_source = + g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND); + g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb, + data, NULL); + g_source_attach (timeout_source, data->context); + g_source_unref (timeout_source); + } + /* Update the desired seek position. If multiple requests are received before it is time + * to perform a seek, only the last one is remembered. */ + data->desired_position = desired_position; + GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" + GST_TIME_FORMAT, GST_TIME_ARGS (desired_position), + GST_TIME_ARGS (SEEK_MIN_DELAY - diff)); + } else { + /* Perform the seek now */ + GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, + GST_TIME_ARGS (desired_position)); + data->last_seek_time = gst_util_get_timestamp (); + gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position); + data->desired_position = GST_CLOCK_TIME_NONE; + } +} + +/* Delayed seek callback. This gets called by the timer setup in the above function. */ +static gboolean +delayed_seek_cb (CustomData * data) +{ + GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, + GST_TIME_ARGS (data->desired_position)); + execute_seek (data->desired_position, data); + return FALSE; +} + +/* Retrieve errors from the bus and show them on the UI */ +static void +error_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GError *err; + gchar *debug_info; + gchar *message_string; + + gst_message_parse_error (msg, &err, &debug_info); + message_string = + g_strdup_printf ("Error received from element %s: %s", + GST_OBJECT_NAME (msg->src), err->message); + g_clear_error (&err); + g_free (debug_info); + set_ui_message (message_string, data); + g_free (message_string); + data->target_state = GST_STATE_NULL; + gst_element_set_state (data->pipeline, GST_STATE_NULL); +} + +/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */ +static void +eos_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + data->target_state = GST_STATE_PAUSED; + data->is_live = + (gst_element_set_state (data->pipeline, + GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); + execute_seek (0, data); +} + +/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */ +static void +duration_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + data->duration = GST_CLOCK_TIME_NONE; +} + +/* Called when buffering messages are received. We inform the UI about the current buffering level and + * keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */ +static void +buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + gint percent; + + if (data->is_live) + return; + + gst_message_parse_buffering (msg, &percent); + if (percent < 100 && data->target_state >= GST_STATE_PAUSED) { + gchar *message_string = g_strdup_printf ("Buffering %d%%", percent); + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); + set_ui_message (message_string, data); + g_free (message_string); + } else if (data->target_state >= GST_STATE_PLAYING) { + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); + } else if (data->target_state >= GST_STATE_PAUSED) { + set_ui_message ("Buffering complete", data); + } +} + +/* Called when the clock is lost */ +static void +clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + if (data->target_state >= GST_STATE_PLAYING) { + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); + } +} + +/* Retrieve the video sink's Caps and tell the application about the media size */ +static void +check_media_size (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GstElement *video_sink; + GstPad *video_sink_pad; + GstCaps *caps; + GstVideoInfo info; + + /* Retrieve the Caps at the entrance of the video sink */ + g_object_get (data->pipeline, "video-sink", &video_sink, NULL); + video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); + caps = gst_pad_get_current_caps (video_sink_pad); + + if (gst_video_info_from_caps (&info, caps)) { + info.width = info.width * info.par_n / info.par_d; + GST_DEBUG ("Media size is %dx%d, notifying application", info.width, + info.height); + + (*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id, + (jint) info.width, (jint) info.height); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + } + + gst_caps_unref (caps); + gst_object_unref (video_sink_pad); + gst_object_unref (video_sink); +} + +/* Notify UI about pipeline state changes */ +static void +state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + /* Only pay attention to messages coming from the pipeline, not its children */ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { + data->state = new_state; + gchar *message = g_strdup_printf ("State changed to %s", + gst_element_state_get_name (new_state)); + set_ui_message (message, data); + g_free (message); + + /* The Ready to Paused state change is particularly interesting: */ + if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) { + /* By now the sink already knows the media size */ + check_media_size (data); + + /* If there was a scheduled seek, perform it now that we have moved to the Paused state */ + if (GST_CLOCK_TIME_IS_VALID (data->desired_position)) + execute_seek (data->desired_position, data); + } + } +} + +/* Check if all conditions are met to report GStreamer as initialized. + * These conditions will change depending on the application */ +static void +check_initialization_complete (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + if (!data->initialized && data->native_window && data->main_loop) { + GST_DEBUG + ("Initialization complete, notifying application. native_window:%p main_loop:%p", + data->native_window, data->main_loop); + + /* The main loop is running and we received a native window, inform the sink about it */ + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), + (guintptr) data->native_window); + + (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + data->initialized = TRUE; + } +} + +/* Main method for the native code. This is executed on its own thread. */ +static void * +app_function (void *userdata) +{ + JavaVMAttachArgs args; + GstBus *bus; + CustomData *data = (CustomData *) userdata; + GSource *timeout_source; + GSource *bus_source; + GError *error = NULL; + guint flags; + + GST_DEBUG ("Creating pipeline in CustomData at %p", data); + + /* Create our own GLib Main Context and make it the default one */ + data->context = g_main_context_new (); + g_main_context_push_thread_default (data->context); + + /* Build pipeline */ + data->pipeline = gst_parse_launch ("playbin", &error); + if (error) { + gchar *message = + g_strdup_printf ("Unable to build pipeline: %s", error->message); + g_clear_error (&error); + set_ui_message (message, data); + g_free (message); + return NULL; + } + + /* Disable subtitles */ + g_object_get (data->pipeline, "flags", &flags, NULL); + flags &= ~GST_PLAY_FLAG_TEXT; + g_object_set (data->pipeline, "flags", flags, NULL); + + /* Set the pipeline to READY, so it can already accept a window handle, if we have one */ + data->target_state = GST_STATE_READY; + gst_element_set_state (data->pipeline, GST_STATE_READY); + + /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ + bus = gst_element_get_bus (data->pipeline); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, data->context); + g_source_unref (bus_source); + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + data); + g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, data); + g_signal_connect (G_OBJECT (bus), "message::duration", + (GCallback) duration_cb, data); + g_signal_connect (G_OBJECT (bus), "message::buffering", + (GCallback) buffering_cb, data); + g_signal_connect (G_OBJECT (bus), "message::clock-lost", + (GCallback) clock_lost_cb, data); + gst_object_unref (bus); + + /* Register a function that GLib will call 4 times per second */ + timeout_source = g_timeout_source_new (250); + g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL); + g_source_attach (timeout_source, data->context); + g_source_unref (timeout_source); + + /* Create a GLib Main Loop and set it to run */ + GST_DEBUG ("Entering main loop... (CustomData:%p)", data); + data->main_loop = g_main_loop_new (data->context, FALSE); + check_initialization_complete (data); + g_main_loop_run (data->main_loop); + GST_DEBUG ("Exited main loop"); + g_main_loop_unref (data->main_loop); + data->main_loop = NULL; + + /* Free resources */ + g_main_context_pop_thread_default (data->context); + g_main_context_unref (data->context); + data->target_state = GST_STATE_NULL; + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->pipeline); + + return NULL; +} + +/* + * Java Bindings + */ + +/* Instruct the native code to create its internal data structure, pipeline and thread */ +static void +gst_native_init (JNIEnv * env, jobject thiz) +{ + CustomData *data = g_new0 (CustomData, 1); + data->desired_position = GST_CLOCK_TIME_NONE; + data->last_seek_time = GST_CLOCK_TIME_NONE; + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); + GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-4", 0, + "Android tutorial 4"); + gst_debug_set_threshold_for_name ("tutorial-4", GST_LEVEL_DEBUG); + GST_DEBUG ("Created CustomData at %p", data); + data->app = (*env)->NewGlobalRef (env, thiz); + GST_DEBUG ("Created GlobalRef for app object at %p", data->app); + pthread_create (&gst_app_thread, NULL, &app_function, data); +} + +/* Quit the main loop, remove the native thread and free resources */ +static void +gst_native_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Quitting main loop..."); + g_main_loop_quit (data->main_loop); + GST_DEBUG ("Waiting for thread to finish..."); + pthread_join (gst_app_thread, NULL); + GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); + (*env)->DeleteGlobalRef (env, data->app); + GST_DEBUG ("Freeing CustomData at %p", data); + g_free (data); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); + GST_DEBUG ("Done finalizing"); +} + +/* Set playbin's URI */ +void +gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data || !data->pipeline) + return; + const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL); + GST_DEBUG ("Setting URI to %s", char_uri); + if (data->target_state >= GST_STATE_READY) + gst_element_set_state (data->pipeline, GST_STATE_READY); + g_object_set (data->pipeline, "uri", char_uri, NULL); + (*env)->ReleaseStringUTFChars (env, uri, char_uri); + data->duration = GST_CLOCK_TIME_NONE; + data->is_live = + (gst_element_set_state (data->pipeline, + data->target_state) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Set pipeline to PLAYING state */ +static void +gst_native_play (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PLAYING"); + data->target_state = GST_STATE_PLAYING; + data->is_live = + (gst_element_set_state (data->pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Set pipeline to PAUSED state */ +static void +gst_native_pause (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PAUSED"); + data->target_state = GST_STATE_PAUSED; + data->is_live = + (gst_element_set_state (data->pipeline, + GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Instruct the pipeline to seek to a different position */ +void +gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + gint64 desired_position = (gint64) (milliseconds * GST_MSECOND); + if (data->state >= GST_STATE_PAUSED) { + execute_seek (desired_position, data); + } else { + GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", + GST_TIME_ARGS (desired_position)); + data->desired_position = desired_position; + } +} + +/* Static class initializer: retrieve method and field IDs */ +static jboolean +gst_native_class_init (JNIEnv * env, jclass klass) +{ + custom_data_field_id = + (*env)->GetFieldID (env, klass, "native_custom_data", "J"); + set_message_method_id = + (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); + set_current_position_method_id = + (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V"); + on_gstreamer_initialized_method_id = + (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); + on_media_size_changed_method_id = + (*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V"); + + if (!custom_data_field_id || !set_message_method_id + || !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id + || !set_current_position_method_id) { + /* We emit this message through the Android log instead of the GStreamer log because the later + * has not been initialized yet. + */ + __android_log_print (ANDROID_LOG_ERROR, "tutorial-4", + "The calling class does not implement all necessary interface methods"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +static void +gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface); + GST_DEBUG ("Received surface %p (native window %p)", surface, + new_native_window); + + if (data->native_window) { + ANativeWindow_release (data->native_window); + if (data->native_window == new_native_window) { + GST_DEBUG ("New native window is the same as the previous one %p", + data->native_window); + if (data->pipeline) { + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline)); + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline)); + } + return; + } else { + GST_DEBUG ("Released previous native window %p", data->native_window); + data->initialized = FALSE; + } + } + data->native_window = new_native_window; + + check_initialization_complete (data); +} + +static void +gst_native_surface_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Releasing Native Window %p", data->native_window); + + if (data->pipeline) { + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), + (guintptr) NULL); + gst_element_set_state (data->pipeline, GST_STATE_READY); + } + + ANativeWindow_release (data->native_window); + data->native_window = NULL; + data->initialized = FALSE; +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeInit", "()V", (void *) gst_native_init}, + {"nativeFinalize", "()V", (void *) gst_native_finalize}, + {"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri}, + {"nativePlay", "()V", (void *) gst_native_play}, + {"nativePause", "()V", (void *) gst_native_pause}, + {"nativeSetPosition", "(I)V", (void *) gst_native_set_position}, + {"nativeSurfaceInit", "(Ljava/lang/Object;)V", + (void *) gst_native_surface_init}, + {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, + {"nativeClassInit", "()Z", (void *) gst_native_class_init} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "tutorial-4", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-hdpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-hdpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..40e968128d Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-hdpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-ldpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-ldpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..bf6d8ca85b Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-ldpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-mdpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-mdpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..0ce2ac02f8 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-mdpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-xhdpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-xhdpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..5c59576540 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-xhdpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-xxhdpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-xxhdpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..0674b0cc87 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-xxhdpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/drawable-xxxhdpi/gstreamer_logo_4.png b/examples/tutorials/android/android-tutorial-4/res/drawable-xxxhdpi/gstreamer_logo_4.png new file mode 100644 index 0000000000..6d4e5753e1 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-4/res/drawable-xxxhdpi/gstreamer_logo_4.png differ diff --git a/examples/tutorials/android/android-tutorial-4/res/layout/main.xml b/examples/tutorials/android/android-tutorial-4/res/layout/main.xml new file mode 100644 index 0000000000..20df7a3917 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/res/layout/main.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-4/res/values/strings.xml b/examples/tutorials/android/android-tutorial-4/res/values/strings.xml new file mode 100644 index 0000000000..d26d1e6e86 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/res/values/strings.xml @@ -0,0 +1,6 @@ + + + GStreamer tutorial 4 + Play + Stop + diff --git a/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/GStreamerSurfaceView.java b/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/GStreamerSurfaceView.java new file mode 100644 index 0000000000..75e7196ec3 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/GStreamerSurfaceView.java @@ -0,0 +1,85 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_4; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; + +// A simple SurfaceView whose width and height can be set from the outside +public class GStreamerSurfaceView extends SurfaceView { + public int media_width = 320; + public int media_height = 240; + + // Mandatory constructors, they do not do much + public GStreamerSurfaceView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public GStreamerSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GStreamerSurfaceView (Context context) { + super(context); + } + + // Called by the layout manager to find out our size and give us some rules. + // We will try to maximize our size, and preserve the media's aspect ratio if + // we are given the freedom to do so. + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0, height = 0; + int wmode = View.MeasureSpec.getMode(widthMeasureSpec); + int hmode = View.MeasureSpec.getMode(heightMeasureSpec); + int wsize = View.MeasureSpec.getSize(widthMeasureSpec); + int hsize = View.MeasureSpec.getSize(heightMeasureSpec); + + Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height); + // Obey width rules + switch (wmode) { + case View.MeasureSpec.AT_MOST: + if (hmode == View.MeasureSpec.EXACTLY) { + width = Math.min(hsize * media_width / media_height, wsize); + break; + } + case View.MeasureSpec.EXACTLY: + width = wsize; + break; + case View.MeasureSpec.UNSPECIFIED: + width = media_width; + } + + // Obey height rules + switch (hmode) { + case View.MeasureSpec.AT_MOST: + if (wmode == View.MeasureSpec.EXACTLY) { + height = Math.min(wsize * media_height / media_width, hsize); + break; + } + case View.MeasureSpec.EXACTLY: + height = hsize; + break; + case View.MeasureSpec.UNSPECIFIED: + height = media_height; + } + + // Finally, calculate best size when both axis are free + if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) { + int correct_height = width * media_height / media_width; + int correct_width = height * media_width / media_height; + + if (correct_height < height) + height = correct_height; + else + width = correct_width; + } + + // Obey minimum size + width = Math.max (getSuggestedMinimumWidth(), width); + height = Math.max (getSuggestedMinimumHeight(), height); + setMeasuredDimension(width, height); + } + +} diff --git a/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4.java b/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4.java new file mode 100644 index 0000000000..083448a769 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-4/src/org/freedesktop/gstreamer/tutorials/tutorial_4/Tutorial4.java @@ -0,0 +1,250 @@ +package org.freedesktop.gstreamer.tutorials.tutorial_4; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; + +import org.freedesktop.gstreamer.GStreamer; + +public class Tutorial4 extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener { + private native void nativeInit(); // Initialize native code, build pipeline, etc + private native void nativeFinalize(); // Destroy pipeline and shutdown native code + private native void nativeSetUri(String uri); // Set the URI of the media to play + private native void nativePlay(); // Set pipeline to PLAYING + private native void nativeSetPosition(int milliseconds); // Seek to the indicated position, in milliseconds + private native void nativePause(); // Set pipeline to PAUSED + private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks + private native void nativeSurfaceInit(Object surface); // A new surface is available + private native void nativeSurfaceFinalize(); // Surface about to be destroyed + private long native_custom_data; // Native code will use this to keep private data + + private boolean is_playing_desired; // Whether the user asked to go to PLAYING + private int position; // Current position, reported by native code + private int duration; // Current clip duration, reported by native code + private boolean is_local_media; // Whether this clip is stored locally or is being streamed + private int desired_position; // Position where the users wants to seek to + private String mediaUri; // URI of the clip being played + + private final String defaultMediaUri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.ogv"; + + // Called when the activity is first created. + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Initialize GStreamer and warn if it fails + try { + GStreamer.init(this); + } catch (Exception e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + setContentView(R.layout.main); + + ImageButton play = (ImageButton) this.findViewById(R.id.button_play); + play.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = true; + nativePlay(); + } + }); + + ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop); + pause.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + is_playing_desired = false; + nativePause(); + } + }); + + SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); + SurfaceHolder sh = sv.getHolder(); + sh.addCallback(this); + + SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); + sb.setOnSeekBarChangeListener(this); + + // Retrieve our previous state, or initialize it to default values + if (savedInstanceState != null) { + is_playing_desired = savedInstanceState.getBoolean("playing"); + position = savedInstanceState.getInt("position"); + duration = savedInstanceState.getInt("duration"); + mediaUri = savedInstanceState.getString("mediaUri"); + Log.i ("GStreamer", "Activity created with saved state:"); + } else { + is_playing_desired = false; + position = duration = 0; + mediaUri = defaultMediaUri; + Log.i ("GStreamer", "Activity created with no saved state:"); + } + is_local_media = false; + Log.i ("GStreamer", " playing:" + is_playing_desired + " position:" + position + + " duration: " + duration + " uri: " + mediaUri); + + // Start with disabled buttons, until native code is initialized + this.findViewById(R.id.button_play).setEnabled(false); + this.findViewById(R.id.button_stop).setEnabled(false); + + nativeInit(); + } + + protected void onSaveInstanceState (Bundle outState) { + Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired + " position:" + position + + " duration: " + duration + " uri: " + mediaUri); + outState.putBoolean("playing", is_playing_desired); + outState.putInt("position", position); + outState.putInt("duration", duration); + outState.putString("mediaUri", mediaUri); + } + + protected void onDestroy() { + nativeFinalize(); + super.onDestroy(); + } + + // Called from native code. This sets the content of the TextView from the UI thread. + private void setMessage(final String message) { + final TextView tv = (TextView) this.findViewById(R.id.textview_message); + runOnUiThread (new Runnable() { + public void run() { + tv.setText(message); + } + }); + } + + // Set the URI to play, and record whether it is a local or remote file + private void setMediaUri() { + nativeSetUri (mediaUri); + is_local_media = mediaUri.startsWith("file://"); + } + + // Called from native code. Native code calls this once it has created its pipeline and + // the main loop is running, so it is ready to accept commands. + private void onGStreamerInitialized () { + Log.i ("GStreamer", "GStreamer initialized:"); + Log.i ("GStreamer", " playing:" + is_playing_desired + " position:" + position + " uri: " + mediaUri); + + // Restore previous playing state + setMediaUri (); + nativeSetPosition (position); + if (is_playing_desired) { + nativePlay(); + } else { + nativePause(); + } + + // Re-enable buttons, now that GStreamer is initialized + final Activity activity = this; + runOnUiThread(new Runnable() { + public void run() { + activity.findViewById(R.id.button_play).setEnabled(true); + activity.findViewById(R.id.button_stop).setEnabled(true); + } + }); + } + + // The text widget acts as an slave for the seek bar, so it reflects what the seek bar shows, whether + // it is an actual pipeline position or the position the user is currently dragging to. + private void updateTimeWidget () { + final TextView tv = (TextView) this.findViewById(R.id.textview_time); + final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); + final int pos = sb.getProgress(); + + SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + final String message = df.format(new Date (pos)) + " / " + df.format(new Date (duration)); + tv.setText(message); + } + + // Called from native code + private void setCurrentPosition(final int position, final int duration) { + final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar); + + // Ignore position messages from the pipeline if the seek bar is being dragged + if (sb.isPressed()) return; + + runOnUiThread (new Runnable() { + public void run() { + sb.setMax(duration); + sb.setProgress(position); + updateTimeWidget(); + } + }); + this.position = position; + this.duration = duration; + } + + static { + System.loadLibrary("gstreamer_android"); + System.loadLibrary("tutorial-4"); + nativeClassInit(); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d("GStreamer", "Surface changed to format " + format + " width " + + width + " height " + height); + nativeSurfaceInit (holder.getSurface()); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.d("GStreamer", "Surface created: " + holder.getSurface()); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d("GStreamer", "Surface destroyed"); + nativeSurfaceFinalize (); + } + + // Called from native code when the size of the media changes or is first detected. + // Inform the video surface about the new size and recalculate the layout. + private void onMediaSizeChanged (int width, int height) { + Log.i ("GStreamer", "Media size changed to " + width + "x" + height); + final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video); + gsv.media_width = width; + gsv.media_height = height; + runOnUiThread(new Runnable() { + public void run() { + gsv.requestLayout(); + } + }); + } + + // The Seek Bar thumb has moved, either because the user dragged it or we have called setProgress() + public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) { + if (fromUser == false) return; + desired_position = progress; + // If this is a local file, allow scrub seeking, this is, seek as soon as the slider is moved. + if (is_local_media) nativeSetPosition(desired_position); + updateTimeWidget(); + } + + // The user started dragging the Seek Bar thumb + public void onStartTrackingTouch(SeekBar sb) { + nativePause(); + } + + // The user released the Seek Bar thumb + public void onStopTrackingTouch(SeekBar sb) { + // If this is a remote file, scrub seeking is probably not going to work smoothly enough. + // Therefore, perform only the seek when the slider is released. + if (!is_local_media) nativeSetPosition(desired_position); + if (is_playing_desired) nativePlay(); + } +} diff --git a/examples/tutorials/android/android-tutorial-5/.gitignore b/examples/tutorials/android/android-tutorial-5/.gitignore new file mode 100644 index 0000000000..f34cf307c0 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/.gitignore @@ -0,0 +1,4 @@ +.externalNativeBuild/ +assets/ +gst-build-*/ +src/org/ diff --git a/examples/tutorials/android/android-tutorial-5/AndroidManifest.xml b/examples/tutorials/android/android-tutorial-5/AndroidManifest.xml new file mode 100755 index 0000000000..a43b75fad9 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/AndroidManifest.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/tutorials/android/android-tutorial-5/build.gradle b/examples/tutorials/android/android-tutorial-5/build.gradle new file mode 100644 index 0000000000..10230fbb4d --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "org.freedesktop.gstreamer.tutorials.tutorial_5" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + + externalNativeBuild { + ndkBuild { + def gstRoot + + if (project.hasProperty('gstAndroidRoot')) + gstRoot = project.gstAndroidRoot + else + gstRoot = System.env.GSTREAMER_ROOT_ANDROID + + if (gstRoot == null) + throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries') + + arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets" + + targets "tutorial-5" + + // All archs except MIPS and MIPS64 are supported + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + } + } + + externalNativeBuild { + ndkBuild { + path 'jni/Android.mk' + } + } +} + +afterEvaluate { + if (project.hasProperty('compileDebugJavaWithJavac')) + project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug' + if (project.hasProperty('compileReleaseJavaWithJavac')) + project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/examples/tutorials/android/android-tutorial-5/jni/Android.mk b/examples/tutorials/android/android-tutorial-5/jni/Android.mk new file mode 100755 index 0000000000..ca00251a1f --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := tutorial-5 +LOCAL_SRC_FILES := tutorial-5.c dummy.cpp +LOCAL_SHARED_LIBRARIES := gstreamer_android +LOCAL_LDLIBS := -llog -landroid +include $(BUILD_SHARED_LIBRARY) + +ifndef GSTREAMER_ROOT_ANDROID +$(error GSTREAMER_ROOT_ANDROID is not defined!) +endif + +ifeq ($(TARGET_ARCH_ABI),armeabi) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm +else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7 +else ifeq ($(TARGET_ARCH_ABI),arm64-v8a) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64 +else ifeq ($(TARGET_ARCH_ABI),x86) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86 +else ifeq ($(TARGET_ARCH_ABI),x86_64) +GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64 +else +$(error Target arch ABI not supported: $(TARGET_ARCH_ABI)) +endif + +GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ +include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk +GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) +G_IO_MODULES := openssl +GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 +include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk diff --git a/examples/tutorials/android/android-tutorial-5/jni/Application.mk b/examples/tutorials/android/android-tutorial-5/jni/Application.mk new file mode 100644 index 0000000000..1f4ab316f5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64 +APP_STL = c++_shared \ No newline at end of file diff --git a/examples/tutorials/android/android-tutorial-5/jni/dummy.cpp b/examples/tutorials/android/android-tutorial-5/jni/dummy.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/android/android-tutorial-5/jni/tutorial-5.c b/examples/tutorials/android/android-tutorial-5/jni/tutorial-5.c new file mode 100755 index 0000000000..2a2a7ec7b9 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/jni/tutorial-5.c @@ -0,0 +1,683 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (debug_category); +#define GST_CAT_DEFAULT debug_category + +/* + * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into + * a jlong, which is always 64 bits, without warnings. + */ +#if GLIB_SIZEOF_VOID_P == 8 +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) +#else +# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) +# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) +#endif + +/* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably + * confuse some demuxers. */ +#define SEEK_MIN_DELAY (500 * GST_MSECOND) + +/* Structure to contain all our information, so we can pass it to callbacks */ +typedef struct _CustomData +{ + jobject app; /* Application instance, used to call its methods. A global reference is kept. */ + GstElement *pipeline; /* The running pipeline */ + GMainContext *context; /* GLib context used to run the main loop */ + GMainLoop *main_loop; /* GLib main loop */ + gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ + ANativeWindow *native_window; /* The Android native window where video will be rendered */ + GstState state; /* Current pipeline state */ + GstState target_state; /* Desired pipeline state, to be set once buffering is complete */ + gint64 duration; /* Cached clip duration */ + gint64 desired_position; /* Position to seek to, once the pipeline is running */ + GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */ + gboolean is_live; /* Live streams do not use buffering */ +} CustomData; + +/* playbin2 flags */ +typedef enum +{ + GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */ +} GstPlayFlags; + +/* These global variables cache values which are not changing during execution */ +static pthread_t gst_app_thread; +static pthread_key_t current_jni_env; +static JavaVM *java_vm; +static jfieldID custom_data_field_id; +static jmethodID set_message_method_id; +static jmethodID set_current_position_method_id; +static jmethodID on_gstreamer_initialized_method_id; +static jmethodID on_media_size_changed_method_id; + +/* + * Private methods + */ + +/* Register this thread with the VM */ +static JNIEnv * +attach_current_thread (void) +{ + JNIEnv *env; + JavaVMAttachArgs args; + + GST_DEBUG ("Attaching thread %p", g_thread_self ()); + args.version = JNI_VERSION_1_4; + args.name = NULL; + args.group = NULL; + + if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { + GST_ERROR ("Failed to attach current thread"); + return NULL; + } + + return env; +} + +/* Unregister this thread from the VM */ +static void +detach_current_thread (void *env) +{ + GST_DEBUG ("Detaching thread %p", g_thread_self ()); + (*java_vm)->DetachCurrentThread (java_vm); +} + +/* Retrieve the JNI environment for this thread */ +static JNIEnv * +get_jni_env (void) +{ + JNIEnv *env; + + if ((env = pthread_getspecific (current_jni_env)) == NULL) { + env = attach_current_thread (); + pthread_setspecific (current_jni_env, env); + } + + return env; +} + +/* Change the content of the UI's TextView */ +static void +set_ui_message (const gchar * message, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GST_DEBUG ("Setting message to: %s", message); + jstring jmessage = (*env)->NewStringUTF (env, message); + (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + (*env)->DeleteLocalRef (env, jmessage); +} + +/* Tell the application what is the current position and clip duration */ +static void +set_current_ui_position (gint position, gint duration, CustomData * data) +{ + JNIEnv *env = get_jni_env (); + (*env)->CallVoidMethod (env, data->app, set_current_position_method_id, + position, duration); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } +} + +/* If we have pipeline and it is running, query the current position and clip duration and inform + * the application */ +static gboolean +refresh_ui (CustomData * data) +{ + gint64 current = -1; + gint64 position; + + /* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */ + if (!data || !data->pipeline || data->state < GST_STATE_PAUSED) + return TRUE; + + /* If we didn't know it yet, query the stream duration */ + if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { + if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, + &data->duration)) { + GST_WARNING + ("Could not query current duration (normal for still pictures)"); + data->duration = 0; + } + } + + if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) { + GST_WARNING + ("Could not query current position (normal for still pictures)"); + position = 0; + } + + /* Java expects these values in milliseconds, and GStreamer provides nanoseconds */ + set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND, + data); + return TRUE; +} + +/* Forward declaration for the delayed seek callback */ +static gboolean delayed_seek_cb (CustomData * data); + +/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for + * some time in the future. */ +static void +execute_seek (gint64 desired_position, CustomData * data) +{ + gint64 diff; + + if (desired_position == GST_CLOCK_TIME_NONE) + return; + + diff = gst_util_get_timestamp () - data->last_seek_time; + + if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) { + /* The previous seek was too close, delay this one */ + GSource *timeout_source; + + if (data->desired_position == GST_CLOCK_TIME_NONE) { + /* There was no previous seek scheduled. Setup a timer for some time in the future */ + timeout_source = + g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND); + g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb, + data, NULL); + g_source_attach (timeout_source, data->context); + g_source_unref (timeout_source); + } + /* Update the desired seek position. If multiple petitions are received before it is time + * to perform a seek, only the last one is remembered. */ + data->desired_position = desired_position; + GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" + GST_TIME_FORMAT, GST_TIME_ARGS (desired_position), + GST_TIME_ARGS (SEEK_MIN_DELAY - diff)); + } else { + /* Perform the seek now */ + GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, + GST_TIME_ARGS (desired_position)); + data->last_seek_time = gst_util_get_timestamp (); + gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position); + data->desired_position = GST_CLOCK_TIME_NONE; + } +} + +/* Delayed seek callback. This gets called by the timer setup in the above function. */ +static gboolean +delayed_seek_cb (CustomData * data) +{ + GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, + GST_TIME_ARGS (data->desired_position)); + execute_seek (data->desired_position, data); + return FALSE; +} + +/* Retrieve errors from the bus and show them on the UI */ +static void +error_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GError *err; + gchar *debug_info; + gchar *message_string; + + gst_message_parse_error (msg, &err, &debug_info); + message_string = + g_strdup_printf ("Error received from element %s: %s", + GST_OBJECT_NAME (msg->src), err->message); + g_clear_error (&err); + g_free (debug_info); + set_ui_message (message_string, data); + g_free (message_string); + data->target_state = GST_STATE_NULL; + gst_element_set_state (data->pipeline, GST_STATE_NULL); +} + +/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */ +static void +eos_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + data->target_state = GST_STATE_PAUSED; + data->is_live |= + (gst_element_set_state (data->pipeline, + GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); + execute_seek (0, data); +} + +/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */ +static void +duration_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + data->duration = GST_CLOCK_TIME_NONE; +} + +/* Called when buffering messages are received. We inform the UI about the current buffering level and + * keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */ +static void +buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + gint percent; + + if (data->is_live) + return; + + gst_message_parse_buffering (msg, &percent); + if (percent < 100 && data->target_state >= GST_STATE_PAUSED) { + gchar *message_string = g_strdup_printf ("Buffering %d%%", percent); + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); + set_ui_message (message_string, data); + g_free (message_string); + } else if (data->target_state >= GST_STATE_PLAYING) { + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); + } else if (data->target_state >= GST_STATE_PAUSED) { + set_ui_message ("Buffering complete", data); + } +} + +/* Called when the clock is lost */ +static void +clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + if (data->target_state >= GST_STATE_PLAYING) { + gst_element_set_state (data->pipeline, GST_STATE_PAUSED); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); + } +} + +/* Retrieve the video sink's Caps and tell the application about the media size */ +static void +check_media_size (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + GstElement *video_sink; + GstPad *video_sink_pad; + GstCaps *caps; + GstVideoInfo info; + + /* Retrieve the Caps at the entrance of the video sink */ + g_object_get (data->pipeline, "video-sink", &video_sink, NULL); + video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); + caps = gst_pad_get_current_caps (video_sink_pad); + + if (gst_video_info_from_caps (&info, caps)) { + info.width = info.width * info.par_n / info.par_d; + GST_DEBUG ("Media size is %dx%d, notifying application", info.width, + info.height); + + (*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id, + (jint) info.width, (jint) info.height); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + } + + gst_caps_unref (caps); + gst_object_unref (video_sink_pad); + gst_object_unref (video_sink); +} + +/* Notify UI about pipeline state changes */ +static void +state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) +{ + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + /* Only pay attention to messages coming from the pipeline, not its children */ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { + data->state = new_state; + gchar *message = g_strdup_printf ("State changed to %s", + gst_element_state_get_name (new_state)); + set_ui_message (message, data); + g_free (message); + + if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY) + data->is_live = FALSE; + + /* The Ready to Paused state change is particularly interesting: */ + if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) { + /* By now the sink already knows the media size */ + check_media_size (data); + + /* If there was a scheduled seek, perform it now that we have moved to the Paused state */ + if (GST_CLOCK_TIME_IS_VALID (data->desired_position)) + execute_seek (data->desired_position, data); + } + } +} + +/* Check if all conditions are met to report GStreamer as initialized. + * These conditions will change depending on the application */ +static void +check_initialization_complete (CustomData * data) +{ + JNIEnv *env = get_jni_env (); + if (!data->initialized && data->native_window && data->main_loop) { + GST_DEBUG + ("Initialization complete, notifying application. native_window:%p main_loop:%p", + data->native_window, data->main_loop); + + /* The main loop is running and we received a native window, inform the sink about it */ + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), + (guintptr) data->native_window); + + (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); + if ((*env)->ExceptionCheck (env)) { + GST_ERROR ("Failed to call Java method"); + (*env)->ExceptionClear (env); + } + data->initialized = TRUE; + } +} + +/* Main method for the native code. This is executed on its own thread. */ +static void * +app_function (void *userdata) +{ + JavaVMAttachArgs args; + GstBus *bus; + CustomData *data = (CustomData *) userdata; + GSource *timeout_source; + GSource *bus_source; + GError *error = NULL; + guint flags; + + GST_DEBUG ("Creating pipeline in CustomData at %p", data); + + /* Create our own GLib Main Context and make it the default one */ + data->context = g_main_context_new (); + g_main_context_push_thread_default (data->context); + + /* Build pipeline */ + data->pipeline = gst_parse_launch ("playbin", &error); + if (error) { + gchar *message = + g_strdup_printf ("Unable to build pipeline: %s", error->message); + g_clear_error (&error); + set_ui_message (message, data); + g_free (message); + return NULL; + } + + /* Disable subtitles */ + g_object_get (data->pipeline, "flags", &flags, NULL); + flags &= ~GST_PLAY_FLAG_TEXT; + g_object_set (data->pipeline, "flags", flags, NULL); + + /* Set the pipeline to READY, so it can already accept a window handle, if we have one */ + data->target_state = GST_STATE_READY; + gst_element_set_state (data->pipeline, GST_STATE_READY); + + /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ + bus = gst_element_get_bus (data->pipeline); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, data->context); + g_source_unref (bus_source); + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + data); + g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, data); + g_signal_connect (G_OBJECT (bus), "message::duration", + (GCallback) duration_cb, data); + g_signal_connect (G_OBJECT (bus), "message::buffering", + (GCallback) buffering_cb, data); + g_signal_connect (G_OBJECT (bus), "message::clock-lost", + (GCallback) clock_lost_cb, data); + gst_object_unref (bus); + + /* Register a function that GLib will call 4 times per second */ + timeout_source = g_timeout_source_new (250); + g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL); + g_source_attach (timeout_source, data->context); + g_source_unref (timeout_source); + + /* Create a GLib Main Loop and set it to run */ + GST_DEBUG ("Entering main loop... (CustomData:%p)", data); + data->main_loop = g_main_loop_new (data->context, FALSE); + check_initialization_complete (data); + g_main_loop_run (data->main_loop); + GST_DEBUG ("Exited main loop"); + g_main_loop_unref (data->main_loop); + data->main_loop = NULL; + + /* Free resources */ + g_main_context_pop_thread_default (data->context); + g_main_context_unref (data->context); + data->target_state = GST_STATE_NULL; + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->pipeline); + + return NULL; +} + +/* + * Java Bindings + */ + +/* Instruct the native code to create its internal data structure, pipeline and thread */ +static void +gst_native_init (JNIEnv * env, jobject thiz) +{ + CustomData *data = g_new0 (CustomData, 1); + data->desired_position = GST_CLOCK_TIME_NONE; + data->last_seek_time = GST_CLOCK_TIME_NONE; + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); + GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-5", 0, + "Android tutorial 5"); + gst_debug_set_threshold_for_name ("tutorial-5", GST_LEVEL_DEBUG); + GST_DEBUG ("Created CustomData at %p", data); + data->app = (*env)->NewGlobalRef (env, thiz); + GST_DEBUG ("Created GlobalRef for app object at %p", data->app); + pthread_create (&gst_app_thread, NULL, &app_function, data); +} + +/* Quit the main loop, remove the native thread and free resources */ +static void +gst_native_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Quitting main loop..."); + g_main_loop_quit (data->main_loop); + GST_DEBUG ("Waiting for thread to finish..."); + pthread_join (gst_app_thread, NULL); + GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); + (*env)->DeleteGlobalRef (env, data->app); + GST_DEBUG ("Freeing CustomData at %p", data); + g_free (data); + SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); + GST_DEBUG ("Done finalizing"); +} + +/* Set playbin2's URI */ +void +gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data || !data->pipeline) + return; + const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL); + GST_DEBUG ("Setting URI to %s", char_uri); + if (data->target_state >= GST_STATE_READY) + gst_element_set_state (data->pipeline, GST_STATE_READY); + g_object_set (data->pipeline, "uri", char_uri, NULL); + (*env)->ReleaseStringUTFChars (env, uri, char_uri); + data->duration = GST_CLOCK_TIME_NONE; + data->is_live |= + (gst_element_set_state (data->pipeline, + data->target_state) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Set pipeline to PLAYING state */ +static void +gst_native_play (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PLAYING"); + data->target_state = GST_STATE_PLAYING; + data->is_live |= + (gst_element_set_state (data->pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Set pipeline to PAUSED state */ +static void +gst_native_pause (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Setting state to PAUSED"); + data->target_state = GST_STATE_PAUSED; + data->is_live |= + (gst_element_set_state (data->pipeline, + GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL); +} + +/* Instruct the pipeline to seek to a different position */ +void +gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + gint64 desired_position = (gint64) (milliseconds * GST_MSECOND); + if (data->state >= GST_STATE_PAUSED) { + execute_seek (desired_position, data); + } else { + GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", + GST_TIME_ARGS (desired_position)); + data->desired_position = desired_position; + } +} + +/* Static class initializer: retrieve method and field IDs */ +static jboolean +gst_native_class_init (JNIEnv * env, jclass klass) +{ + custom_data_field_id = + (*env)->GetFieldID (env, klass, "native_custom_data", "J"); + set_message_method_id = + (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); + set_current_position_method_id = + (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V"); + on_gstreamer_initialized_method_id = + (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); + on_media_size_changed_method_id = + (*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V"); + + if (!custom_data_field_id || !set_message_method_id + || !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id + || !set_current_position_method_id) { + /* We emit this message through the Android log instead of the GStreamer log because the later + * has not been initialized yet. + */ + __android_log_print (ANDROID_LOG_ERROR, "tutorial-4", + "The calling class does not implement all necessary interface methods"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +static void +gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface); + GST_DEBUG ("Received surface %p (native window %p)", surface, + new_native_window); + + if (data->native_window) { + ANativeWindow_release (data->native_window); + if (data->native_window == new_native_window) { + GST_DEBUG ("New native window is the same as the previous one %p", + data->native_window); + if (data->pipeline) { + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline)); + gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline)); + } + return; + } else { + GST_DEBUG ("Released previous native window %p", data->native_window); + data->initialized = FALSE; + } + } + data->native_window = new_native_window; + + check_initialization_complete (data); +} + +static void +gst_native_surface_finalize (JNIEnv * env, jobject thiz) +{ + CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); + if (!data) + return; + GST_DEBUG ("Releasing Native Window %p", data->native_window); + + if (data->pipeline) { + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), + (guintptr) NULL); + gst_element_set_state (data->pipeline, GST_STATE_READY); + } + + ANativeWindow_release (data->native_window); + data->native_window = NULL; + data->initialized = FALSE; +} + +/* List of implemented native methods */ +static JNINativeMethod native_methods[] = { + {"nativeInit", "()V", (void *) gst_native_init}, + {"nativeFinalize", "()V", (void *) gst_native_finalize}, + {"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri}, + {"nativePlay", "()V", (void *) gst_native_play}, + {"nativePause", "()V", (void *) gst_native_pause}, + {"nativeSetPosition", "(I)V", (void *) gst_native_set_position}, + {"nativeSurfaceInit", "(Ljava/lang/Object;)V", + (void *) gst_native_surface_init}, + {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize}, + {"nativeClassInit", "()Z", (void *) gst_native_class_init} +}; + +/* Library initializer */ +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + java_vm = vm; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "tutorial-5", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, + "org/freedesktop/gstreamer/tutorials/tutorial_5/Tutorial5"); + (*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods)); + + pthread_key_create (¤t_jni_env, detach_current_thread); + + return JNI_VERSION_1_4; +} diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/file.png b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/file.png new file mode 100644 index 0000000000..6a64f0e329 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/file.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/folder.png b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/folder.png new file mode 100644 index 0000000000..d54f034e87 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/folder.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/gstreamer_logo_5.png b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/gstreamer_logo_5.png new file mode 100644 index 0000000000..d8cd8ea023 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-ldpi/gstreamer_logo_5.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-mdpi/gstreamer_logo_5.png b/examples/tutorials/android/android-tutorial-5/res/drawable-mdpi/gstreamer_logo_5.png new file mode 100644 index 0000000000..2e2e776413 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-mdpi/gstreamer_logo_5.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-xhdpi/gstreamer_logo_5.png b/examples/tutorials/android/android-tutorial-5/res/drawable-xhdpi/gstreamer_logo_5.png new file mode 100644 index 0000000000..b72499ee2e Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-xhdpi/gstreamer_logo_5.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-xxhdpi/gstreamer_logo_5.png b/examples/tutorials/android/android-tutorial-5/res/drawable-xxhdpi/gstreamer_logo_5.png new file mode 100644 index 0000000000..e420415121 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-xxhdpi/gstreamer_logo_5.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/drawable-xxxhdpi/gstreamer_logo_5.png b/examples/tutorials/android/android-tutorial-5/res/drawable-xxxhdpi/gstreamer_logo_5.png new file mode 100644 index 0000000000..093d4c2616 Binary files /dev/null and b/examples/tutorials/android/android-tutorial-5/res/drawable-xxxhdpi/gstreamer_logo_5.png differ diff --git a/examples/tutorials/android/android-tutorial-5/res/layout/file_dialog_main.xml b/examples/tutorials/android/android-tutorial-5/res/layout/file_dialog_main.xml new file mode 100644 index 0000000000..3e33d06bc5 --- /dev/null +++ b/examples/tutorials/android/android-tutorial-5/res/layout/file_dialog_main.xml @@ -0,0 +1,39 @@ + + + + + + + + + +