mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-19 14:56:36 +00:00
Merging gst-plugins-good
This commit is contained in:
commit
2217dc11ee
1488 changed files with 731113 additions and 0 deletions
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
*~
|
||||
*.bak
|
||||
|
||||
Build
|
||||
*.user
|
||||
*.suo
|
||||
*.ipch
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.DS_Store
|
||||
|
||||
# Meson
|
||||
/build
|
||||
/_build
|
||||
/subprojects
|
1
.gitlab-ci.yml
Normal file
1
.gitlab-ci.yml
Normal file
|
@ -0,0 +1 @@
|
|||
include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"
|
21
AUTHORS
Normal file
21
AUTHORS
Normal file
|
@ -0,0 +1,21 @@
|
|||
Erik Walthinsen <omega@temple-baptist.com>
|
||||
Matt Howell <mhowell@users.sourceforge.net>
|
||||
Brent Bradburn <bbradburn@users.sourceforge.net>
|
||||
Wim Taymans <wim.taymans@chello.be>
|
||||
Richard Boulton <richard@tartarus.org>
|
||||
Zaheer Abbas Merali <zaheerabbas at merali dot org>
|
||||
David I. Lehn <dlehn@users.sourceforge.net>
|
||||
Chris Emerson <chris@tartarus.org>
|
||||
Jens Thiele <karme@unforgettable.com>
|
||||
Thomas Nyberg <thomas@codefactory.se>
|
||||
Bastien Nocera <hadess@hadess.net>
|
||||
Christian Fredrik Kalager Schaller <Uraeus@linuxrising.org>
|
||||
Thomas Vander Stichele <thomas@apestaart.org>
|
||||
Andy Wingo <wingo@pobox.com>
|
||||
Cameron Hutchison <camh@xdna.net>
|
||||
David Schleef <ds@schleef.org>
|
||||
Benjamin Otte <in7y118@public.uni-hamburg.de>
|
||||
Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||
Julien MOUTTE <julien@moutte.net>
|
||||
Jan Schmidt <thaytan@mad.scientist.com>
|
||||
Arwed v. Merkatz <v.merkatz@gmx.net>
|
504
COPYING
Normal file
504
COPYING
Normal file
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
12
MAINTAINERS
Normal file
12
MAINTAINERS
Normal file
|
@ -0,0 +1,12 @@
|
|||
GStreamer is currently maintained by the consensus of a number
|
||||
of people, including, but not limited to:
|
||||
|
||||
Jan Schmidt <thaytan@noraisin.net>
|
||||
Wim Taymans <wim.taymans@gmail.com>
|
||||
David Schleef <ds@schleef.org>
|
||||
Tim-Philipp Müller <tim centricular net>
|
||||
Sebastian Dröge <slomo@coaxion.net>
|
||||
|
||||
Maintainer-related issues should be addressed to:
|
||||
|
||||
gstreamer-devel@lists.freedesktop.org
|
299
NEWS
Normal file
299
NEWS
Normal file
|
@ -0,0 +1,299 @@
|
|||
GStreamer 1.20 Release Notes
|
||||
|
||||
GStreamer 1.20 has not been released yet. It is scheduled for release
|
||||
around October/November 2021.
|
||||
|
||||
1.19.x is the unstable development version that is being developed in
|
||||
the git main branch and which will eventually result in 1.20, and 1.19.2
|
||||
is the current development release in that series
|
||||
|
||||
It is expected that feature freeze will be in early October 2021,
|
||||
followed by one or two 1.19.9x pre-releases and the new 1.20 stable
|
||||
release around October/November 2021.
|
||||
|
||||
1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12,
|
||||
1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series.
|
||||
|
||||
See https://gstreamer.freedesktop.org/releases/1.20/ for the latest
|
||||
version of this document.
|
||||
|
||||
Last updated: Wednesday 22 September 2021, 18:00 UTC (log)
|
||||
|
||||
Introduction
|
||||
|
||||
The GStreamer team is proud to announce a new major feature release in
|
||||
the stable 1.x API series of your favourite cross-platform multimedia
|
||||
framework!
|
||||
|
||||
As always, this release is again packed with many new features, bug
|
||||
fixes and other improvements.
|
||||
|
||||
Highlights
|
||||
|
||||
- this section will be completed in due course
|
||||
|
||||
Major new features and changes
|
||||
|
||||
Noteworthy new features and API
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
New elements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
New element features and additions
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Plugin and library moves
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
- There were no plugin moves or library moves in this cycle.
|
||||
|
||||
Plugin removals
|
||||
|
||||
The following elements or plugins have been removed:
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous API additions
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous performance, latency and memory optimisations
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous other changes and enhancements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Tracing framework and debugging improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Tools
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer RTSP server
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer VAAPI
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer OMX
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Editing Services and NLE
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer validate
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Python Bindings
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer C# Bindings
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Rust Bindings and Rust Plugins
|
||||
|
||||
The GStreamer Rust bindings are released separately with a different
|
||||
release cadence that’s tied to gtk-rs, but the latest release has
|
||||
already been updated for the upcoming new GStreamer 1.20 API.
|
||||
|
||||
gst-plugins-rs, the module containing GStreamer plugins written in Rust,
|
||||
has also seen lots of activity with many new elements and plugins.
|
||||
|
||||
What follows is a list of elements and plugins available in
|
||||
gst-plugins-rs, so people don’t miss out on all those potentially useful
|
||||
elements that have no C equivalent.
|
||||
|
||||
- FIXME: add new elements
|
||||
|
||||
Rust audio plugins
|
||||
|
||||
- audiornnoise: New element for audio denoising which implements the
|
||||
noise removal algorithm of the Xiph RNNoise library, in Rust
|
||||
- rsaudioecho: Port of the audioecho element from gst-plugins-good
|
||||
rsaudioloudnorm: Live audio loudness normalization element based on
|
||||
the FFmpeg af_loudnorm filter
|
||||
- claxondec: FLAC lossless audio codec decoder element based on the
|
||||
pure-Rust claxon implementation
|
||||
- csoundfilter: Audio filter that can use any filter defined via the
|
||||
Csound audio programming language
|
||||
- lewtondec: Vorbis audio decoder element based on the pure-Rust
|
||||
lewton implementation
|
||||
|
||||
Rust video plugins
|
||||
|
||||
- cdgdec/cdgparse: Decoder and parser for the CD+G video codec based
|
||||
on a pure-Rust CD+G implementation, used for example by karaoke CDs
|
||||
- cea608overlay: CEA-608 Closed Captions overlay element
|
||||
- cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT
|
||||
subtitles) converter
|
||||
- tttocea608: CEA-608 Closed Captions from timed-text converter
|
||||
- mccenc/mccparse: MacCaption Closed Caption format encoder and parser
|
||||
- sccenc/sccparse: Scenarist Closed Caption format encoder and parser
|
||||
- dav1dec: AV1 video decoder based on the dav1d decoder implementation
|
||||
by the VLC project
|
||||
- rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e
|
||||
encoder implementation
|
||||
- rsflvdemux: Alternative to the flvdemux FLV demuxer element from
|
||||
gst-plugins-good, not feature-equivalent yet
|
||||
- rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust
|
||||
implementations by the image-rs project
|
||||
|
||||
Rust text plugins
|
||||
|
||||
- textwrap: Element for line-wrapping timed text (e.g. subtitles) for
|
||||
better screen-fitting, including hyphenation support for some
|
||||
languages
|
||||
|
||||
Rust network plugins
|
||||
|
||||
- reqwesthttpsrc: HTTP(S) source element based on the Rust
|
||||
reqwest/hyper HTTP implementations and almost feature-equivalent
|
||||
with the main GStreamer HTTP source souphttpsrc
|
||||
- s3src/s3sink: Source/sink element for the Amazon S3 cloud storage
|
||||
- awstranscriber: Live audio to timed text transcription element using
|
||||
the Amazon AWS Transcribe API
|
||||
|
||||
Generic Rust plugins
|
||||
|
||||
- sodiumencrypter/sodiumdecrypter: Encryption/decryption element based
|
||||
on libsodium/NaCl
|
||||
- togglerecord: Recording element that allows to pause/resume
|
||||
recordings easily and considers keyframe boundaries
|
||||
- fallbackswitch/fallbacksrc: Elements for handling potentially
|
||||
failing (network) sources, restarting them on errors/timeout and
|
||||
showing a fallback stream instead
|
||||
- threadshare: Set of elements that provide alternatives for various
|
||||
existing GStreamer elements but allow to share the streaming threads
|
||||
between each other to reduce the number of threads
|
||||
- rsfilesrc/rsfilesink: File source/sink elements as replacements for
|
||||
the existing filesrc/filesink elements
|
||||
|
||||
Build and Dependencies
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
gst-build
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Cerbero
|
||||
|
||||
Cerbero is a meta build system used to build GStreamer plus dependencies
|
||||
on platforms where dependencies are not readily available, such as
|
||||
Windows, Android, iOS and macOS.
|
||||
|
||||
General improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
macOS / iOS
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows MSI installer
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Linux
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Android
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Platform-specific changes and improvements
|
||||
|
||||
Android
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
macOS and iOS
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Linux
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Documentation improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Possibly Breaking Changes
|
||||
|
||||
- this section will be filled in in due course
|
||||
- MPEG-TS SCTE-35 API changes (FIXME: flesh out)
|
||||
- gst_parse_launch() and friends now error out on non-existing
|
||||
properties on top-level bins where they would silently fail and
|
||||
ignore those before.
|
||||
|
||||
Known Issues
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
- There are a couple of known WebRTC-related regressions/blockers:
|
||||
|
||||
- webrtc: DTLS setup with Chrome is broken
|
||||
- webrtcbin: First keyframe is usually lost
|
||||
|
||||
Contributors
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
… and many others who have contributed bug reports, translations, sent
|
||||
suggestions or helped testing.
|
||||
|
||||
Stable 1.20 branch
|
||||
|
||||
After the 1.20.0 release there will be several 1.20.x bug-fix releases
|
||||
which will contain bug fixes which have been deemed suitable for a
|
||||
stable branch, but no new features or intrusive changes will be added to
|
||||
a bug-fix release usually. The 1.20.x bug-fix releases will be made from
|
||||
the git 1.20 branch, which will be a stable branch.
|
||||
|
||||
1.20.0
|
||||
|
||||
1.20.0 is scheduled to be released around October/November 2021.
|
||||
|
||||
Schedule for 1.22
|
||||
|
||||
Our next major feature release will be 1.22, and 1.21 will be the
|
||||
unstable development version leading up to the stable 1.22 release. The
|
||||
development of 1.21/1.22 will happen in the git main branch.
|
||||
|
||||
The plan for the 1.22 development cycle is yet to be confirmed.
|
||||
|
||||
1.22 will be backwards-compatible to the stable 1.20, 1.18, 1.16, 1.14,
|
||||
1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0 release series.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
These release notes have been prepared by Tim-Philipp Müller with
|
||||
contributions from …
|
||||
|
||||
License: CC BY-SA 4.0
|
252
README
Normal file
252
README
Normal file
|
@ -0,0 +1,252 @@
|
|||
GStreamer 1.19.x development series
|
||||
|
||||
WHAT IT IS
|
||||
----------
|
||||
|
||||
This is GStreamer, a framework for streaming media.
|
||||
|
||||
WHERE TO START
|
||||
--------------
|
||||
|
||||
We have a website at
|
||||
|
||||
https://gstreamer.freedesktop.org
|
||||
|
||||
Our documentation, including tutorials, API reference and FAQ can be found at
|
||||
|
||||
https://gstreamer.freedesktop.org/documentation/
|
||||
|
||||
You can subscribe to our mailing lists:
|
||||
|
||||
https://lists.freedesktop.org/mailman/listinfo/gstreamer-announce
|
||||
|
||||
https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel
|
||||
|
||||
We track bugs, feature requests and merge requests (patches) in GitLab at
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer/
|
||||
|
||||
You can join us on IRC - #gstreamer on irc.freenode.org
|
||||
|
||||
GStreamer 1.0 series
|
||||
--------------------
|
||||
|
||||
Starring
|
||||
|
||||
GSTREAMER
|
||||
|
||||
The core around which all other modules revolve. Base functionality and
|
||||
libraries, some essential elements, documentation, and testing.
|
||||
|
||||
BASE
|
||||
|
||||
A well-groomed and well-maintained collection of GStreamer plug-ins and
|
||||
elements, spanning the range of possible types of elements one would want
|
||||
to write for GStreamer.
|
||||
|
||||
And introducing, for the first time ever, on the development screen ...
|
||||
|
||||
THE GOOD
|
||||
|
||||
--- "Such ingratitude. After all the times I've saved your life."
|
||||
|
||||
A collection of plug-ins you'd want to have right next to you on the
|
||||
battlefield. Shooting sharp and making no mistakes, these plug-ins have it
|
||||
all: good looks, good code, and good licensing. Documented and dressed up
|
||||
in tests. If you're looking for a role model to base your own plug-in on,
|
||||
here it is.
|
||||
|
||||
If you find a plot hole or a badly lip-synced line of code in them,
|
||||
let us know - it is a matter of honour for us to ensure Blondie doesn't look
|
||||
like he's been walking 100 miles through the desert without water.
|
||||
|
||||
THE UGLY
|
||||
|
||||
--- "When you have to shoot, shoot. Don't talk."
|
||||
|
||||
There are times when the world needs a color between black and white.
|
||||
Quality code to match the good's, but two-timing, backstabbing and ready to
|
||||
sell your freedom down the river. These plug-ins might have a patent noose
|
||||
around their neck, or a lock-up license, or any other problem that makes you
|
||||
think twice about shipping them.
|
||||
|
||||
We don't call them ugly because we like them less. Does a mother love her
|
||||
son less because he's not as pretty as the other ones ? No - she commends
|
||||
him on his great personality. These plug-ins are the life of the party.
|
||||
And we'll still step in and set them straight if you report any unacceptable
|
||||
behaviour - because there are two kinds of people in the world, my friend:
|
||||
those with a rope around their neck and the people who do the cutting.
|
||||
|
||||
THE BAD
|
||||
|
||||
--- "That an accusation?"
|
||||
|
||||
No perfectly groomed moustache or any amount of fine clothing is going to
|
||||
cover up the truth - these plug-ins are Bad with a capital B.
|
||||
They look fine on the outside, and might even appear to get the job done, but
|
||||
at the end of the day they're a black sheep. Without a golden-haired angel
|
||||
to watch over them, they'll probably land in an unmarked grave at the final
|
||||
showdown.
|
||||
|
||||
Don't bug us about their quality - exercise your Free Software rights,
|
||||
patch up the offender and send us the patch on the fastest steed you can
|
||||
steal from the Confederates. Because you see, in this world, there's two
|
||||
kinds of people, my friend: those with loaded guns and those who dig.
|
||||
You dig.
|
||||
|
||||
The Lowdown
|
||||
-----------
|
||||
|
||||
--- "I've never seen so many plug-ins wasted so badly."
|
||||
|
||||
GStreamer Plug-ins has grown so big that it's hard to separate the wheat from
|
||||
the chaff. Also, distributors have brought up issues about the legal status
|
||||
of some of the plug-ins we ship. To remedy this, we've divided the previous
|
||||
set of available plug-ins into four modules:
|
||||
|
||||
- gst-plugins-base: a small and fixed set of plug-ins, covering a wide range
|
||||
of possible types of elements; these are continuously kept up-to-date
|
||||
with any core changes during the development series.
|
||||
|
||||
- We believe distributors can safely ship these plug-ins.
|
||||
- People writing elements should base their code on these elements.
|
||||
- These elements come with examples, documentation, and regression tests.
|
||||
|
||||
- gst-plugins-good: a set of plug-ins that we consider to have good quality
|
||||
code, correct functionality, our preferred license (LGPL for the plug-in
|
||||
code, LGPL or LGPL-compatible for the supporting library).
|
||||
|
||||
- We believe distributors can safely ship these plug-ins.
|
||||
- People writing elements should base their code on these elements.
|
||||
|
||||
- gst-plugins-ugly: a set of plug-ins that have good quality and correct
|
||||
functionality, but distributing them might pose problems. The license
|
||||
on either the plug-ins or the supporting libraries might not be how we'd
|
||||
like. The code might be widely known to present patent problems.
|
||||
|
||||
- Distributors should check if they want/can ship these plug-ins.
|
||||
- People writing elements should base their code on these elements.
|
||||
|
||||
- gst-plugins-bad: a set of plug-ins that aren't up to par compared to the
|
||||
rest. They might be close to being good quality, but they're missing
|
||||
something - be it a good code review, some documentation, a set of tests,
|
||||
a real live maintainer, or some actual wide use.
|
||||
If the blanks are filled in they might be upgraded to become part of
|
||||
either gst-plugins-good or gst-plugins-ugly, depending on the other factors.
|
||||
|
||||
- If the plug-ins break, you can't complain - instead, you can fix the
|
||||
problem and send us a patch, or bribe someone into fixing them for you.
|
||||
- New contributors can start here for things to work on.
|
||||
|
||||
PLATFORMS
|
||||
---------
|
||||
|
||||
- Linux is of course fully supported
|
||||
- FreeBSD is reported to work; other BSDs should work too; same for Solaris
|
||||
- MacOS works, binary 1.x packages can be built using the cerbero build tool
|
||||
- Windows works; binary 1.x packages can be built using the cerbero build tool
|
||||
- MSys/MinGW builds
|
||||
- Microsoft Visual Studio builds are also available and supported
|
||||
- Android works, binary 1.x packages can be built using the cerbero build tool
|
||||
- iOS works
|
||||
|
||||
INSTALLING FROM PACKAGES
|
||||
------------------------
|
||||
|
||||
You should always prefer installing from packages first. GStreamer is
|
||||
well-maintained for a number of distributions, including Fedora, Debian,
|
||||
Ubuntu, Mandrake, Arch Linux, Gentoo, ...
|
||||
|
||||
Only in cases where you:
|
||||
|
||||
- want to hack on GStreamer
|
||||
- want to verify that a bug has been fixed
|
||||
- do not have a sane distribution
|
||||
|
||||
should you choose to build from source tarballs or git.
|
||||
|
||||
Find more information about the various packages at
|
||||
|
||||
https://gstreamer.freedesktop.org/download/
|
||||
|
||||
COMPILING FROM SOURCE TARBALLS
|
||||
------------------------------
|
||||
|
||||
- again, make sure that you really need to install from source!
|
||||
If GStreamer is one of your first projects ever that you build from source,
|
||||
consider taking on an easier project.
|
||||
|
||||
- you need a recent version of Meson installed, see
|
||||
|
||||
http://mesonbuild.com/Getting-meson.html
|
||||
|
||||
and
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer/gst-build/blob/master/README.md
|
||||
|
||||
- run
|
||||
|
||||
meson build
|
||||
ninja -C build
|
||||
|
||||
to build GStreamer.
|
||||
|
||||
- if you want to install it (not required, but what you usually want to do), run
|
||||
|
||||
ninja -C build install
|
||||
|
||||
- try out a simple test:
|
||||
gst-launch-1.0 -v fakesrc num_buffers=5 ! fakesink
|
||||
(If you didn't install GStreamer, run `./build/tools/gst-launch-1.0`)
|
||||
|
||||
If it outputs a bunch of messages from fakesrc and fakesink, everything is
|
||||
ok.
|
||||
|
||||
If it did not work, keep in mind that you might need to adjust the
|
||||
PATH and/or LD_LIBRARY_PATH environment variables to make the system
|
||||
find GStreamer in the prefix where you installed (by default that is /usr/local).
|
||||
|
||||
- After this, you're ready to install gst-plugins, which will provide the
|
||||
functionality you're probably looking for by now, so go on and read
|
||||
that README.
|
||||
|
||||
COMPILING FROM GIT
|
||||
------------------
|
||||
|
||||
You can build an uninstalled GStreamer from git for development or testing
|
||||
purposes without affecting your system installation.
|
||||
|
||||
Get started with:
|
||||
|
||||
git clone https://gitlab.freedesktop.org/gstreamer/gst-build
|
||||
meson build
|
||||
ninja -C build
|
||||
ninja -C build uninstalled
|
||||
|
||||
For more information, see the `gst-build` module and its documentation:
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer/gst-build/blob/master/README.md
|
||||
|
||||
|
||||
PLUG-IN DEPENDENCIES AND LICENSES
|
||||
---------------------------------
|
||||
|
||||
GStreamer is developed under the terms of the LGPL (see COPYING file for
|
||||
details). Some of our plug-ins however rely on libraries which are available
|
||||
under other licenses. This means that if you are distributing an application
|
||||
which has a non-GPL compatible license (for instance a closed-source
|
||||
application) with GStreamer, you have to make sure not to distribute GPL-linked
|
||||
plug-ins.
|
||||
|
||||
When using GPL-linked plug-ins, GStreamer is for all practical reasons
|
||||
under the GPL itself.
|
||||
|
||||
HISTORY
|
||||
-------
|
||||
|
||||
The fundamental design comes from the video pipeline at Oregon Graduate
|
||||
Institute, as well as some ideas from DirectMedia. It's based on plug-ins that
|
||||
will provide the various codec and other functionality. The interface
|
||||
hopefully is generic enough for various companies (ahem, Apple) to release
|
||||
binary codecs for Linux, until such time as they get a clue and release the
|
||||
source.
|
174
README.static-linking
Normal file
174
README.static-linking
Normal file
|
@ -0,0 +1,174 @@
|
|||
=================================
|
||||
GStreamer Static Linking README
|
||||
=================================
|
||||
|
||||
DRAFT, April 2013
|
||||
|
||||
|
||||
I. INTRODUCTION
|
||||
|
||||
It is possible to link GStreamer libraries, plugins and applications
|
||||
statically, both in case of free/libre/open-source software applications
|
||||
and proprietary applications. On some platforms static linking may even
|
||||
be required.
|
||||
|
||||
However, distributing statically linked binaries using GStreamer usually
|
||||
requires additional effort to stay compliant with the GNU LGPL v2.1 license.
|
||||
|
||||
The purpose of this document is to draw attention to this fact, and to
|
||||
summarise in layman's terms what we believe is required from anyone
|
||||
distributing statically linked GStreamer binaries. Most of this also
|
||||
applies to dynamically linked GStreamer binaries.
|
||||
|
||||
|
||||
II. DISCLAIMER
|
||||
|
||||
This document is not legal advice, nor is it comprehensive. It may use
|
||||
words in ways that do not match the definition or use in the license
|
||||
text. It may even be outright wrong. Read the license text for all the
|
||||
details, it is the only legally binding document in this respect.
|
||||
|
||||
This document is primarily concerned with the implications for the
|
||||
distribution of binaries based on LGPL-licensed software as imposed by
|
||||
the LGPL license, but there may be other restrictions to the distribution
|
||||
of such binaries, such as terms and conditions of distribution channels
|
||||
(e.g. "app stores").
|
||||
|
||||
|
||||
III. THE SPIRIT OF THE LGPL LICENSE
|
||||
|
||||
The GNU LGPL v2.1 license allows use of such-licensed software by
|
||||
proprietary applications, but still aims to ensure that at least the
|
||||
LGPL-licensed software parts remain free under all circumstances. This
|
||||
means any changes to LGPL-licensed source code must be documented and
|
||||
be made available on request to those who received binaries of the
|
||||
software. It also means that it must be possible to make changes to the
|
||||
LGPL-licensed software parts and make the application use those, as far
|
||||
as that is possible. And that recipients of an application using
|
||||
LGPL-licensed software are made aware of their rights according to the
|
||||
LGPL license.
|
||||
|
||||
In an environment where GStreamer libraries and plugins are used as
|
||||
dynamically-loaded shared objects (DLL/.so/.dyn files), this is usually
|
||||
not a big problem, because it is fairly easy to compile a modified version
|
||||
of the GStreamer libraries or LGPL plugins, and the application will/should
|
||||
just pick up and use the modified version automatically. All that is needed
|
||||
is for the original, LGPL-licensed source code and source code modifications
|
||||
to be made available, and for a way to build the libraries or plugins for
|
||||
the platform required (usually that will be using the build system scripts
|
||||
that come with GStreamer, and using the typical build environment on the
|
||||
system in question, but where that is not the case the needed build scripts
|
||||
and/or tools would need to be provided as well).
|
||||
|
||||
|
||||
IV. THINGS YOU NEED TO DO
|
||||
|
||||
* You must tell users of your application that you are using LGPL-licensed
|
||||
software, which LGPL-licensed software exactly, and you must provide them
|
||||
with a copy of the license so they know their rights under the LGPL.
|
||||
|
||||
* You must provide (on request) all the source code and all the changes
|
||||
or additions you have made to the LGPL-licensed software you are using.
|
||||
|
||||
For GStreamer code we would recommend that the changes be provided either
|
||||
in form of a branch in a git repository, or as a set of "git format-patch"-
|
||||
style patches against a GStreamer release or a snapshot of a GStreamer git
|
||||
repository. The patches should ideally say what was changed and why it
|
||||
was changed, and there should ideally be separate patches for independent
|
||||
changes.
|
||||
|
||||
* You must provide a way for users of your application to make changes to
|
||||
the LGPL-licensed parts of the code, and re-create a full application
|
||||
binary with the changes (using the standard toolchain and tools of the
|
||||
target platform; if you are using a custom toolchain or custom tools
|
||||
you must provide these and document how to use them to create a new
|
||||
application binary).
|
||||
|
||||
Note that this of course does not mean that the user is allowed to
|
||||
re-distribute the changed application. Nor does it mean that you have
|
||||
to provide your proprietary source code - it is sufficient to provide a
|
||||
ready-made compiled object file that can be relinked into an application
|
||||
binary with the re-compiled LGPL components.
|
||||
|
||||
|
||||
V. THINGS TO LOOK OUT FOR
|
||||
|
||||
While most GStreamer plugins and the libraries they depend on are licensed
|
||||
under the LGPL or even more permissive licenses, that is not the case for
|
||||
all plugins and libraries used, esp. those in the gst-plugins-ugly or
|
||||
some of those in the gst-plugins-bad set of plugins.
|
||||
|
||||
When statically linking proprietary code, care must be taken not to
|
||||
statically link plugins or libraries that are licensed under less permissive
|
||||
terms than the LGPL, such as e.g. GPL-licensed libraries.
|
||||
|
||||
|
||||
VI. SPECIAL CONSIDERATIONS FOR SPECIFIC USE-CASES
|
||||
|
||||
|
||||
1. Proprietary GStreamer/GLib-based Application On iOS
|
||||
|
||||
Let's assume an individual or a company wants to distribute a proprietary
|
||||
iOS application that is built on top of GStreamer and GLib through
|
||||
Apple's App Store. At the time of writing the Apple iPhone developer
|
||||
agreement didn’t allow the bundling of shared libraries, so distributing
|
||||
a proprietary iOS application with shared libraries is only possible using
|
||||
distribution mechanisms outside of the App Store and/or only to jailbroken
|
||||
devices, a prospect that may not appeal to our individual or company. So the
|
||||
only alternative then is to link everything statically, which means the
|
||||
obligations mentioned above come into play.
|
||||
|
||||
|
||||
2. Example: Jabber on iOS
|
||||
|
||||
Tandberg (now Cisco) created a Jabber application for iOS, based on GStreamer.
|
||||
On request they provided an LGPL compliance bundle in form of a zip file, with
|
||||
roughly the following contents:
|
||||
|
||||
buildapp.sh
|
||||
readme.txt
|
||||
Jabber/Jabber-Info.plist
|
||||
Jabber/libip.a [236MB binary with proprietary code]
|
||||
Jabber/main.mm
|
||||
Jabber/xcconfig/Application.xcconfig
|
||||
Jabber/xcconfig/Debug.xcconfig
|
||||
Jabber/xcconfig/Release.xcconfig
|
||||
Jabber/xcconfig/Shared.xcconfig
|
||||
Jabber/Resources/*.lproj/Localizable.strings
|
||||
Jabber/Resources/{Images,Audio,Sounds,IB,Message Styles,Emoticons,Fonts}/*
|
||||
Jabber/Resources/*
|
||||
Jabber.xcodeproj/project.pbxproj
|
||||
Jabber.xcodeproj/project.xcworkspace/contents.xcworkspacedata
|
||||
opensource/build/config.site
|
||||
opensource/build/m4/movi.m4
|
||||
opensource/build/scripts/clean-deps.sh
|
||||
opensource/build/scripts/fixup-makefile.sh
|
||||
opensource/build/scripts/MoviMaker.py
|
||||
opensource/build.sh
|
||||
opensource/env.sh
|
||||
opensource/Makefile
|
||||
opensource/external/glib/*
|
||||
opensource/external/gstreamer/{gstreamer,gst-plugins-*}/*
|
||||
opensource/external/openssl/*
|
||||
opensource/external/proxy-libintl/*
|
||||
opensource/toolchain/darwin-x86/bin/{misc autotoools,m4,glib-mkenums,glib-genmarshal,libtool,pkg-config,etc.}
|
||||
opensource/toolchain/darwin-x86/share/{aclocal,aclocal-1.11,autoconf,automake-1.11,libtool}/*
|
||||
opensource/toolchain/darwin-x86/share/Config.pm
|
||||
opensource/toolchain/darwin-x86/share/Config.pm.movi.in
|
||||
patches/glib/glib.patch
|
||||
patches/gst-plugins-bad/gst-plugins-bad.patch
|
||||
patches/gst-plugins-base/gst-plugins-base.patch
|
||||
patches/gst-plugins-good/gst-plugins-good.patch
|
||||
patches/gstreamer/gstreamer.patch
|
||||
patches/openssl/openssl.patch
|
||||
|
||||
readme.txt starts with "This Readme file describes how to build the Cisco
|
||||
Jabber for iPad application. You need to install Xcode, but the final package
|
||||
is built by running buildapp.sh." and describes how to build project,
|
||||
prerequisites, the procedure in detail, and a "How to Include Provisioning
|
||||
Profile Manually / Alternate Code Signing Instructions" section.
|
||||
|
||||
|
||||
3. Random Links Which May Be Of Interest
|
||||
|
||||
[0] http://multinc.com/2009/08/24/compatibility-between-the-iphone-app-store-and-the-lgpl/
|
96
RELEASE
Normal file
96
RELEASE
Normal file
|
@ -0,0 +1,96 @@
|
|||
This is GStreamer gst-plugins-good 1.19.2.
|
||||
|
||||
GStreamer 1.19 is the development branch leading up to the next major
|
||||
stable version which will be 1.20.
|
||||
|
||||
The 1.19 development series adds new features on top of the 1.18 series and is
|
||||
part of the API and ABI-stable 1.x release series of the GStreamer multimedia
|
||||
framework.
|
||||
|
||||
Full release notes will one day be found at:
|
||||
|
||||
https://gstreamer.freedesktop.org/releases/1.20/
|
||||
|
||||
Binaries for Android, iOS, Mac OS X and Windows will usually be provided
|
||||
shortly after the release.
|
||||
|
||||
This module will not be very useful by itself and should be used in conjunction
|
||||
with other GStreamer modules for a complete multimedia experience.
|
||||
|
||||
- gstreamer: provides the core GStreamer libraries and some generic plugins
|
||||
|
||||
- gst-plugins-base: a basic set of well-supported plugins and additional
|
||||
media-specific GStreamer helper libraries for audio,
|
||||
video, rtsp, rtp, tags, OpenGL, etc.
|
||||
|
||||
- gst-plugins-good: a set of well-supported plugins under our preferred
|
||||
license
|
||||
|
||||
- gst-plugins-ugly: a set of well-supported plugins which might pose
|
||||
problems for distributors
|
||||
|
||||
- gst-plugins-bad: a set of plugins of varying quality that have not made
|
||||
their way into one of core/base/good/ugly yet, for one
|
||||
reason or another. Many of these are are production quality
|
||||
elements, but may still be missing documentation or unit
|
||||
tests; others haven't passed the rigorous quality testing
|
||||
we expect yet.
|
||||
|
||||
- gst-libav: a set of codecs plugins based on the ffmpeg library. This is
|
||||
where you can find audio and video decoders and encoders
|
||||
for a wide variety of formats including H.264, AAC, etc.
|
||||
|
||||
- gstreamer-vaapi: hardware-accelerated video decoding and encoding using
|
||||
VA-API on Linux. Primarily for Intel graphics hardware.
|
||||
|
||||
- gst-omx: hardware-accelerated video decoding and encoding, primarily for
|
||||
embedded Linux systems that provide an OpenMax
|
||||
implementation layer such as the Raspberry Pi.
|
||||
|
||||
- gst-rtsp-server: library to serve files or streaming pipelines via RTSP
|
||||
|
||||
- gst-editing-services: library an plugins for non-linear editing
|
||||
|
||||
==== Download ====
|
||||
|
||||
You can find source releases of gstreamer in the download
|
||||
directory: https://gstreamer.freedesktop.org/src/gstreamer/
|
||||
|
||||
The git repository and details how to clone it can be found at
|
||||
https://gitlab.freedesktop.org/gstreamer/
|
||||
|
||||
==== Homepage ====
|
||||
|
||||
The project's website is https://gstreamer.freedesktop.org/
|
||||
|
||||
==== Support and Bugs ====
|
||||
|
||||
We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org
|
||||
for bug reports and feature requests:
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer
|
||||
|
||||
Please submit patches via GitLab as well, in form of Merge Requests. See
|
||||
|
||||
https://gstreamer.freedesktop.org/documentation/contribute/
|
||||
|
||||
for more details.
|
||||
|
||||
For help and support, please subscribe to and send questions to the
|
||||
gstreamer-devel mailing list (see below for details).
|
||||
|
||||
There is also a #gstreamer IRC channel on the Freenode IRC network.
|
||||
|
||||
==== Developers ====
|
||||
|
||||
GStreamer source code repositories can be found on GitLab on freedesktop.org:
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer
|
||||
|
||||
and can also be cloned from there and this is also where you can submit
|
||||
Merge Requests or file issues for bugs or feature requests.
|
||||
|
||||
Interested developers of the core library, plugins, and applications should
|
||||
subscribe to the gstreamer-devel list:
|
||||
|
||||
https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel
|
182
REQUIREMENTS
Normal file
182
REQUIREMENTS
Normal file
|
@ -0,0 +1,182 @@
|
|||
GStreamer uses a *large* array of tools and libraries, most of which are
|
||||
optional. We have attempted to make sure that any code that depends on
|
||||
optional libraries doesn't get built unless you have those libraries. If
|
||||
you find this not to be the case, please, let us know by filing a bug
|
||||
report at http://bugzilla.gnome.org/.
|
||||
|
||||
On Debian/Ubuntu, the easiest way to install most build requirements is:
|
||||
|
||||
sudo apt-get build-dep gst-plugins-good1.0
|
||||
|
||||
Required tools:
|
||||
===============
|
||||
|
||||
An extra set of tools is required if you wish to build GStreamer
|
||||
from git (using autogen.sh):
|
||||
|
||||
autoconf >= 2.68 https://www.gnu.org/software/autoconf/
|
||||
automake >= 1.11 https://www.gnu.org/software/automake/
|
||||
gettext >= 0.17 https://www.gnu.org/software/gettext/
|
||||
libtool >= 2.2.6 https://www.gnu.org/software/libtool/
|
||||
pkgconfig >= 0.9.0 https://www.freedesktop.org/software/pkgconfig/
|
||||
|
||||
Required libraries:
|
||||
===================
|
||||
|
||||
Package: GStreamer
|
||||
Version: 1.x (same 1.x version as this package)
|
||||
Recommended: Latest 1.x
|
||||
URL: http://gstreamer.freedesktop.org/
|
||||
DebianPackage: libgstreamer1.0-dev
|
||||
Notes: The required version is updated frequently, so the version
|
||||
listed in this file is often out of date. If you are compiling
|
||||
from git master, you will usually need GStreamer core and
|
||||
gst-plugins-base from git master as well.
|
||||
|
||||
Package: GStreamer Base Plugins
|
||||
Version: 1.x (same 1.x version as this package)
|
||||
Recommended: Latest 1.x
|
||||
URL: http://gstreamer.freedesktop.org/
|
||||
DebianPackage: libgstreamer-plugins-base1.0-dev
|
||||
Notes: The required version is updated frequently, so the version
|
||||
listed in this file is often out of date. If you are compiling
|
||||
from git master, you will usually need GStreamer core and
|
||||
gst-plugins-base from git master as well.
|
||||
|
||||
|
||||
Optional libraries:
|
||||
===================
|
||||
|
||||
This file lists supporting libraries for which gst-plugins-good contains
|
||||
plugins, as well as their minimum required version. You can find the
|
||||
corresponding plugins in ext/(library)
|
||||
|
||||
Package: Orc
|
||||
Version: >= 0.4.17
|
||||
Recommended: Latest 0.4.x
|
||||
URL: http://gstreamer.freedesktop.org/data/src/orc/
|
||||
DebianPackage: liborc-0.4-dev
|
||||
Notes: Used by many plugins for accelerating SIMD processing using
|
||||
run-time generated assembly. Not a hard requirement, but
|
||||
highly recommended. Packagers, please hard-depend on liborc.
|
||||
The git repository is http://cgit.freedesktop.org/gstreamer/orc/
|
||||
(all versions on entropywave.com are outdated)
|
||||
|
||||
Package: gdk-pixbuf Library
|
||||
Version: >= 2.8.0
|
||||
Recommended: Latest 2.x
|
||||
URL: http://www.gtk.org/
|
||||
DebianPackage: libgdk-pixbuf2.0-dev
|
||||
Plugins: gdkpixbuf (gdkpixbufdec, gdkpixbufoverlay)
|
||||
Notes: This library does not depend on Gtk+ or Gdk or any window system
|
||||
|
||||
Package: GTK+
|
||||
Version: >= 3.0
|
||||
Recommended: Latest 3.x
|
||||
URL: http://www.gtk.org/
|
||||
DebianPackage: libgtk-3-dev
|
||||
Plugins: None
|
||||
Notes: Only needed by some of the examples, not by any plugins.
|
||||
|
||||
Package: Xlib
|
||||
Plugins: ximagesrc
|
||||
DebianPackage: libx11-dev libxv-dev libxt-dev
|
||||
|
||||
Package: AALib
|
||||
Plugins: aasink
|
||||
URL: http://aa-project.sourceforge.net/aalib/
|
||||
|
||||
Package: Cairo
|
||||
Version: >= 1.10
|
||||
DebianPackage: libcairo2-dev
|
||||
Plugins: cairo (cairooverlay)
|
||||
URL: http://cairographics.org/
|
||||
|
||||
Package: FLAC
|
||||
Version: >= 1.1.4
|
||||
DebianPackage: libflac-dev
|
||||
Plugins: flac (flacenc, flacdec)
|
||||
URL: http://flac.sourceforge.net/
|
||||
|
||||
Package: gudev
|
||||
Version: >= 147
|
||||
DebianPackage: libgudev-1.0-dev
|
||||
Plugins: v4l2 (v4l2src)
|
||||
URL: http://www.freedesktop.org/software/systemd/
|
||||
Notes: This dependency is entirely optional, the video4linux plugin
|
||||
will work just fine without it. gudev is only required for
|
||||
the device probing and monitoring functionality to detect
|
||||
video4linux devices appearing/disappearing at run-time.
|
||||
|
||||
Package: JPEG library
|
||||
Plugins: jpeg (jpegenc, jpegdec, smokeenc, smokedec)
|
||||
DebianPackage: libjpeg62-turbo-dev
|
||||
URL: http://www.libjpeg-turbo.org/
|
||||
or http://www.ijg.org/ for the IJG version
|
||||
|
||||
Package: Libcaca
|
||||
Plugins: cacasink
|
||||
DebianPackage: libcaca-dev
|
||||
URL: http://libcaca.zoy.org/
|
||||
|
||||
Package: Libdv
|
||||
Version: >= 0.100
|
||||
DebianPackage: libdv4-dev
|
||||
Plugins: dv (dvdec)
|
||||
URL: http://libdv.sourceforge.net/
|
||||
|
||||
Package: liblame
|
||||
Version: >= 3.98
|
||||
DebianPackage: libmp3lame-dev
|
||||
Plugins: lame (lamemp3enc)
|
||||
URL: http://www.mp3dev.org/mp3/
|
||||
|
||||
Package: libmpg123
|
||||
Version: >= 1.3
|
||||
DebianPackage: libpng12-dev
|
||||
Plugins: mpg123 (mpg123audiodec)
|
||||
URL: https://www.mpg123.de/api/
|
||||
|
||||
Package: Libpng
|
||||
Version: >= 1.2
|
||||
DebianPackage: libpng12-dev
|
||||
Plugins: png (pngenc, pngdec)
|
||||
URL: http://www.libpng.org/pub/png/libpng.html
|
||||
|
||||
Package: libraw1394
|
||||
Plugins: dv1394
|
||||
URL: http://www.linux1394.org/
|
||||
|
||||
Package: libshout
|
||||
Version: >= 2.0
|
||||
DebianPackage: libshout3-dev
|
||||
plugins: shout2 (shout2send)
|
||||
URL: http://www.icecast.org/
|
||||
|
||||
Package: speex
|
||||
Version: >= 1.1.6
|
||||
Plugins: speex (speexenc, speexdec)
|
||||
URL: http://www.speex.org/
|
||||
|
||||
Package: taglib
|
||||
Version: >= 1.5
|
||||
DebianPackage: libtag1-dev
|
||||
Plugins: taglib (id3v2mux)
|
||||
URL: http://taglib.github.io/
|
||||
|
||||
Package: twolame
|
||||
Version: >= 0.3.13
|
||||
DebianPackage: libtwolame-dev
|
||||
Plugins: twolame (twolamemp2enc)
|
||||
URL: http://www.twolame.org
|
||||
|
||||
Package: zlib
|
||||
DebianPackage: zlib1g-dev
|
||||
Plugins: isomp4 (qtdemux), matroska (matroskademux)
|
||||
URL: http://www.zlib.net/
|
||||
|
||||
Optional (debian) packages:
|
||||
===========================
|
||||
|
||||
gtk-doc-tools >= 1.12 -- needed to build documentation
|
||||
python-xml -- needed to build plugin documentation
|
10
docs/all_index.md
Normal file
10
docs/all_index.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
short-description: GStreamer Good Plugins API reference.
|
||||
...
|
||||
|
||||
# Good Plugins
|
||||
|
||||
GStreamer Good Plugins {{ gst_api_version.md }}
|
||||
|
||||
The latest version of this documentation can be found on-line at
|
||||
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/
|
1
docs/gst_api_version.in
Normal file
1
docs/gst_api_version.in
Normal file
|
@ -0,0 +1 @@
|
|||
@GST_API_VERSION@
|
27043
docs/gst_plugins_cache.json
Normal file
27043
docs/gst_plugins_cache.json
Normal file
File diff suppressed because one or more lines are too long
0
docs/index.md
Normal file
0
docs/index.md
Normal file
111
docs/meson.build
Normal file
111
docs/meson.build
Normal file
|
@ -0,0 +1,111 @@
|
|||
build_hotdoc = false
|
||||
|
||||
if meson.is_cross_build()
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but building the doc while cross building is not supported yet.')
|
||||
endif
|
||||
|
||||
message('Documentation not built as building it while cross building is not supported yet.')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
required_hotdoc_extensions = ['gi-extension', 'gst-extension']
|
||||
if gst_dep.type_name() == 'internal'
|
||||
gst_proj = subproject('gstreamer')
|
||||
plugins_cache_generator = gst_proj.get_variable('plugins_cache_generator')
|
||||
else
|
||||
plugins_cache_generator = find_program(join_paths(gst_dep.get_pkgconfig_variable('libexecdir'), 'gstreamer-' + api_version, 'gst-plugins-doc-cache-generator'),
|
||||
required: false)
|
||||
endif
|
||||
|
||||
plugins_cache = join_paths(meson.current_source_dir(), 'gst_plugins_cache.json')
|
||||
if plugins.length() == 0
|
||||
message('All good plugins have been disabled')
|
||||
elif plugins_cache_generator.found()
|
||||
plugins_doc_dep = custom_target('good-plugins-doc-cache',
|
||||
command: [plugins_cache_generator, plugins_cache, '@OUTPUT@', '@INPUT@'],
|
||||
input: plugins,
|
||||
output: 'gst_plugins_cache.json',
|
||||
build_always_stale: true,
|
||||
)
|
||||
else
|
||||
warning('GStreamer plugin inspector for documentation not found, can\'t update the cache')
|
||||
endif
|
||||
|
||||
hotdoc_p = find_program('hotdoc', required: get_option('doc'))
|
||||
if not hotdoc_p.found()
|
||||
message('Hotdoc not found, not building the documentation')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
hotdoc_req = '>= 0.11.0'
|
||||
hotdoc_version = run_command(hotdoc_p, '--version').stdout()
|
||||
if not hotdoc_version.version_compare(hotdoc_req)
|
||||
if get_option('doc').enabled()
|
||||
error('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version))
|
||||
else
|
||||
message('Hotdoc version @0@ not found, got @1@'.format(hotdoc_req, hotdoc_version))
|
||||
subdir_done()
|
||||
endif
|
||||
endif
|
||||
|
||||
hotdoc = import('hotdoc')
|
||||
foreach extension: required_hotdoc_extensions
|
||||
if not hotdoc.has_extensions(extension)
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but @0@ missing'.format(extension))
|
||||
endif
|
||||
|
||||
message('@0@ extension not found, not building documentation'.format(extension))
|
||||
subdir_done()
|
||||
endif
|
||||
endforeach
|
||||
|
||||
docconf = configuration_data()
|
||||
docconf.set('GST_API_VERSION', api_version)
|
||||
configure_file(input : 'gst_api_version.in',
|
||||
output : 'gst_api_version.md',
|
||||
configuration : docconf)
|
||||
libs_doc = []
|
||||
plugins_doc = []
|
||||
excludes = []
|
||||
build_hotdoc = true
|
||||
foreach f: ['gstgdkpixbufplugin.c']
|
||||
excludes += [join_paths(meson.current_source_dir(), '..', 'ext/gdk_pixbuf/', f)]
|
||||
endforeach
|
||||
|
||||
excludes += [join_paths(meson.current_source_dir(), '..', 'sys', 'rpicamsrc', 'Raspi*.[ch]')]
|
||||
|
||||
list_plugin_res = run_command(python3, '-c',
|
||||
'''
|
||||
import sys
|
||||
import json
|
||||
|
||||
with open("@0@") as f:
|
||||
print(':'.join(json.load(f).keys()), end='')
|
||||
'''.format(plugins_cache))
|
||||
|
||||
assert(list_plugin_res.returncode() == 0,
|
||||
'Could not list plugins from @0@'.format(plugins_cache))
|
||||
|
||||
foreach plugin_name: list_plugin_res.stdout().split(':')
|
||||
plugins_doc += [hotdoc.generate_doc(plugin_name,
|
||||
project_version: api_version,
|
||||
sitemap: 'sitemap.txt',
|
||||
index: 'index.md',
|
||||
gst_index: 'index.md',
|
||||
gst_smart_index: true,
|
||||
gst_c_sources: ['../sys/*/*.[ch]',
|
||||
'../ext/*/*.[ch]',
|
||||
'../gst/*/*.[ch]',
|
||||
],
|
||||
gst_c_source_filters: excludes,
|
||||
dependencies: [gst_dep, plugins],
|
||||
gst_order_generated_subpages: true,
|
||||
install: false,
|
||||
disable_incremental_build: true,
|
||||
gst_cache_file: plugins_cache,
|
||||
gst_plugin_name: plugin_name,
|
||||
include_paths: join_paths(meson.current_source_dir(), '..'),
|
||||
)]
|
||||
endforeach
|
1
docs/sitemap.txt
Normal file
1
docs/sitemap.txt
Normal file
|
@ -0,0 +1 @@
|
|||
gst-index
|
41
ext/aalib/gstaaplugin.c
Normal file
41
ext/aalib/gstaaplugin.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Huawei Technologies Co., Ltd.
|
||||
* @Author: Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstaasink.h"
|
||||
#include "gstaatv.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (aasink, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (aatv, plugin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
aasink,
|
||||
"ASCII Art video sink & filter",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
|
585
ext/aalib/gstaasink.c
Normal file
585
ext/aalib/gstaasink.c
Normal file
|
@ -0,0 +1,585 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
/**
|
||||
* SECTION:element-aasink
|
||||
* @title: aasink
|
||||
* @see_also: #GstCACASink
|
||||
*
|
||||
* Displays video as b/w ascii art.
|
||||
*
|
||||
* ## Example launch line
|
||||
* |[
|
||||
* gst-launch-1.0 filesrc location=test.avi ! decodebin ! videoconvert ! aasink
|
||||
* ]| This pipeline renders a video to ascii art into a separate window.
|
||||
* |[
|
||||
* gst-launch-1.0 filesrc location=test.avi ! decodebin ! videoconvert ! aasink driver=curses
|
||||
* ]| This pipeline renders a video to ascii art into the current terminal.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <gst/video/gstvideometa.h>
|
||||
|
||||
#include "gstaasink.h"
|
||||
|
||||
/* aasink signals and args */
|
||||
enum
|
||||
{
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDTH,
|
||||
PROP_HEIGHT,
|
||||
PROP_DRIVER,
|
||||
PROP_DITHER,
|
||||
PROP_BRIGHTNESS,
|
||||
PROP_CONTRAST,
|
||||
PROP_GAMMA,
|
||||
PROP_INVERSION,
|
||||
PROP_RANDOMVAL,
|
||||
PROP_FRAMES_DISPLAYED,
|
||||
PROP_FRAME_TIME
|
||||
};
|
||||
|
||||
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420"))
|
||||
);
|
||||
|
||||
static GstCaps *gst_aasink_fixate (GstBaseSink * bsink, GstCaps * caps);
|
||||
static gboolean gst_aasink_setcaps (GstBaseSink * bsink, GstCaps * caps);
|
||||
static void gst_aasink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
|
||||
GstClockTime * start, GstClockTime * end);
|
||||
static gboolean gst_aasink_propose_allocation (GstBaseSink * bsink,
|
||||
GstQuery * query);
|
||||
static GstFlowReturn gst_aasink_show_frame (GstVideoSink * videosink,
|
||||
GstBuffer * buffer);
|
||||
|
||||
static void gst_aasink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_aasink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static GstStateChangeReturn gst_aasink_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
#define gst_aasink_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstAASink, gst_aasink, GST_TYPE_VIDEO_SINK);
|
||||
GST_ELEMENT_REGISTER_DEFINE (aasink, "aasink", GST_RANK_NONE, GST_TYPE_AASINK);
|
||||
|
||||
#define GST_TYPE_AADRIVERS (gst_aasink_drivers_get_type())
|
||||
static GType
|
||||
gst_aasink_drivers_get_type (void)
|
||||
{
|
||||
static GType driver_type = 0;
|
||||
|
||||
if (!driver_type) {
|
||||
GEnumValue *drivers;
|
||||
const struct aa_driver *driver;
|
||||
gint n_drivers;
|
||||
gint i;
|
||||
|
||||
for (n_drivers = 0; aa_drivers[n_drivers]; n_drivers++) {
|
||||
/* count number of drivers */
|
||||
}
|
||||
|
||||
drivers = g_new0 (GEnumValue, n_drivers + 1);
|
||||
|
||||
for (i = 0; i < n_drivers; i++) {
|
||||
driver = aa_drivers[i];
|
||||
drivers[i].value = i;
|
||||
drivers[i].value_name = g_strdup (driver->name);
|
||||
drivers[i].value_nick = g_utf8_strdown (driver->shortname, -1);
|
||||
}
|
||||
drivers[i].value = 0;
|
||||
drivers[i].value_name = NULL;
|
||||
drivers[i].value_nick = NULL;
|
||||
|
||||
driver_type = g_enum_register_static ("GstAASinkDrivers", drivers);
|
||||
}
|
||||
return driver_type;
|
||||
}
|
||||
|
||||
#define GST_TYPE_AADITHER (gst_aasink_dither_get_type())
|
||||
static GType
|
||||
gst_aasink_dither_get_type (void)
|
||||
{
|
||||
static GType dither_type = 0;
|
||||
|
||||
if (!dither_type) {
|
||||
GEnumValue *ditherers;
|
||||
gint n_ditherers;
|
||||
gint i;
|
||||
|
||||
for (n_ditherers = 0; aa_dithernames[n_ditherers]; n_ditherers++) {
|
||||
/* count number of ditherers */
|
||||
}
|
||||
|
||||
ditherers = g_new0 (GEnumValue, n_ditherers + 1);
|
||||
|
||||
for (i = 0; i < n_ditherers; i++) {
|
||||
ditherers[i].value = i;
|
||||
ditherers[i].value_name = g_strdup (aa_dithernames[i]);
|
||||
ditherers[i].value_nick =
|
||||
g_strdelimit (g_strdup (aa_dithernames[i]), " _", '-');
|
||||
}
|
||||
ditherers[i].value = 0;
|
||||
ditherers[i].value_name = NULL;
|
||||
ditherers[i].value_nick = NULL;
|
||||
|
||||
dither_type = g_enum_register_static ("GstAASinkDitherers", ditherers);
|
||||
}
|
||||
return dither_type;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aasink_class_init (GstAASinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstVideoSinkClass *gstvideosink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstvideosink_class = (GstVideoSinkClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_aasink_set_property;
|
||||
gobject_class->get_property = gst_aasink_get_property;
|
||||
|
||||
/* FIXME: add long property descriptions */
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
|
||||
g_param_spec_int ("width", "width", "width", G_MININT, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
|
||||
g_param_spec_int ("height", "height", "height", G_MININT, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRIVER,
|
||||
g_param_spec_enum ("driver", "driver", "driver", GST_TYPE_AADRIVERS, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DITHER,
|
||||
g_param_spec_enum ("dither", "dither", "dither", GST_TYPE_AADITHER, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS,
|
||||
g_param_spec_int ("brightness", "brightness", "brightness", G_MININT,
|
||||
G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONTRAST,
|
||||
g_param_spec_int ("contrast", "contrast", "contrast", G_MININT, G_MAXINT,
|
||||
0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAMMA,
|
||||
g_param_spec_float ("gamma", "gamma", "gamma", 0.0, 5.0, 1.0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INVERSION,
|
||||
g_param_spec_boolean ("inversion", "inversion", "inversion", TRUE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RANDOMVAL,
|
||||
g_param_spec_int ("randomval", "randomval", "randomval", G_MININT,
|
||||
G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_FRAMES_DISPLAYED, g_param_spec_int ("frames-displayed",
|
||||
"frames displayed", "frames displayed", G_MININT, G_MAXINT, 0,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FRAME_TIME,
|
||||
g_param_spec_int ("frame-time", "frame time", "frame time", G_MININT,
|
||||
G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class,
|
||||
"ASCII art video sink", "Sink/Video", "An ASCII art videosink",
|
||||
"Wim Taymans <wim.taymans@chello.be>");
|
||||
|
||||
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_aasink_change_state);
|
||||
|
||||
gstbasesink_class->fixate = GST_DEBUG_FUNCPTR (gst_aasink_fixate);
|
||||
gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_aasink_setcaps);
|
||||
gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_aasink_get_times);
|
||||
gstbasesink_class->propose_allocation =
|
||||
GST_DEBUG_FUNCPTR (gst_aasink_propose_allocation);
|
||||
|
||||
gstvideosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_aasink_show_frame);
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_AADRIVERS, 0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_AADITHER, 0);
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_aasink_fixate (GstBaseSink * bsink, GstCaps * caps)
|
||||
{
|
||||
GstStructure *structure;
|
||||
|
||||
caps = gst_caps_make_writable (caps);
|
||||
|
||||
structure = gst_caps_get_structure (caps, 0);
|
||||
|
||||
gst_structure_fixate_field_nearest_int (structure, "width", 320);
|
||||
gst_structure_fixate_field_nearest_int (structure, "height", 240);
|
||||
gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
|
||||
|
||||
caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_aasink_setcaps (GstBaseSink * basesink, GstCaps * caps)
|
||||
{
|
||||
GstAASink *aasink;
|
||||
GstVideoInfo info;
|
||||
|
||||
aasink = GST_AASINK (basesink);
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
aasink->info = info;
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
invalid_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (aasink, "invalid caps");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aasink_init (GstAASink * aasink)
|
||||
{
|
||||
memcpy (&aasink->ascii_surf, &aa_defparams,
|
||||
sizeof (struct aa_hardware_params));
|
||||
aasink->ascii_parms.bright = 0;
|
||||
aasink->ascii_parms.contrast = 16;
|
||||
aasink->ascii_parms.gamma = 1.0;
|
||||
aasink->ascii_parms.dither = 0;
|
||||
aasink->ascii_parms.inversion = 0;
|
||||
aasink->ascii_parms.randomval = 0;
|
||||
aasink->aa_driver = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aasink_scale (GstAASink * aasink, guchar * src, guchar * dest,
|
||||
gint sw, gint sh, gint ss, gint dw, gint dh)
|
||||
{
|
||||
gint ypos, yinc, y;
|
||||
gint xpos, xinc, x;
|
||||
|
||||
g_return_if_fail ((dw != 0) && (dh != 0));
|
||||
|
||||
ypos = 0x10000;
|
||||
yinc = (sh << 16) / dh;
|
||||
xinc = (sw << 16) / dw;
|
||||
|
||||
for (y = dh; y; y--) {
|
||||
while (ypos > 0x10000) {
|
||||
ypos -= 0x10000;
|
||||
src += ss;
|
||||
}
|
||||
xpos = 0x10000;
|
||||
{
|
||||
guchar *destp = dest;
|
||||
guchar *srcp = src;
|
||||
|
||||
for (x = dw; x; x--) {
|
||||
while (xpos >= 0x10000L) {
|
||||
srcp++;
|
||||
xpos -= 0x10000L;
|
||||
}
|
||||
*destp++ = *srcp;
|
||||
xpos += xinc;
|
||||
}
|
||||
}
|
||||
dest += dw;
|
||||
ypos += yinc;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aasink_get_times (GstBaseSink * sink, GstBuffer * buffer,
|
||||
GstClockTime * start, GstClockTime * end)
|
||||
{
|
||||
*start = GST_BUFFER_TIMESTAMP (buffer);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buffer))
|
||||
*end = *start + GST_BUFFER_DURATION (buffer);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_aasink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstCaps *caps;
|
||||
GstVideoInfo info;
|
||||
guint size;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, NULL);
|
||||
|
||||
if (caps == NULL)
|
||||
goto no_caps;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
size = GST_VIDEO_INFO_SIZE (&info);
|
||||
|
||||
/* we need at least 2 buffer because we hold on to the last one */
|
||||
gst_query_add_allocation_pool (query, NULL, size, 2, 0);
|
||||
|
||||
/* we support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
invalid_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "invalid caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_aasink_show_frame (GstVideoSink * videosink, GstBuffer * buffer)
|
||||
{
|
||||
GstAASink *aasink;
|
||||
GstVideoFrame frame;
|
||||
|
||||
aasink = GST_AASINK (videosink);
|
||||
|
||||
GST_DEBUG ("show frame");
|
||||
|
||||
if (!gst_video_frame_map (&frame, &aasink->info, buffer, GST_MAP_READ))
|
||||
goto invalid_frame;
|
||||
|
||||
gst_aasink_scale (aasink, GST_VIDEO_FRAME_PLANE_DATA (&frame, 0), /* src */
|
||||
aa_image (aasink->context), /* dest */
|
||||
GST_VIDEO_INFO_WIDTH (&aasink->info), /* sw */
|
||||
GST_VIDEO_INFO_HEIGHT (&aasink->info), /* sh */
|
||||
GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0), /* ss */
|
||||
aa_imgwidth (aasink->context), /* dw */
|
||||
aa_imgheight (aasink->context)); /* dh */
|
||||
|
||||
aa_render (aasink->context, &aasink->ascii_parms,
|
||||
0, 0, aa_imgwidth (aasink->context), aa_imgheight (aasink->context));
|
||||
aa_flush (aasink->context);
|
||||
aa_getevent (aasink->context, FALSE);
|
||||
gst_video_frame_unmap (&frame);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
/* ERRORS */
|
||||
invalid_frame:
|
||||
{
|
||||
GST_DEBUG_OBJECT (aasink, "invalid frame");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_aasink_set_property (GObject * object, guint prop_id, const GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstAASink *aasink;
|
||||
|
||||
aasink = GST_AASINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDTH:
|
||||
aasink->ascii_surf.width = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_HEIGHT:
|
||||
aasink->ascii_surf.height = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_DRIVER:{
|
||||
aasink->aa_driver = g_value_get_enum (value);
|
||||
break;
|
||||
}
|
||||
case PROP_DITHER:{
|
||||
aasink->ascii_parms.dither = g_value_get_enum (value);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS:{
|
||||
aasink->ascii_parms.bright = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_CONTRAST:{
|
||||
aasink->ascii_parms.contrast = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_GAMMA:{
|
||||
aasink->ascii_parms.gamma = g_value_get_float (value);
|
||||
break;
|
||||
}
|
||||
case PROP_INVERSION:{
|
||||
aasink->ascii_parms.inversion = g_value_get_boolean (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RANDOMVAL:{
|
||||
aasink->ascii_parms.randomval = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aasink_get_property (GObject * object, guint prop_id, GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstAASink *aasink;
|
||||
|
||||
aasink = GST_AASINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDTH:{
|
||||
g_value_set_int (value, aasink->ascii_surf.width);
|
||||
break;
|
||||
}
|
||||
case PROP_HEIGHT:{
|
||||
g_value_set_int (value, aasink->ascii_surf.height);
|
||||
break;
|
||||
}
|
||||
case PROP_DRIVER:{
|
||||
g_value_set_enum (value, aasink->aa_driver);
|
||||
break;
|
||||
}
|
||||
case PROP_DITHER:{
|
||||
g_value_set_enum (value, aasink->ascii_parms.dither);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS:{
|
||||
g_value_set_int (value, aasink->ascii_parms.bright);
|
||||
break;
|
||||
}
|
||||
case PROP_CONTRAST:{
|
||||
g_value_set_int (value, aasink->ascii_parms.contrast);
|
||||
break;
|
||||
}
|
||||
case PROP_GAMMA:{
|
||||
g_value_set_float (value, aasink->ascii_parms.gamma);
|
||||
break;
|
||||
}
|
||||
case PROP_INVERSION:{
|
||||
g_value_set_boolean (value, aasink->ascii_parms.inversion);
|
||||
break;
|
||||
}
|
||||
case PROP_RANDOMVAL:{
|
||||
g_value_set_int (value, aasink->ascii_parms.randomval);
|
||||
break;
|
||||
}
|
||||
case PROP_FRAMES_DISPLAYED:{
|
||||
g_value_set_int (value, aasink->frames_displayed);
|
||||
break;
|
||||
}
|
||||
case PROP_FRAME_TIME:{
|
||||
g_value_set_int (value, aasink->frame_time / 1000000);
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_aasink_open (GstAASink * aasink)
|
||||
{
|
||||
if (!aasink->context) {
|
||||
aa_recommendhidisplay (aa_drivers[aasink->aa_driver]->shortname);
|
||||
|
||||
aasink->context = aa_autoinit (&aasink->ascii_surf);
|
||||
if (aasink->context == NULL) {
|
||||
GST_ELEMENT_ERROR (GST_ELEMENT (aasink), LIBRARY, TOO_LAZY, (NULL),
|
||||
("error opening aalib context"));
|
||||
return FALSE;
|
||||
}
|
||||
aa_autoinitkbd (aasink->context, 0);
|
||||
aa_resizehandler (aasink->context, (void *) aa_resize);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_aasink_close (GstAASink * aasink)
|
||||
{
|
||||
aa_close (aasink->context);
|
||||
aasink->context = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_aasink_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
if (!gst_aasink_open (GST_AASINK (element)))
|
||||
goto open_failed;
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
gst_aasink_close (GST_AASINK (element));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
open_failed:
|
||||
{
|
||||
return GST_STATE_CHANGE_FAILURE;
|
||||
}
|
||||
}
|
54
ext/aalib/gstaasink.h
Normal file
54
ext/aalib/gstaasink.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_AASINK_H__
|
||||
#define __GST_AASINK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
|
||||
#include <aalib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_AASINK (gst_aasink_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstAASink, gst_aasink, GST, AASINK, GstVideoSink)
|
||||
|
||||
struct _GstAASink {
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo info;
|
||||
|
||||
gint frames_displayed;
|
||||
guint64 frame_time;
|
||||
|
||||
aa_context *context;
|
||||
struct aa_hardware_params ascii_surf;
|
||||
struct aa_renderparams ascii_parms;
|
||||
aa_palette palette;
|
||||
gint aa_driver;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (aasink);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_AASINKE_H__ */
|
983
ext/aalib/gstaatv.c
Normal file
983
ext/aalib/gstaatv.c
Normal file
|
@ -0,0 +1,983 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
/**
|
||||
* SECTION:element-aatv
|
||||
* @see_also: #GstAASink
|
||||
*
|
||||
* Transforms video into ascii art.
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch-1.0 videotestsrc ! aatv ! videoconvert ! autovideosink
|
||||
* ]| This pipeline shows the effect of aatv on a test stream.
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstaatv.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define PROP_AATV_COLOR_TEXT_DEFAULT 0xffffffff /* White */
|
||||
#define PROP_AATV_COLOR_BACKGROUND_DEFAULT 0xff000000 /* Black */
|
||||
#define PROP_AATV_COLOR_RAIN_DEFAULT 0xff00ff00 /* Green */
|
||||
#define PROP_AATV_RAIN_MODE_DEFAULT GST_RAIN_OFF
|
||||
#define PROP_BRIGHTNESS_TARGET_MIN_DEFAULT 0.3
|
||||
#define PROP_BRIGHTNESS_TARGET_MAX_DEFAULT 0.4
|
||||
#define PROP_RAIN_SPAWN_DEFAULT 0.2
|
||||
#define PROP_RAIN_DELAY_MIN_DEFAULT 0
|
||||
#define PROP_RAIN_DELAY_MAX_DEFAULT 3
|
||||
#define PROP_RAIN_LENGTH_MIN_DEFAULT 4
|
||||
#define PROP_RAIN_LENGTH_MAX_DEFAULT 30
|
||||
|
||||
/* aatv signals and args */
|
||||
enum
|
||||
{
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDTH,
|
||||
PROP_HEIGHT,
|
||||
PROP_DITHER,
|
||||
PROP_FONT,
|
||||
PROP_CONTRAST,
|
||||
PROP_GAMMA,
|
||||
PROP_RANDOMVAL,
|
||||
PROP_BRIGHTNESS_AUTO,
|
||||
PROP_BRIGHTNESS_ACTUAL,
|
||||
PROP_BRIGHTNESS,
|
||||
PROP_BRIGHTNESS_TARGET_MIN,
|
||||
PROP_BRIGHTNESS_TARGET_MAX,
|
||||
PROP_COLOR_BACKGROUND,
|
||||
PROP_COLOR_TEXT,
|
||||
PROP_COLOR_TEXT_BOLD,
|
||||
PROP_COLOR_TEXT_NORMAL,
|
||||
PROP_COLOR_TEXT_DIM,
|
||||
PROP_COLOR_RAIN,
|
||||
PROP_COLOR_RAIN_BOLD,
|
||||
PROP_COLOR_RAIN_NORMAL,
|
||||
PROP_COLOR_RAIN_DIM,
|
||||
PROP_RAIN_MODE,
|
||||
PROP_RAIN_SPAWN_RATE,
|
||||
PROP_RAIN_DELAY_MIN,
|
||||
PROP_RAIN_DELAY_MAX,
|
||||
PROP_RAIN_LENGTH_MIN,
|
||||
PROP_RAIN_LENGTH_MAX
|
||||
};
|
||||
|
||||
static GstStaticPadTemplate sink_template_tv = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ I420 }"))
|
||||
);
|
||||
static GstStaticPadTemplate src_template_tv = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ RGBA }"))
|
||||
);
|
||||
|
||||
static void gst_aatv_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_aatv_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
#define GST_TYPE_AATV_RAIN_MODE (gst_aatv_rain_mode_get_type())
|
||||
|
||||
static GType
|
||||
gst_aatv_rain_mode_get_type (void)
|
||||
{
|
||||
static GType rain_mode = 0;
|
||||
|
||||
static const GEnumValue rain_modes[] = {
|
||||
{GST_RAIN_OFF, "No Rain", "none"},
|
||||
{GST_RAIN_DOWN, "Rain Down", "down"},
|
||||
{GST_RAIN_UP, "Rain Up", "up"},
|
||||
{GST_RAIN_LEFT, "Rain Left", "left"},
|
||||
{GST_RAIN_RIGHT, "Rain Right", "right"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
|
||||
if (!rain_mode) {
|
||||
rain_mode = g_enum_register_static ("GstAATvRainModes", rain_modes);
|
||||
}
|
||||
return rain_mode;
|
||||
}
|
||||
|
||||
#define gst_aatv_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstAATv, gst_aatv, GST_TYPE_VIDEO_FILTER);
|
||||
GST_ELEMENT_REGISTER_DEFINE (aatv, "aatv", GST_RANK_NONE, GST_TYPE_AATV);
|
||||
|
||||
static void
|
||||
gst_aatv_scale (GstAATv * aatv, guchar * src, guchar * dest,
|
||||
gint sw, gint sh, gint ss, gint dw, gint dh)
|
||||
{
|
||||
gint ypos, yinc, y;
|
||||
gint xpos, xinc, x;
|
||||
|
||||
g_return_if_fail ((dw != 0) && (dh != 0));
|
||||
|
||||
ypos = 0x10000;
|
||||
yinc = (sh << 16) / dh;
|
||||
xinc = (sw << 16) / dw;
|
||||
|
||||
for (y = dh; y; y--) {
|
||||
while (ypos > 0x10000) {
|
||||
ypos -= 0x10000;
|
||||
src += ss;
|
||||
}
|
||||
|
||||
xpos = 0x10000;
|
||||
|
||||
{
|
||||
guchar *destp = dest;
|
||||
guchar *srcp = src;
|
||||
|
||||
for (x = dw; x; x--) {
|
||||
while (xpos >= 0x10000L) {
|
||||
srcp++;
|
||||
xpos -= 0x10000L;
|
||||
}
|
||||
*destp++ = *srcp;
|
||||
xpos += xinc;
|
||||
}
|
||||
}
|
||||
dest += dw;
|
||||
ypos += yinc;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_rain (GstAATv * aatv)
|
||||
{
|
||||
gint i;
|
||||
gboolean obstructed;
|
||||
|
||||
GstAATvDroplet *raindrops = aatv->raindrops;
|
||||
|
||||
for (i = 0; i < aatv->rain_width; i++) {
|
||||
if (raindrops[i].enabled == FALSE) {
|
||||
if (g_random_double () < aatv->rain_spawn_rate) {
|
||||
|
||||
obstructed = FALSE;
|
||||
|
||||
/* Don't let adjacent lines be enabled at the same time. */
|
||||
if (i > 0)
|
||||
if (raindrops[i - 1].enabled == TRUE)
|
||||
if (raindrops[i - 1].location - raindrops[i - 1].length <
|
||||
aatv->rain_height / 4)
|
||||
obstructed = TRUE;
|
||||
|
||||
if (i < aatv->rain_width)
|
||||
if (raindrops[i + 1].enabled == TRUE)
|
||||
if (raindrops[i + 1].location - raindrops[i + 1].length <
|
||||
aatv->rain_height / 4)
|
||||
obstructed = TRUE;
|
||||
|
||||
if (obstructed == FALSE) {
|
||||
raindrops[i].location = 0;
|
||||
raindrops[i].length =
|
||||
g_random_int_range (aatv->rain_length_min, aatv->rain_length_max);
|
||||
raindrops[i].delay =
|
||||
g_random_int_range (aatv->rain_delay_min, aatv->rain_delay_max);
|
||||
raindrops[i].delay_counter = 0;
|
||||
raindrops[i].enabled = TRUE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
raindrops[i].delay_counter++;
|
||||
if (raindrops[i].delay_counter > raindrops[i].delay) {
|
||||
raindrops[i].delay_counter = 0;
|
||||
raindrops[i].location++;
|
||||
}
|
||||
if (raindrops[i].location - raindrops[i].length > aatv->rain_height) {
|
||||
raindrops[i].enabled = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_render (GstAATv * aatv, gint32 * dest)
|
||||
{
|
||||
gint x, y;
|
||||
guint font_x, font_y;
|
||||
guint background_pixels = 0;
|
||||
guint foreground_pixels = 0;
|
||||
guint char_index = 0;
|
||||
guint dest_index = 0;
|
||||
|
||||
gchar input_letter, input_glyph, attribute;
|
||||
gboolean rain_pixel;
|
||||
|
||||
GstAATvDroplet *raindrops = aatv->raindrops;
|
||||
|
||||
const guchar *font_base_address = aa_currentfont (aatv->context)->data;
|
||||
guint font_height = aa_currentfont (aatv->context)->height;
|
||||
|
||||
/* loop through the canvas height */
|
||||
for (y = 0; y < aa_scrheight (aatv->context); y++) {
|
||||
/* loop through the height of a character's font */
|
||||
for (font_y = 0; font_y < font_height; font_y++) {
|
||||
/* loop through the canvas width */
|
||||
for (x = 0; x < aa_scrwidth (aatv->context); x++) {
|
||||
|
||||
/* which char are we working on */
|
||||
char_index = x + y * aa_scrwidth (aatv->context);
|
||||
/* lookup what character we need to render */
|
||||
input_letter = aa_text (aatv->context)[char_index];
|
||||
/* check for special attributes like bold or dimmed */
|
||||
attribute = aa_attrs (aatv->context)[char_index];
|
||||
/* look that character up in the font glyph table */
|
||||
input_glyph = font_base_address[input_letter * font_height + font_y];
|
||||
|
||||
/* check if we need to re-color this character for rain effect */
|
||||
rain_pixel = FALSE;
|
||||
|
||||
if (aatv->rain_mode == GST_RAIN_DOWN) {
|
||||
if (raindrops[x].enabled)
|
||||
if (y <= raindrops[x].location)
|
||||
if (y >= raindrops[x].location - raindrops[x].length)
|
||||
rain_pixel = TRUE;
|
||||
} else if (aatv->rain_mode == GST_RAIN_UP) {
|
||||
if (raindrops[x].enabled)
|
||||
if (aatv->rain_height - y <= raindrops[x].location)
|
||||
if (aatv->rain_height - y >=
|
||||
raindrops[x].location - raindrops[x].length)
|
||||
rain_pixel = TRUE;
|
||||
} else if (aatv->rain_mode == GST_RAIN_LEFT) {
|
||||
if (raindrops[y].enabled)
|
||||
if (x <= raindrops[y].location)
|
||||
if (x >= raindrops[y].location - raindrops[y].length)
|
||||
rain_pixel = TRUE;
|
||||
} else if (aatv->rain_mode == GST_RAIN_RIGHT) {
|
||||
if (raindrops[y].enabled)
|
||||
if (aatv->rain_height - x <= raindrops[y].location)
|
||||
if (aatv->rain_height - x >=
|
||||
raindrops[y].location - raindrops[y].length)
|
||||
rain_pixel = TRUE;
|
||||
}
|
||||
/* loop through the width of a character's font (always 8 pixels wide) */
|
||||
for (font_x = 0; font_x < 8; font_x++) {
|
||||
guint32 *pixel_argb;
|
||||
if (CHECK_BIT (input_glyph, font_x)) {
|
||||
if (attribute == AA_DIM) {
|
||||
if (rain_pixel)
|
||||
pixel_argb = &aatv->color_rain_dim;
|
||||
else
|
||||
pixel_argb = &aatv->color_text_dim;
|
||||
} else if (attribute == AA_BOLD) {
|
||||
if (rain_pixel)
|
||||
pixel_argb = &aatv->color_rain_bold;
|
||||
else
|
||||
pixel_argb = &aatv->color_text_bold;
|
||||
} else {
|
||||
if (rain_pixel)
|
||||
pixel_argb = &aatv->color_rain_normal;
|
||||
else
|
||||
pixel_argb = &aatv->color_text_normal;
|
||||
}
|
||||
foreground_pixels++;
|
||||
} else {
|
||||
pixel_argb = &aatv->color_background;
|
||||
background_pixels++;
|
||||
}
|
||||
dest[dest_index++] = *pixel_argb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aatv->lit_percentage =
|
||||
0.2 * (aatv->lit_percentage) +
|
||||
0.8 * (float) foreground_pixels / background_pixels;
|
||||
|
||||
if (aatv->auto_brightness) {
|
||||
if (aatv->lit_percentage > aatv->brightness_target_max)
|
||||
if (aatv->ascii_parms.bright > -254)
|
||||
aatv->ascii_parms.bright--;
|
||||
if (aatv->lit_percentage < aatv->brightness_target_min)
|
||||
if (aatv->ascii_parms.bright < 254)
|
||||
aatv->ascii_parms.bright++;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_aatv_transform_frame (GstVideoFilter * vfilter, GstVideoFrame * in_frame,
|
||||
GstVideoFrame * out_frame)
|
||||
{
|
||||
GstAATv *aatv = GST_AATV (vfilter);
|
||||
|
||||
if (aatv->rain_mode != GST_RAIN_OFF)
|
||||
gst_aatv_rain (aatv);
|
||||
|
||||
GST_OBJECT_LOCK (aatv);
|
||||
|
||||
gst_aatv_scale (aatv, GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0), /* src */
|
||||
aa_image (aatv->context), /* dest */
|
||||
GST_VIDEO_FRAME_WIDTH (in_frame), /* sw */
|
||||
GST_VIDEO_FRAME_HEIGHT (in_frame), /* sh */
|
||||
GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0), /* ss */
|
||||
aa_imgwidth (aatv->context), /* dw */
|
||||
aa_imgheight (aatv->context)); /* dh */
|
||||
|
||||
aa_render (aatv->context, &aatv->ascii_parms, 0, 0,
|
||||
aa_imgwidth (aatv->context), aa_imgheight (aatv->context));
|
||||
gst_aatv_render (aatv, GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0));
|
||||
|
||||
GST_OBJECT_UNLOCK (aatv);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
|
||||
#define GST_TYPE_AADITHER (gst_aatv_dither_get_type())
|
||||
static GType
|
||||
gst_aatv_dither_get_type (void)
|
||||
{
|
||||
static GType dither_type = 0;
|
||||
|
||||
if (!dither_type) {
|
||||
GEnumValue *ditherers;
|
||||
gint n_ditherers;
|
||||
gint i;
|
||||
|
||||
for (n_ditherers = 0; aa_dithernames[n_ditherers]; n_ditherers++) {
|
||||
/* count number of ditherers */
|
||||
}
|
||||
|
||||
ditherers = g_new0 (GEnumValue, n_ditherers + 1);
|
||||
|
||||
for (i = 0; i < n_ditherers; i++) {
|
||||
ditherers[i].value = i;
|
||||
ditherers[i].value_name = g_strdup (aa_dithernames[i]);
|
||||
ditherers[i].value_nick =
|
||||
g_strdelimit (g_strdup (aa_dithernames[i]), " _", '-');
|
||||
}
|
||||
|
||||
ditherers[i].value = 0;
|
||||
ditherers[i].value_name = NULL;
|
||||
ditherers[i].value_nick = NULL;
|
||||
|
||||
dither_type = g_enum_register_static ("GstAATvDitherers", ditherers);
|
||||
}
|
||||
return dither_type;
|
||||
}
|
||||
|
||||
#define GST_TYPE_AAFONT (gst_aatv_font_get_type())
|
||||
static GType
|
||||
gst_aatv_font_get_type (void)
|
||||
{
|
||||
static GType font_type = 0;
|
||||
|
||||
if (!font_type) {
|
||||
GEnumValue *fonts;
|
||||
gint n_fonts;
|
||||
gint i;
|
||||
|
||||
for (n_fonts = 0; aa_fonts[n_fonts]; n_fonts++) {
|
||||
/* count number of fonts */
|
||||
}
|
||||
|
||||
fonts = g_new0 (GEnumValue, n_fonts + 1);
|
||||
|
||||
for (i = 0; i < n_fonts; i++) {
|
||||
fonts[i].value = i;
|
||||
fonts[i].value_name = g_strdup (aa_fonts[i]->shortname);
|
||||
fonts[i].value_nick =
|
||||
g_strdelimit (g_strdup (aa_fonts[i]->name), " _", '-');
|
||||
}
|
||||
fonts[i].value = 0;
|
||||
fonts[i].value_name = NULL;
|
||||
fonts[i].value_nick = NULL;
|
||||
|
||||
font_type = g_enum_register_static ("GstAATvFonts", fonts);
|
||||
}
|
||||
return font_type;
|
||||
}
|
||||
|
||||
/* use a custom transform_caps */
|
||||
static GstCaps *
|
||||
gst_aatv_transform_caps (GstBaseTransform * trans, GstPadDirection direction,
|
||||
GstCaps * caps, GstCaps * filter)
|
||||
{
|
||||
GstCaps *ret;
|
||||
GstAATv *aatv = GST_AATV (trans);
|
||||
GValue formats = G_VALUE_INIT;
|
||||
GValue value = G_VALUE_INIT;
|
||||
GValue src_width = G_VALUE_INIT;
|
||||
GValue src_height = G_VALUE_INIT;
|
||||
|
||||
if (direction == GST_PAD_SINK) {
|
||||
|
||||
ret = gst_caps_copy (caps);
|
||||
|
||||
g_value_init (&src_width, G_TYPE_INT);
|
||||
g_value_init (&src_height, G_TYPE_INT);
|
||||
/* calculate output resolution from canvas size and font size */
|
||||
|
||||
g_value_set_int (&src_width, aa_defparams.width * 8);
|
||||
g_value_set_int (&src_height,
|
||||
aa_defparams.height * aa_currentfont (aatv->context)->height);
|
||||
|
||||
gst_caps_set_value (ret, "width", &src_width);
|
||||
gst_caps_set_value (ret, "height", &src_height);
|
||||
/* force RGBA output format */
|
||||
g_value_init (&formats, GST_TYPE_LIST);
|
||||
g_value_init (&value, G_TYPE_STRING);
|
||||
g_value_set_string (&value, "RGBA");
|
||||
gst_value_list_append_value (&formats, &value);
|
||||
|
||||
gst_caps_set_value (ret, "format", &formats);
|
||||
|
||||
} else {
|
||||
ret = gst_static_pad_template_get_caps (&sink_template_tv);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_aatv_finalize (GObject * object)
|
||||
{
|
||||
GstAATv *aatv = GST_AATV (object);
|
||||
free (aatv->raindrops);
|
||||
if (aatv->context != NULL)
|
||||
aa_close (aatv->context);
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_aatv_class_init (GstAATvClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstVideoFilterClass *videofilter_class;
|
||||
GstBaseTransformClass *transform_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
videofilter_class = (GstVideoFilterClass *) klass;
|
||||
transform_class = (GstBaseTransformClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_aatv_set_property;
|
||||
gobject_class->get_property = gst_aatv_get_property;
|
||||
gobject_class->finalize = gst_aatv_finalize;
|
||||
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
|
||||
g_param_spec_int ("width", "width", "Width of the ASCII canvas", 0,
|
||||
G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
|
||||
g_param_spec_int ("height", "height", "Height of the ASCII canvas", 0,
|
||||
G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DITHER,
|
||||
g_param_spec_enum ("dither", "dither",
|
||||
"Add noise to more closely approximate gray levels.",
|
||||
GST_TYPE_AADITHER, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT,
|
||||
g_param_spec_enum ("font", "font", "AAlib Font", GST_TYPE_AAFONT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT,
|
||||
g_param_spec_uint ("color-text", "color-text",
|
||||
"Automatically sets color-test-bold, color-text-normal, and color-text-dim with progressively dimmer values (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT_BOLD,
|
||||
g_param_spec_uint ("color-text-bold", "color-text-bold",
|
||||
"Sets the brightest color to use for foreground ASCII text (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_COLOR_TEXT_NORMAL, g_param_spec_uint ("color-text-normal",
|
||||
"color-text-normal",
|
||||
"Sets the normal brightness color to use for foreground ASCII text (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_TEXT_DIM,
|
||||
g_param_spec_uint ("color-text-dim", "color-text-dim",
|
||||
"Sets the dimmest brightness color to use for foreground ASCII text (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_COLOR_BACKGROUND, g_param_spec_uint ("color-background",
|
||||
"color-background",
|
||||
"Color to use as the background for the ASCII text (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS,
|
||||
g_param_spec_int ("brightness", "brightness", "Brightness", -255,
|
||||
255, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS_AUTO,
|
||||
g_param_spec_boolean ("brightness-auto", "brightness-auto",
|
||||
"Automatically adjust brightness based on the previous frame's foreground pixel fill percentage",
|
||||
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_BRIGHTNESS_ACTUAL, g_param_spec_float ("brightness-actual",
|
||||
"brightness-actual",
|
||||
"Actual calculated foreground pixel fill percentage", 0.0, 1.0, 0.0,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_BRIGHTNESS_TARGET_MIN, g_param_spec_float ("brightness-min",
|
||||
"brightness-min",
|
||||
"Minimum target foreground pixel fill percentage for automatic brightness control",
|
||||
0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_SPAWN_RATE,
|
||||
g_param_spec_float ("rain-spawn-rate", "rain-spawn-rate",
|
||||
"Percentage chance for a raindrop to spawn", 0.0, 1.0, 0.0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_BRIGHTNESS_TARGET_MAX, g_param_spec_float ("brightness-max",
|
||||
"brightness-max",
|
||||
"Maximum target foreground pixel fill percentage for automatic brightness control",
|
||||
0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONTRAST,
|
||||
g_param_spec_int ("contrast", "contrast", "Contrast", 0, G_MAXUINT8,
|
||||
0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAMMA,
|
||||
g_param_spec_float ("gamma", "gamma", "Gamma correction", 0.0, 5.0, 1.0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RANDOMVAL,
|
||||
g_param_spec_int ("randomval", "randomval",
|
||||
"Adds a random value in the range (-randomval/2,ranomval/2) to each pixel during rendering",
|
||||
0, 255, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_DELAY_MIN,
|
||||
g_param_spec_int ("rain-delay-min", "rain-delay-min",
|
||||
"Minimum frame delay between rain motion", 0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_DELAY_MAX,
|
||||
g_param_spec_int ("rain-delay-max", "rain-delay-max",
|
||||
"Maximum frame delay between rain motion", 0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_LENGTH_MIN,
|
||||
g_param_spec_int ("rain-length-min", "rain-length-min",
|
||||
"Minimum length of a rain", 0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_LENGTH_MAX,
|
||||
g_param_spec_int ("rain-length-max", "rain-length-max",
|
||||
"Maximum length of a rain", 0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RAIN_MODE,
|
||||
g_param_spec_enum ("rain-mode", "rain-mode",
|
||||
"Set the direction of raindrops", GST_TYPE_AATV_RAIN_MODE, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN,
|
||||
g_param_spec_uint ("color-rain", "color-rain",
|
||||
"Automatically sets color-rain-bold, color-rain-normal, and color-rain-dim with progressively dimmer values (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN_BOLD,
|
||||
g_param_spec_uint ("color-rain-bold", "color-rain-bold",
|
||||
"Sets the brightest color to use for foreground ASCII text rain overlays (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass),
|
||||
PROP_COLOR_RAIN_NORMAL, g_param_spec_uint ("color-rain-normal",
|
||||
"color-rain-normal",
|
||||
"Sets the normal brightness color to use for foreground ASCII text rain overlays (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR_RAIN_DIM,
|
||||
g_param_spec_uint ("color-rain-dim", "color-rain-dim",
|
||||
"Sets the dimmest brightness color to use for foreground ASCII text rain overlays (big-endian ARGB).",
|
||||
0, G_MAXUINT32, 0,
|
||||
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&sink_template_tv);
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&src_template_tv);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class,
|
||||
"aaTV effect", "Filter/Effect/Video",
|
||||
"ASCII art effect", "Eric Marks <bigmarkslp@gmail.com>");
|
||||
|
||||
transform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_aatv_transform_caps);
|
||||
videofilter_class->transform_frame =
|
||||
GST_DEBUG_FUNCPTR (gst_aatv_transform_frame);
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_AATV_RAIN_MODE, 0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_AADITHER, 0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_AAFONT, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_rain_init (GstAATv * aatv)
|
||||
{
|
||||
switch (aatv->rain_mode) {
|
||||
case GST_RAIN_DOWN:
|
||||
case GST_RAIN_UP:
|
||||
aatv->rain_width = aa_defparams.width;
|
||||
aatv->rain_height = aa_defparams.height;
|
||||
break;
|
||||
case GST_RAIN_LEFT:
|
||||
case GST_RAIN_RIGHT:
|
||||
aatv->rain_width = aa_defparams.height;
|
||||
aatv->rain_height = aa_defparams.width;
|
||||
break;
|
||||
case GST_RAIN_OFF:
|
||||
aatv->rain_width = 0;
|
||||
aatv->rain_height = 0;
|
||||
}
|
||||
|
||||
if (aatv->context != NULL)
|
||||
aa_close (aatv->context);
|
||||
aatv->context = aa_init (&mem_d, &aa_defparams, NULL);
|
||||
aa_setfont (aatv->context, aa_fonts[0]);
|
||||
|
||||
aatv->raindrops =
|
||||
realloc (aatv->raindrops,
|
||||
aatv->rain_width * sizeof (struct _GstAATvDroplet));
|
||||
for (gint i = 0; i < aatv->rain_width; i++)
|
||||
aatv->raindrops[i].enabled = FALSE;
|
||||
|
||||
}
|
||||
|
||||
static guint32
|
||||
gst_aatv_set_color (guint32 input_color, guint8 dim)
|
||||
{
|
||||
guint8 a = ((input_color >> 24) & 0xff);
|
||||
guint8 b = ((input_color >> 16) & 0xff) >> dim;
|
||||
guint8 g = ((input_color >> 8) & 0xff) >> dim;
|
||||
guint8 r = ((input_color >> 0) & 0xff) >> dim;
|
||||
|
||||
return ((a << 24) | (b << 16) | (g << 8) | (r << 0));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_set_color_rain (GstAATv * aatv, guint input_color)
|
||||
{
|
||||
aatv->color_rain = input_color;
|
||||
aatv->color_rain_bold = gst_aatv_set_color (input_color, 0);
|
||||
aatv->color_rain_normal = gst_aatv_set_color (aatv->color_rain_bold, 1);
|
||||
aatv->color_rain_dim = gst_aatv_set_color (aatv->color_rain_normal, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_set_color_text (GstAATv * aatv, guint input_color)
|
||||
{
|
||||
aatv->color_text = input_color;
|
||||
aatv->color_text_bold = gst_aatv_set_color (input_color, 0);
|
||||
aatv->color_text_normal = gst_aatv_set_color (aatv->color_text_bold, 1);
|
||||
aatv->color_text_dim = gst_aatv_set_color (aatv->color_text_normal, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_init (GstAATv * aatv)
|
||||
{
|
||||
aa_defparams.width = 80;
|
||||
aa_defparams.height = 24;
|
||||
|
||||
aatv->ascii_parms.bright = 0;
|
||||
aatv->ascii_parms.contrast = 0;
|
||||
aatv->ascii_parms.gamma = 1.0;
|
||||
aatv->ascii_parms.dither = 0;
|
||||
aatv->ascii_parms.inversion = 0;
|
||||
aatv->ascii_parms.randomval = 0;
|
||||
|
||||
aatv->color_background =
|
||||
gst_aatv_set_color (PROP_AATV_COLOR_BACKGROUND_DEFAULT, 0);
|
||||
gst_aatv_set_color_rain (aatv, PROP_AATV_COLOR_RAIN_DEFAULT);
|
||||
gst_aatv_set_color_text (aatv, PROP_AATV_COLOR_TEXT_DEFAULT);
|
||||
|
||||
aatv->rain_mode = PROP_AATV_RAIN_MODE_DEFAULT;
|
||||
|
||||
gst_aatv_rain_init (aatv);
|
||||
|
||||
aatv->rain_spawn_rate = PROP_RAIN_SPAWN_DEFAULT;
|
||||
|
||||
aatv->auto_brightness = TRUE;
|
||||
aatv->brightness_target_min = PROP_BRIGHTNESS_TARGET_MIN_DEFAULT;
|
||||
aatv->brightness_target_max = PROP_BRIGHTNESS_TARGET_MAX_DEFAULT;
|
||||
aatv->lit_percentage =
|
||||
(PROP_BRIGHTNESS_TARGET_MIN_DEFAULT +
|
||||
PROP_BRIGHTNESS_TARGET_MAX_DEFAULT) / 2;
|
||||
|
||||
aatv->rain_length_min = PROP_RAIN_LENGTH_MIN_DEFAULT;
|
||||
aatv->rain_length_max = PROP_RAIN_LENGTH_MAX_DEFAULT;
|
||||
|
||||
aatv->rain_delay_min = PROP_RAIN_DELAY_MIN_DEFAULT;
|
||||
aatv->rain_delay_max = PROP_RAIN_DELAY_MAX_DEFAULT;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_set_property (GObject * object, guint prop_id, const GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstAATv *aatv = GST_AATV (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDTH:{
|
||||
aa_defparams.width = g_value_get_int (value);
|
||||
/* recalculate output resolution based on new width */
|
||||
gst_aatv_rain_init (aatv);
|
||||
gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
|
||||
break;
|
||||
}
|
||||
case PROP_HEIGHT:{
|
||||
aa_defparams.height = g_value_get_int (value);
|
||||
/* recalculate output resolution based on new height */
|
||||
gst_aatv_rain_init (aatv);
|
||||
gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
|
||||
break;
|
||||
}
|
||||
case PROP_DITHER:{
|
||||
aatv->ascii_parms.dither = g_value_get_enum (value);
|
||||
break;
|
||||
}
|
||||
case PROP_FONT:{
|
||||
aa_setfont (aatv->context, aa_fonts[g_value_get_enum (value)]);
|
||||
/* recalculate output resolution based on new font */
|
||||
gst_pad_mark_reconfigure (GST_BASE_TRANSFORM_SRC_PAD (object));
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS:{
|
||||
aatv->ascii_parms.bright = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_CONTRAST:{
|
||||
aatv->ascii_parms.contrast = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_GAMMA:{
|
||||
aatv->ascii_parms.gamma = g_value_get_float (value);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_TARGET_MIN:{
|
||||
if (g_value_get_float (value) <= aatv->brightness_target_max)
|
||||
aatv->brightness_target_min = g_value_get_float (value);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_TARGET_MAX:{
|
||||
if (g_value_get_float (value) >= aatv->brightness_target_min)
|
||||
aatv->brightness_target_max = g_value_get_float (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_SPAWN_RATE:{
|
||||
aatv->rain_spawn_rate = g_value_get_float (value);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT:{
|
||||
aatv->color_text = g_value_get_uint (value);
|
||||
gst_aatv_set_color_text (aatv, aatv->color_text);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_BOLD:{
|
||||
aatv->color_text_bold = gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_NORMAL:{
|
||||
aatv->color_text_normal =
|
||||
gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_DIM:{
|
||||
aatv->color_text_dim = gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_BACKGROUND:{
|
||||
aatv->color_background = gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN:{
|
||||
aatv->color_rain = g_value_get_uint (value);
|
||||
gst_aatv_set_color_rain (aatv, aatv->color_rain);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_BOLD:{
|
||||
aatv->color_rain_bold = gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_NORMAL:{
|
||||
aatv->color_rain_normal =
|
||||
gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_DIM:{
|
||||
aatv->color_rain_dim = gst_aatv_set_color (g_value_get_uint (value), 0);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_AUTO:{
|
||||
aatv->auto_brightness = g_value_get_boolean (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RANDOMVAL:{
|
||||
aatv->ascii_parms.randomval = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_DELAY_MIN:{
|
||||
if (g_value_get_float (value) <= aatv->rain_delay_max)
|
||||
aatv->rain_delay_min = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_DELAY_MAX:{
|
||||
if (g_value_get_float (value) >= aatv->rain_delay_min)
|
||||
aatv->rain_delay_max = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_LENGTH_MIN:{
|
||||
if (g_value_get_float (value) <= aatv->rain_length_max)
|
||||
aatv->rain_length_min = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_LENGTH_MAX:{
|
||||
if (g_value_get_float (value) >= aatv->rain_length_min)
|
||||
aatv->rain_length_max = g_value_get_int (value);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_MODE:{
|
||||
aatv->rain_mode = g_value_get_enum (value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_aatv_get_property (GObject * object, guint prop_id, GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstAATv *aatv = GST_AATV (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_BRIGHTNESS_ACTUAL:{
|
||||
g_value_set_float (value, aatv->lit_percentage);
|
||||
break;
|
||||
}
|
||||
case PROP_WIDTH:{
|
||||
g_value_set_int (value, aa_defparams.width);
|
||||
break;
|
||||
}
|
||||
case PROP_HEIGHT:{
|
||||
g_value_set_int (value, aa_defparams.height);
|
||||
|
||||
break;
|
||||
}
|
||||
case PROP_DITHER:{
|
||||
g_value_set_enum (value, aatv->ascii_parms.dither);
|
||||
break;
|
||||
}
|
||||
case PROP_FONT:{
|
||||
g_value_set_enum (value, aatv->ascii_parms.dither);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS:{
|
||||
g_value_set_int (value, aatv->ascii_parms.bright);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_AUTO:{
|
||||
g_value_set_boolean (value, aatv->auto_brightness);
|
||||
break;
|
||||
}
|
||||
case PROP_CONTRAST:{
|
||||
g_value_set_int (value, aatv->ascii_parms.contrast);
|
||||
break;
|
||||
}
|
||||
case PROP_GAMMA:{
|
||||
g_value_set_float (value, aatv->ascii_parms.gamma);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_SPAWN_RATE:{
|
||||
g_value_set_float (value, aatv->rain_spawn_rate);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_TARGET_MIN:{
|
||||
g_value_set_float (value, aatv->brightness_target_min);
|
||||
break;
|
||||
}
|
||||
case PROP_BRIGHTNESS_TARGET_MAX:{
|
||||
g_value_set_float (value, aatv->brightness_target_max);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT:{
|
||||
g_value_set_uint (value, aatv->color_text);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_BOLD:{
|
||||
g_value_set_uint (value, aatv->color_text_bold);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_NORMAL:{
|
||||
g_value_set_uint (value, aatv->color_text_normal);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_TEXT_DIM:{
|
||||
g_value_set_uint (value, aatv->color_text_dim);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_BACKGROUND:{
|
||||
g_value_set_uint (value, aatv->color_background);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN:{
|
||||
g_value_set_uint (value, aatv->color_rain);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_BOLD:{
|
||||
g_value_set_uint (value, aatv->color_rain_bold);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_NORMAL:{
|
||||
g_value_set_uint (value, aatv->color_rain_normal);
|
||||
break;
|
||||
}
|
||||
case PROP_COLOR_RAIN_DIM:{
|
||||
g_value_set_uint (value, aatv->color_rain_dim);
|
||||
break;
|
||||
}
|
||||
case PROP_RANDOMVAL:{
|
||||
g_value_set_int (value, aatv->ascii_parms.randomval);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_MODE:{
|
||||
g_value_set_enum (value, aatv->rain_mode);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_DELAY_MIN:{
|
||||
g_value_set_int (value, aatv->rain_delay_min);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_DELAY_MAX:{
|
||||
g_value_set_int (value, aatv->rain_delay_max);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_LENGTH_MIN:{
|
||||
g_value_set_int (value, aatv->rain_length_min);
|
||||
break;
|
||||
}
|
||||
case PROP_RAIN_LENGTH_MAX:{
|
||||
g_value_set_int (value, aatv->rain_length_max);
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
92
ext/aalib/gstaatv.h
Normal file
92
ext/aalib/gstaatv.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2019> Eric Marks <bigmarkslp@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_AATV_H__
|
||||
#define __GST_AATV_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideofilter.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <aalib.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_AATV (gst_aatv_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstAATv, gst_aatv, GST, AATV, GstVideoFilter)
|
||||
|
||||
typedef struct _GstAATvDroplet GstAATvDroplet;
|
||||
typedef struct _GstAATvARGB GstAATvARGB;
|
||||
|
||||
typedef enum {
|
||||
GST_RAIN_OFF,
|
||||
GST_RAIN_DOWN,
|
||||
GST_RAIN_UP,
|
||||
GST_RAIN_LEFT,
|
||||
GST_RAIN_RIGHT
|
||||
} GstRainMode;
|
||||
|
||||
struct _GstAATvDroplet {
|
||||
gboolean enabled;
|
||||
gint location;
|
||||
gint length;
|
||||
gint delay;
|
||||
gint delay_counter;
|
||||
};
|
||||
|
||||
struct _GstAATv {
|
||||
GstVideoFilter videofilter;
|
||||
|
||||
aa_context *context;
|
||||
|
||||
guint32 color_text;
|
||||
guint32 color_text_bold,color_text_normal,color_text_dim;
|
||||
guint32 color_rain;
|
||||
guint32 color_rain_bold,color_rain_normal,color_rain_dim;
|
||||
guint32 color_background;
|
||||
|
||||
GstRainMode rain_mode;
|
||||
|
||||
gint rain_width;
|
||||
gint rain_height;
|
||||
|
||||
gint rain_length_min;
|
||||
gint rain_length_max;
|
||||
|
||||
gint rain_delay_min;
|
||||
gint rain_delay_max;
|
||||
|
||||
gfloat rain_spawn_rate;
|
||||
|
||||
gboolean auto_brightness;
|
||||
gfloat brightness_target_min;
|
||||
gfloat brightness_target_max;
|
||||
gfloat lit_percentage;
|
||||
|
||||
GstAATvDroplet * raindrops;
|
||||
struct aa_renderparams ascii_parms;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (aatv);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
#endif /* __GST_AASINKE_H__ */
|
28
ext/aalib/meson.build
Normal file
28
ext/aalib/meson.build
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Very much not going to implement all kinds of logic around aalib-config
|
||||
|
||||
have_aalib = false
|
||||
|
||||
# TODO: https://github.com/mesonbuild/meson/issues/3940
|
||||
aalib_option = get_option('aalib')
|
||||
if not aalib_option.disabled()
|
||||
have_aalib = cc.has_header('aalib.h')
|
||||
if not have_aalib and aalib_option.enabled()
|
||||
error('aalib plugin enabled, but aalib.h not found')
|
||||
endif
|
||||
endif
|
||||
|
||||
if have_aalib
|
||||
libaa_dep = cc.find_library('aa', required : aalib_option)
|
||||
if libaa_dep.found()
|
||||
gstaasink = library('gstaasink', ['gstaasink.c','gstaatv.c', 'gstaaplugin.c'],
|
||||
c_args : gst_plugins_good_args,
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [gstvideo_dep, gstbase_dep, libaa_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir
|
||||
)
|
||||
pkgconfig.generate(gstaasink, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstaasink]
|
||||
endif
|
||||
endif
|
39
ext/cairo/gstcairo.c
Normal file
39
ext/cairo/gstcairo.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2003,2004> David Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gstcairooverlay.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
return GST_ELEMENT_REGISTER (cairooverlay, plugin);
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, cairo,
|
||||
"Cairo-based elements", plugin_init, VERSION,
|
||||
GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
|
582
ext/cairo/gstcairooverlay.c
Normal file
582
ext/cairo/gstcairooverlay.c
Normal file
|
@ -0,0 +1,582 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-cairooverlay
|
||||
* @title: cairooverlay
|
||||
*
|
||||
* cairooverlay renders an overlay using a application provided render function.
|
||||
*
|
||||
* The full example can be found in tests/examples/cairo/cairo_overlay.c
|
||||
*
|
||||
* ## Example code
|
||||
* |[
|
||||
*
|
||||
* #include <gst/gst.h>
|
||||
* #include <gst/video/video.h>
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* typedef struct {
|
||||
* gboolean valid;
|
||||
* int width;
|
||||
* int height;
|
||||
* } CairoOverlayState;
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* static void
|
||||
* prepare_overlay (GstElement * overlay, GstCaps * caps, gpointer user_data)
|
||||
* {
|
||||
* CairoOverlayState *state = (CairoOverlayState *)user_data;
|
||||
*
|
||||
* gst_video_format_parse_caps (caps, NULL, &state->width, &state->height);
|
||||
* state->valid = TRUE;
|
||||
* }
|
||||
*
|
||||
* static void
|
||||
* draw_overlay (GstElement * overlay, cairo_t * cr, guint64 timestamp,
|
||||
* guint64 duration, gpointer user_data)
|
||||
* {
|
||||
* CairoOverlayState *s = (CairoOverlayState *)user_data;
|
||||
* double scale;
|
||||
*
|
||||
* if (!s->valid)
|
||||
* return;
|
||||
*
|
||||
* scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
|
||||
* cairo_translate(cr, s->width/2, (s->height/2)-30);
|
||||
* cairo_scale (cr, scale, scale);
|
||||
*
|
||||
* cairo_move_to (cr, 0, 0);
|
||||
* cairo_curve_to (cr, 0,-30, -50,-30, -50,0);
|
||||
* cairo_curve_to (cr, -50,30, 0,35, 0,60 );
|
||||
* cairo_curve_to (cr, 0,35, 50,30, 50,0 ); *
|
||||
* cairo_curve_to (cr, 50,-30, 0,-30, 0,0 );
|
||||
* cairo_set_source_rgba (cr, 0.9, 0.0, 0.1, 0.7);
|
||||
* cairo_fill (cr);
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* cairo_overlay = gst_element_factory_make ("cairooverlay", "overlay");
|
||||
*
|
||||
* g_signal_connect (cairo_overlay, "draw", G_CALLBACK (draw_overlay),
|
||||
* overlay_state);
|
||||
* g_signal_connect (cairo_overlay, "caps-changed",
|
||||
* G_CALLBACK (prepare_overlay), overlay_state);
|
||||
* ...
|
||||
*
|
||||
* ]|
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstcairooverlay.h"
|
||||
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <cairo.h>
|
||||
|
||||
/* RGB16 is native-endianness in GStreamer */
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ BGRx, BGRA, RGB16 }")
|
||||
#else
|
||||
#define TEMPLATE_CAPS GST_VIDEO_CAPS_MAKE("{ xRGB, ARGB, RGB16 }")
|
||||
#endif
|
||||
|
||||
GST_DEBUG_CATEGORY (cairo_debug);
|
||||
|
||||
static GstStaticPadTemplate gst_cairo_overlay_src_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (TEMPLATE_CAPS)
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate gst_cairo_overlay_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (TEMPLATE_CAPS)
|
||||
);
|
||||
|
||||
G_DEFINE_TYPE (GstCairoOverlay, gst_cairo_overlay, GST_TYPE_BASE_TRANSFORM);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (cairooverlay, "cairooverlay",
|
||||
GST_RANK_NONE, GST_TYPE_CAIRO_OVERLAY, GST_DEBUG_CATEGORY_INIT (cairo_debug,
|
||||
"cairo", 0, "Cairo elements"););
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_DRAW_ON_TRANSPARENT_SURFACE,
|
||||
};
|
||||
|
||||
#define DEFAULT_DRAW_ON_TRANSPARENT_SURFACE (FALSE)
|
||||
|
||||
enum
|
||||
{
|
||||
SIGNAL_DRAW,
|
||||
SIGNAL_CAPS_CHANGED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint gst_cairo_overlay_signals[N_SIGNALS];
|
||||
|
||||
static void
|
||||
gst_cairo_overlay_set_property (GObject * object, guint property_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DRAW_ON_TRANSPARENT_SURFACE:
|
||||
overlay->draw_on_transparent_surface = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_cairo_overlay_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (object);
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DRAW_ON_TRANSPARENT_SURFACE:
|
||||
g_value_set_boolean (value, overlay->draw_on_transparent_surface);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_cairo_overlay_query (GstBaseTransform * trans, GstPadDirection direction,
|
||||
GstQuery * query)
|
||||
{
|
||||
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_ALLOCATION:
|
||||
{
|
||||
/* We're always running in passthrough mode, which means that
|
||||
* basetransform just passes through ALLOCATION queries and
|
||||
* never ever calls BaseTransform::decide_allocation().
|
||||
*
|
||||
* We hook into the query handling for that reason
|
||||
*/
|
||||
overlay->attach_compo_to_buffer = FALSE;
|
||||
|
||||
if (!GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
|
||||
(trans, direction, query)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
overlay->attach_compo_to_buffer = gst_query_find_allocation_meta (query,
|
||||
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
default:
|
||||
return
|
||||
GST_BASE_TRANSFORM_CLASS (gst_cairo_overlay_parent_class)->query
|
||||
(trans, direction, query);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_cairo_overlay_set_caps (GstBaseTransform * trans, GstCaps * in_caps,
|
||||
GstCaps * out_caps)
|
||||
{
|
||||
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
|
||||
|
||||
if (!gst_video_info_from_caps (&overlay->info, in_caps))
|
||||
return FALSE;
|
||||
|
||||
g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED], 0,
|
||||
in_caps, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_premultiply_0 (GstVideoFrame * frame)
|
||||
{
|
||||
int i, j;
|
||||
int width = GST_VIDEO_FRAME_WIDTH (frame);
|
||||
int height = GST_VIDEO_FRAME_HEIGHT (frame);
|
||||
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
|
||||
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
|
||||
|
||||
for (j = 0; j < height; ++j) {
|
||||
guint8 *line;
|
||||
|
||||
line = data;
|
||||
line += stride * j;
|
||||
for (i = 0; i < width; ++i) {
|
||||
int a = line[0];
|
||||
line[1] = line[1] * a / 255;
|
||||
line[2] = line[2] * a / 255;
|
||||
line[3] = line[3] * a / 255;
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_premultiply_3 (GstVideoFrame * frame)
|
||||
{
|
||||
int i, j;
|
||||
int width = GST_VIDEO_FRAME_WIDTH (frame);
|
||||
int height = GST_VIDEO_FRAME_HEIGHT (frame);
|
||||
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
|
||||
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
|
||||
|
||||
for (j = 0; j < height; ++j) {
|
||||
guint8 *line;
|
||||
|
||||
line = data;
|
||||
line += stride * j;
|
||||
for (i = 0; i < width; ++i) {
|
||||
int a = line[3];
|
||||
line[0] = line[0] * a / 255;
|
||||
line[1] = line[1] * a / 255;
|
||||
line[2] = line[2] * a / 255;
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_premultiply (GstVideoFrame * frame)
|
||||
{
|
||||
gint alpha_offset;
|
||||
|
||||
alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
|
||||
switch (alpha_offset) {
|
||||
case 0:
|
||||
gst_video_overlay_rectangle_premultiply_0 (frame);
|
||||
break;
|
||||
case 3:
|
||||
gst_video_overlay_rectangle_premultiply_3 (frame);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_unpremultiply_0 (GstVideoFrame * frame)
|
||||
{
|
||||
int i, j;
|
||||
int width = GST_VIDEO_FRAME_WIDTH (frame);
|
||||
int height = GST_VIDEO_FRAME_HEIGHT (frame);
|
||||
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
|
||||
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
|
||||
|
||||
for (j = 0; j < height; ++j) {
|
||||
guint8 *line;
|
||||
|
||||
line = data;
|
||||
line += stride * j;
|
||||
for (i = 0; i < width; ++i) {
|
||||
int a = line[0];
|
||||
if (a) {
|
||||
line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
|
||||
line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
|
||||
line[3] = MIN ((line[3] * 255 + a / 2) / a, 255);
|
||||
}
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_unpremultiply_3 (GstVideoFrame * frame)
|
||||
{
|
||||
int i, j;
|
||||
int width = GST_VIDEO_FRAME_WIDTH (frame);
|
||||
int height = GST_VIDEO_FRAME_HEIGHT (frame);
|
||||
int stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
|
||||
guint8 *data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
|
||||
|
||||
for (j = 0; j < height; ++j) {
|
||||
guint8 *line;
|
||||
|
||||
line = data;
|
||||
line += stride * j;
|
||||
for (i = 0; i < width; ++i) {
|
||||
int a = line[3];
|
||||
if (a) {
|
||||
line[0] = MIN ((line[0] * 255 + a / 2) / a, 255);
|
||||
line[1] = MIN ((line[1] * 255 + a / 2) / a, 255);
|
||||
line[2] = MIN ((line[2] * 255 + a / 2) / a, 255);
|
||||
}
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy from video-overlay-composition.c */
|
||||
static void
|
||||
gst_video_overlay_rectangle_unpremultiply (GstVideoFrame * frame)
|
||||
{
|
||||
gint alpha_offset;
|
||||
|
||||
alpha_offset = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
|
||||
switch (alpha_offset) {
|
||||
case 0:
|
||||
gst_video_overlay_rectangle_unpremultiply_0 (frame);
|
||||
break;
|
||||
case 3:
|
||||
gst_video_overlay_rectangle_unpremultiply_3 (frame);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_cairo_overlay_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
|
||||
{
|
||||
GstCairoOverlay *overlay = GST_CAIRO_OVERLAY (trans);
|
||||
GstVideoFrame frame;
|
||||
cairo_surface_t *surface;
|
||||
cairo_t *cr;
|
||||
cairo_format_t format;
|
||||
gboolean draw_on_transparent_surface = overlay->draw_on_transparent_surface;
|
||||
|
||||
switch (GST_VIDEO_INFO_FORMAT (&overlay->info)) {
|
||||
case GST_VIDEO_FORMAT_ARGB:
|
||||
case GST_VIDEO_FORMAT_BGRA:
|
||||
format = CAIRO_FORMAT_ARGB32;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_xRGB:
|
||||
case GST_VIDEO_FORMAT_BGRx:
|
||||
format = CAIRO_FORMAT_RGB24;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_RGB16:
|
||||
format = CAIRO_FORMAT_RGB16_565;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
GST_WARNING ("No matching cairo format for %s",
|
||||
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&overlay->info)));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we need to map the buffer writable, do so */
|
||||
if (!draw_on_transparent_surface || !overlay->attach_compo_to_buffer) {
|
||||
if (!gst_video_frame_map (&frame, &overlay->info, buf, GST_MAP_READWRITE)) {
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
} else {
|
||||
frame.buffer = NULL;
|
||||
}
|
||||
|
||||
if (draw_on_transparent_surface) {
|
||||
surface =
|
||||
cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
GST_VIDEO_INFO_WIDTH (&overlay->info),
|
||||
GST_VIDEO_INFO_HEIGHT (&overlay->info));
|
||||
} else {
|
||||
if (format == CAIRO_FORMAT_ARGB32)
|
||||
gst_video_overlay_rectangle_premultiply (&frame);
|
||||
|
||||
surface =
|
||||
cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&frame,
|
||||
0), format, GST_VIDEO_FRAME_WIDTH (&frame),
|
||||
GST_VIDEO_FRAME_HEIGHT (&frame), GST_VIDEO_FRAME_PLANE_STRIDE (&frame,
|
||||
0));
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (!surface))
|
||||
return GST_FLOW_ERROR;
|
||||
|
||||
cr = cairo_create (surface);
|
||||
if (G_UNLIKELY (!cr)) {
|
||||
cairo_surface_destroy (surface);
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
g_signal_emit (overlay, gst_cairo_overlay_signals[SIGNAL_DRAW], 0,
|
||||
cr, GST_BUFFER_PTS (buf), GST_BUFFER_DURATION (buf), NULL);
|
||||
|
||||
cairo_destroy (cr);
|
||||
|
||||
if (draw_on_transparent_surface) {
|
||||
guint size;
|
||||
GstBuffer *surface_buffer;
|
||||
GstVideoOverlayRectangle *rect;
|
||||
GstVideoOverlayComposition *composition;
|
||||
gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
|
||||
gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
|
||||
|
||||
size =
|
||||
cairo_image_surface_get_height (surface) *
|
||||
cairo_image_surface_get_stride (surface);
|
||||
stride[0] = cairo_image_surface_get_stride (surface);
|
||||
|
||||
/* Create a GstVideoOverlayComposition for blending, this handles
|
||||
* pre-multiplied alpha correctly */
|
||||
surface_buffer =
|
||||
gst_buffer_new_wrapped_full (0, cairo_image_surface_get_data (surface),
|
||||
size, 0, size, surface, (GDestroyNotify) cairo_surface_destroy);
|
||||
gst_buffer_add_video_meta_full (surface_buffer, GST_VIDEO_FRAME_FLAG_NONE,
|
||||
(G_BYTE_ORDER ==
|
||||
G_LITTLE_ENDIAN ? GST_VIDEO_FORMAT_BGRA : GST_VIDEO_FORMAT_ARGB),
|
||||
GST_VIDEO_INFO_WIDTH (&overlay->info),
|
||||
GST_VIDEO_INFO_HEIGHT (&overlay->info), 1, offset, stride);
|
||||
rect =
|
||||
gst_video_overlay_rectangle_new_raw (surface_buffer, 0, 0,
|
||||
GST_VIDEO_INFO_WIDTH (&overlay->info),
|
||||
GST_VIDEO_INFO_HEIGHT (&overlay->info),
|
||||
GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
|
||||
gst_buffer_unref (surface_buffer);
|
||||
|
||||
if (overlay->attach_compo_to_buffer) {
|
||||
GstVideoOverlayCompositionMeta *composition_meta;
|
||||
|
||||
composition_meta = gst_buffer_get_video_overlay_composition_meta (buf);
|
||||
if (composition_meta) {
|
||||
GstVideoOverlayComposition *merged_composition =
|
||||
gst_video_overlay_composition_copy (composition_meta->overlay);
|
||||
gst_video_overlay_composition_add_rectangle (merged_composition, rect);
|
||||
gst_video_overlay_composition_unref (composition_meta->overlay);
|
||||
composition_meta->overlay = merged_composition;
|
||||
gst_video_overlay_rectangle_unref (rect);
|
||||
} else {
|
||||
composition = gst_video_overlay_composition_new (rect);
|
||||
gst_video_overlay_rectangle_unref (rect);
|
||||
gst_buffer_add_video_overlay_composition_meta (buf, composition);
|
||||
gst_video_overlay_composition_unref (composition);
|
||||
}
|
||||
} else {
|
||||
composition = gst_video_overlay_composition_new (rect);
|
||||
gst_video_overlay_rectangle_unref (rect);
|
||||
gst_video_overlay_composition_blend (composition, &frame);
|
||||
gst_video_overlay_composition_unref (composition);
|
||||
}
|
||||
} else {
|
||||
cairo_surface_destroy (surface);
|
||||
if (format == CAIRO_FORMAT_ARGB32)
|
||||
gst_video_overlay_rectangle_unpremultiply (&frame);
|
||||
}
|
||||
|
||||
if (frame.buffer) {
|
||||
gst_video_frame_unmap (&frame);
|
||||
}
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_cairo_overlay_class_init (GstCairoOverlayClass * klass)
|
||||
{
|
||||
GstBaseTransformClass *btrans_class;
|
||||
GstElementClass *element_class;
|
||||
GObjectClass *gobject_class;
|
||||
|
||||
btrans_class = (GstBaseTransformClass *) klass;
|
||||
element_class = (GstElementClass *) klass;
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
|
||||
btrans_class->set_caps = gst_cairo_overlay_set_caps;
|
||||
btrans_class->transform_ip = gst_cairo_overlay_transform_ip;
|
||||
btrans_class->query = gst_cairo_overlay_query;
|
||||
|
||||
gobject_class->set_property = gst_cairo_overlay_set_property;
|
||||
gobject_class->get_property = gst_cairo_overlay_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_DRAW_ON_TRANSPARENT_SURFACE,
|
||||
g_param_spec_boolean ("draw-on-transparent-surface",
|
||||
"Draw on transparent surface",
|
||||
"Let the draw signal work on a transparent surface "
|
||||
"and blend the results with the video at a later time",
|
||||
DEFAULT_DRAW_ON_TRANSPARENT_SURFACE,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* GstCairoOverlay::draw:
|
||||
* @overlay: Overlay element emitting the signal.
|
||||
* @cr: Cairo context to draw to.
|
||||
* @timestamp: Timestamp (see #GstClockTime) of the current buffer.
|
||||
* @duration: Duration (see #GstClockTime) of the current buffer.
|
||||
*
|
||||
* This signal is emitted when the overlay should be drawn.
|
||||
*/
|
||||
gst_cairo_overlay_signals[SIGNAL_DRAW] =
|
||||
g_signal_new ("draw",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
0, 0, NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 3, CAIRO_GOBJECT_TYPE_CONTEXT, G_TYPE_UINT64, G_TYPE_UINT64);
|
||||
|
||||
/**
|
||||
* GstCairoOverlay::caps-changed:
|
||||
* @overlay: Overlay element emitting the signal.
|
||||
* @caps: The #GstCaps of the element.
|
||||
*
|
||||
* This signal is emitted when the caps of the element has changed.
|
||||
*/
|
||||
gst_cairo_overlay_signals[SIGNAL_CAPS_CHANGED] =
|
||||
g_signal_new ("caps-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CAPS);
|
||||
|
||||
gst_element_class_set_static_metadata (element_class, "Cairo overlay",
|
||||
"Filter/Editor/Video",
|
||||
"Render overlay on a video stream using Cairo",
|
||||
"Jon Nordby <jononor@gmail.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_cairo_overlay_sink_template);
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_cairo_overlay_src_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_cairo_overlay_init (GstCairoOverlay * overlay)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
50
ext/cairo/gstcairooverlay.h
Normal file
50
ext/cairo/gstcairooverlay.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <2011> Jon Nordby <jononor@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_CAIRO_OVERLAY_H__
|
||||
#define __GST_CAIRO_OVERLAY_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <cairo.h>
|
||||
#include <cairo-gobject.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_CAIRO_OVERLAY (gst_cairo_overlay_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstCairoOverlay, gst_cairo_overlay,
|
||||
GST, CAIRO_OVERLAY, GstBaseTransform)
|
||||
|
||||
struct _GstCairoOverlay {
|
||||
GstBaseTransform parent;
|
||||
|
||||
/* properties */
|
||||
gboolean draw_on_transparent_surface;
|
||||
|
||||
/* state */
|
||||
GstVideoInfo info;
|
||||
gboolean attach_compo_to_buffer;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (cairooverlay);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_CAIRO_OVERLAY_H__ */
|
15
ext/cairo/meson.build
Normal file
15
ext/cairo/meson.build
Normal file
|
@ -0,0 +1,15 @@
|
|||
cairo_dep = dependency('cairo-gobject', version : '>=1.10.0', required : get_option('cairo'))
|
||||
|
||||
if cairo_dep.found()
|
||||
gstcairo = library('gstcairo',
|
||||
'gstcairo.c', 'gstcairooverlay.c',
|
||||
c_args : gst_plugins_good_args,
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [gstbase_dep, gstvideo_dep, cairo_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstcairo, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstcairo]
|
||||
endif
|
13
ext/dv/NOTES
Normal file
13
ext/dv/NOTES
Normal file
|
@ -0,0 +1,13 @@
|
|||
Packets come from 1394 480 bytes at a time. This is not a video segment
|
||||
length. This causes problems, since a packet boundary crossing a video
|
||||
segment can split a video segment if we lose an iso packet. We can
|
||||
recover from this, sorta, with significant changes to the parser. We have
|
||||
to deal with the idea that a) some macroblocks just don't exist (we have
|
||||
zero's for them) and b) when any of the 5 macroblocks doesn't exist, we
|
||||
can't do pass 3.
|
||||
|
||||
Since things are bitstream-based, we can deal with this, but we have to
|
||||
add a layer of code that tries to save time (maybe) by not decoding things
|
||||
that don't exist. Not sure how this is gonna work with the parse code
|
||||
being based on video segments, and not easily splittable into
|
||||
macroblock-level parsing (or is it?).
|
42
ext/dv/gstdv.c
Normal file
42
ext/dv/gstdv.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2005> Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstdvelements.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (dvdemux, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (dvdec, plugin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
dv,
|
||||
"DV demuxer and decoder based on libdv (libdv.sf.net)",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
|
695
ext/dv/gstdvdec.c
Normal file
695
ext/dv/gstdvdec.c
Normal file
|
@ -0,0 +1,695 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2005> Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-dvdec
|
||||
* @title: dvdec
|
||||
*
|
||||
* dvdec decodes DV video into raw video. The element expects a full DV frame
|
||||
* as input, which is 120000 bytes for NTSC and 144000 for PAL video.
|
||||
*
|
||||
* This element can perform simple frame dropping with the #GstDVDec:drop-factor
|
||||
* property. Setting this property to a value N > 1 will only decode every
|
||||
* Nth frame.
|
||||
*
|
||||
* ## Example launch line
|
||||
* |[
|
||||
* gst-launch-1.0 filesrc location=test.dv ! dvdemux name=demux ! dvdec ! xvimagesink
|
||||
* ]| This pipeline decodes and renders the raw DV stream to a videosink.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideometa.h>
|
||||
#include <gst/video/gstvideopool.h>
|
||||
|
||||
#include "gstdvelements.h"
|
||||
#include "gstdvdec.h"
|
||||
|
||||
/* sizes of one input buffer */
|
||||
#define NTSC_HEIGHT 480
|
||||
#define NTSC_BUFFER 120000
|
||||
#define NTSC_FRAMERATE_NUMERATOR 30000
|
||||
#define NTSC_FRAMERATE_DENOMINATOR 1001
|
||||
|
||||
#define PAL_HEIGHT 576
|
||||
#define PAL_BUFFER 144000
|
||||
#define PAL_FRAMERATE_NUMERATOR 25
|
||||
#define PAL_FRAMERATE_DENOMINATOR 1
|
||||
|
||||
#define PAL_NORMAL_PAR_X 16
|
||||
#define PAL_NORMAL_PAR_Y 15
|
||||
#define PAL_WIDE_PAR_X 64
|
||||
#define PAL_WIDE_PAR_Y 45
|
||||
|
||||
#define NTSC_NORMAL_PAR_X 8
|
||||
#define NTSC_NORMAL_PAR_Y 9
|
||||
#define NTSC_WIDE_PAR_X 32
|
||||
#define NTSC_WIDE_PAR_Y 27
|
||||
|
||||
#define DV_DEFAULT_QUALITY DV_QUALITY_BEST
|
||||
#define DV_DEFAULT_DECODE_NTH 1
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (dvdec_debug);
|
||||
#define GST_CAT_DEFAULT dvdec_debug
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_CLAMP_LUMA,
|
||||
PROP_CLAMP_CHROMA,
|
||||
PROP_QUALITY,
|
||||
PROP_DECODE_NTH
|
||||
};
|
||||
|
||||
const gint qualities[] = {
|
||||
DV_QUALITY_DC,
|
||||
DV_QUALITY_AC_1,
|
||||
DV_QUALITY_AC_2,
|
||||
DV_QUALITY_DC | DV_QUALITY_COLOR,
|
||||
DV_QUALITY_AC_1 | DV_QUALITY_COLOR,
|
||||
DV_QUALITY_AC_2 | DV_QUALITY_COLOR
|
||||
};
|
||||
|
||||
static GstStaticPadTemplate sink_temp = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-dv, systemstream = (boolean) false")
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate src_temp = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-raw, "
|
||||
"format = (string) { YUY2, BGRx, RGB }, "
|
||||
"framerate = (fraction) [ 1/1, 60/1 ], "
|
||||
"width = (int) 720, " "height = (int) { 576, 480 }")
|
||||
);
|
||||
|
||||
#define GST_TYPE_DVDEC_QUALITY (gst_dvdec_quality_get_type())
|
||||
static GType
|
||||
gst_dvdec_quality_get_type (void)
|
||||
{
|
||||
static GType qtype = 0;
|
||||
|
||||
if (qtype == 0) {
|
||||
static const GEnumValue values[] = {
|
||||
{0, "Monochrome, DC (Fastest)", "fastest"},
|
||||
{1, "Monochrome, first AC coefficient", "monochrome-ac"},
|
||||
{2, "Monochrome, highest quality", "monochrome-best"},
|
||||
{3, "Colour, DC, fastest", "colour-fastest"},
|
||||
{4, "Colour, using only the first AC coefficient", "colour-ac"},
|
||||
{5, "Highest quality colour decoding", "best"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
|
||||
qtype = g_enum_register_static ("GstDVDecQualityEnum", values);
|
||||
}
|
||||
return qtype;
|
||||
}
|
||||
|
||||
#define gst_dvdec_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstDVDec, gst_dvdec, GST_TYPE_ELEMENT);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (dvdec, "dvdec", GST_RANK_MARGINAL,
|
||||
GST_TYPE_DVDEC, dv_element_init (plugin));
|
||||
|
||||
static GstFlowReturn gst_dvdec_chain (GstPad * pad, GstObject * parent,
|
||||
GstBuffer * buffer);
|
||||
static gboolean gst_dvdec_sink_event (GstPad * pad, GstObject * parent,
|
||||
GstEvent * event);
|
||||
|
||||
static GstStateChangeReturn gst_dvdec_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static void gst_dvdec_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_dvdec_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static void
|
||||
gst_dvdec_class_init (GstDVDecClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_dvdec_set_property;
|
||||
gobject_class->get_property = gst_dvdec_get_property;
|
||||
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CLAMP_LUMA,
|
||||
g_param_spec_boolean ("clamp-luma", "Clamp luma", "Clamp luma",
|
||||
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CLAMP_CHROMA,
|
||||
g_param_spec_boolean ("clamp-chroma", "Clamp chroma", "Clamp chroma",
|
||||
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_QUALITY,
|
||||
g_param_spec_enum ("quality", "Quality", "Decoding quality",
|
||||
GST_TYPE_DVDEC_QUALITY, DV_DEFAULT_QUALITY,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DECODE_NTH,
|
||||
g_param_spec_int ("drop-factor", "Drop Factor", "Only decode Nth frame",
|
||||
1, G_MAXINT, DV_DEFAULT_DECODE_NTH,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_dvdec_change_state);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &sink_temp);
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &src_temp);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "DV video decoder",
|
||||
"Codec/Decoder/Video",
|
||||
"Uses libdv to decode DV video (smpte314) (libdv.sourceforge.net)",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>," "Wim Taymans <wim@fluendo.com>");
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (dvdec_debug, "dvdec", 0, "DV decoding element");
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_DVDEC_QUALITY, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dvdec_init (GstDVDec * dvdec)
|
||||
{
|
||||
dvdec->sinkpad = gst_pad_new_from_static_template (&sink_temp, "sink");
|
||||
gst_pad_set_chain_function (dvdec->sinkpad, gst_dvdec_chain);
|
||||
gst_pad_set_event_function (dvdec->sinkpad, gst_dvdec_sink_event);
|
||||
gst_element_add_pad (GST_ELEMENT (dvdec), dvdec->sinkpad);
|
||||
|
||||
dvdec->srcpad = gst_pad_new_from_static_template (&src_temp, "src");
|
||||
gst_pad_use_fixed_caps (dvdec->srcpad);
|
||||
gst_element_add_pad (GST_ELEMENT (dvdec), dvdec->srcpad);
|
||||
|
||||
dvdec->framerate_numerator = 0;
|
||||
dvdec->framerate_denominator = 0;
|
||||
dvdec->wide = FALSE;
|
||||
dvdec->drop_factor = 1;
|
||||
|
||||
dvdec->clamp_luma = FALSE;
|
||||
dvdec->clamp_chroma = FALSE;
|
||||
dvdec->quality = DV_DEFAULT_QUALITY;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_dvdec_sink_setcaps (GstDVDec * dvdec, GstCaps * caps)
|
||||
{
|
||||
GstStructure *s;
|
||||
const GValue *par = NULL, *rate = NULL;
|
||||
|
||||
/* first parse the caps */
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
|
||||
/* we allow framerate and PAR to be overwritten. framerate is mandatory. */
|
||||
if (!(rate = gst_structure_get_value (s, "framerate")))
|
||||
goto no_framerate;
|
||||
par = gst_structure_get_value (s, "pixel-aspect-ratio");
|
||||
|
||||
if (par) {
|
||||
dvdec->par_x = gst_value_get_fraction_numerator (par);
|
||||
dvdec->par_y = gst_value_get_fraction_denominator (par);
|
||||
dvdec->need_par = FALSE;
|
||||
} else {
|
||||
dvdec->par_x = 0;
|
||||
dvdec->par_y = 0;
|
||||
dvdec->need_par = TRUE;
|
||||
}
|
||||
dvdec->framerate_numerator = gst_value_get_fraction_numerator (rate);
|
||||
dvdec->framerate_denominator = gst_value_get_fraction_denominator (rate);
|
||||
dvdec->sink_negotiated = TRUE;
|
||||
dvdec->src_negotiated = FALSE;
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_framerate:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dvdec, "no framerate specified in caps");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dvdec_negotiate_pool (GstDVDec * dec, GstCaps * caps, GstVideoInfo * info)
|
||||
{
|
||||
GstQuery *query;
|
||||
GstBufferPool *pool;
|
||||
guint size, min, max;
|
||||
GstStructure *config;
|
||||
|
||||
/* find a pool for the negotiated caps now */
|
||||
query = gst_query_new_allocation (caps, TRUE);
|
||||
|
||||
if (!gst_pad_peer_query (dec->srcpad, query)) {
|
||||
GST_DEBUG_OBJECT (dec, "didn't get downstream ALLOCATION hints");
|
||||
}
|
||||
|
||||
if (gst_query_get_n_allocation_pools (query) > 0) {
|
||||
/* we got configuration from our peer, parse them */
|
||||
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
|
||||
size = MAX (size, info->size);
|
||||
} else {
|
||||
pool = NULL;
|
||||
size = info->size;
|
||||
min = max = 0;
|
||||
}
|
||||
|
||||
if (pool == NULL) {
|
||||
/* we did not get a pool, make one ourselves then */
|
||||
pool = gst_video_buffer_pool_new ();
|
||||
}
|
||||
|
||||
if (dec->pool) {
|
||||
gst_buffer_pool_set_active (dec->pool, FALSE);
|
||||
gst_object_unref (dec->pool);
|
||||
}
|
||||
dec->pool = pool;
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, caps, size, min, max);
|
||||
|
||||
if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
|
||||
/* just set the option, if the pool can support it we will transparently use
|
||||
* it through the video info API. We could also see if the pool support this
|
||||
* option and only activate it then. */
|
||||
gst_buffer_pool_config_add_option (config,
|
||||
GST_BUFFER_POOL_OPTION_VIDEO_META);
|
||||
}
|
||||
gst_buffer_pool_set_config (pool, config);
|
||||
|
||||
/* and activate */
|
||||
gst_buffer_pool_set_active (pool, TRUE);
|
||||
|
||||
gst_query_unref (query);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_dvdec_src_negotiate (GstDVDec * dvdec)
|
||||
{
|
||||
GstCaps *othercaps;
|
||||
gboolean ret;
|
||||
|
||||
/* no PAR was specified in input, derive from encoded data */
|
||||
if (dvdec->need_par) {
|
||||
if (dvdec->PAL) {
|
||||
if (dvdec->wide) {
|
||||
dvdec->par_x = PAL_WIDE_PAR_X;
|
||||
dvdec->par_y = PAL_WIDE_PAR_Y;
|
||||
} else {
|
||||
dvdec->par_x = PAL_NORMAL_PAR_X;
|
||||
dvdec->par_y = PAL_NORMAL_PAR_Y;
|
||||
}
|
||||
} else {
|
||||
if (dvdec->wide) {
|
||||
dvdec->par_x = NTSC_WIDE_PAR_X;
|
||||
dvdec->par_y = NTSC_WIDE_PAR_Y;
|
||||
} else {
|
||||
dvdec->par_x = NTSC_NORMAL_PAR_X;
|
||||
dvdec->par_y = NTSC_NORMAL_PAR_Y;
|
||||
}
|
||||
}
|
||||
GST_DEBUG_OBJECT (dvdec, "Inferred PAR %d/%d from video format",
|
||||
dvdec->par_x, dvdec->par_y);
|
||||
}
|
||||
|
||||
/* ignoring rgb, bgr0 for now */
|
||||
dvdec->bpp = 2;
|
||||
|
||||
gst_video_info_set_format (&dvdec->vinfo, GST_VIDEO_FORMAT_YUY2,
|
||||
720, dvdec->height);
|
||||
dvdec->vinfo.fps_n = dvdec->framerate_numerator;
|
||||
dvdec->vinfo.fps_d = dvdec->framerate_denominator;
|
||||
dvdec->vinfo.par_n = dvdec->par_x;
|
||||
dvdec->vinfo.par_d = dvdec->par_y;
|
||||
if (dvdec->interlaced) {
|
||||
dvdec->vinfo.interlace_mode = GST_VIDEO_INTERLACE_MODE_INTERLEAVED;
|
||||
} else {
|
||||
dvdec->vinfo.interlace_mode = GST_VIDEO_INTERLACE_MODE_PROGRESSIVE;
|
||||
}
|
||||
|
||||
othercaps = gst_video_info_to_caps (&dvdec->vinfo);
|
||||
ret = gst_pad_set_caps (dvdec->srcpad, othercaps);
|
||||
|
||||
gst_dvdec_negotiate_pool (dvdec, othercaps, &dvdec->vinfo);
|
||||
gst_caps_unref (othercaps);
|
||||
|
||||
dvdec->src_negotiated = TRUE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_dvdec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
||||
{
|
||||
GstDVDec *dvdec;
|
||||
gboolean res = TRUE;
|
||||
|
||||
dvdec = GST_DVDEC (parent);
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_FLUSH_STOP:
|
||||
gst_segment_init (&dvdec->segment, GST_FORMAT_UNDEFINED);
|
||||
dvdec->need_segment = FALSE;
|
||||
break;
|
||||
case GST_EVENT_SEGMENT:{
|
||||
const GstSegment *segment;
|
||||
|
||||
gst_event_parse_segment (event, &segment);
|
||||
|
||||
GST_DEBUG_OBJECT (dvdec, "Got SEGMENT %" GST_SEGMENT_FORMAT, &segment);
|
||||
|
||||
gst_segment_copy_into (segment, &dvdec->segment);
|
||||
if (!gst_pad_has_current_caps (dvdec->srcpad)) {
|
||||
dvdec->need_segment = TRUE;
|
||||
gst_event_unref (event);
|
||||
event = NULL;
|
||||
res = TRUE;
|
||||
} else {
|
||||
dvdec->need_segment = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_EVENT_CAPS:
|
||||
{
|
||||
GstCaps *caps;
|
||||
|
||||
gst_event_parse_caps (event, &caps);
|
||||
res = gst_dvdec_sink_setcaps (dvdec, caps);
|
||||
gst_event_unref (event);
|
||||
event = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (event)
|
||||
res = gst_pad_push_event (dvdec->srcpad, event);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dvdec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
||||
{
|
||||
GstDVDec *dvdec;
|
||||
guint8 *inframe;
|
||||
guint8 *outframe_ptrs[3];
|
||||
gint outframe_pitches[3];
|
||||
GstMapInfo map;
|
||||
GstVideoFrame frame;
|
||||
GstBuffer *outbuf;
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
guint length;
|
||||
guint64 cstart = GST_CLOCK_TIME_NONE, cstop = GST_CLOCK_TIME_NONE;
|
||||
gboolean PAL, wide;
|
||||
|
||||
dvdec = GST_DVDEC (parent);
|
||||
|
||||
gst_buffer_map (buf, &map, GST_MAP_READ);
|
||||
inframe = map.data;
|
||||
|
||||
/* buffer should be at least the size of one NTSC frame, this should
|
||||
* be enough to decode the header. */
|
||||
if (G_UNLIKELY (map.size < NTSC_BUFFER))
|
||||
goto wrong_size;
|
||||
|
||||
/* preliminary dropping. unref and return if outside of configured segment */
|
||||
if ((dvdec->segment.format == GST_FORMAT_TIME) &&
|
||||
(!(gst_segment_clip (&dvdec->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_TIMESTAMP (buf),
|
||||
GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf),
|
||||
&cstart, &cstop))))
|
||||
goto dropping;
|
||||
|
||||
if (G_UNLIKELY (dv_parse_header (dvdec->decoder, inframe) < 0))
|
||||
goto parse_header_error;
|
||||
|
||||
/* get size */
|
||||
PAL = dv_system_50_fields (dvdec->decoder);
|
||||
wide = dv_format_wide (dvdec->decoder);
|
||||
|
||||
/* check the buffer is of right size after we know if we are
|
||||
* dealing with PAL or NTSC */
|
||||
length = (PAL ? PAL_BUFFER : NTSC_BUFFER);
|
||||
if (G_UNLIKELY (map.size < length))
|
||||
goto wrong_size;
|
||||
|
||||
dv_parse_packs (dvdec->decoder, inframe);
|
||||
|
||||
if (dvdec->video_offset % dvdec->drop_factor != 0)
|
||||
goto skip;
|
||||
|
||||
/* renegotiate on change */
|
||||
if (PAL != dvdec->PAL || wide != dvdec->wide) {
|
||||
dvdec->src_negotiated = FALSE;
|
||||
dvdec->PAL = PAL;
|
||||
dvdec->wide = wide;
|
||||
}
|
||||
|
||||
dvdec->height = (dvdec->PAL ? PAL_HEIGHT : NTSC_HEIGHT);
|
||||
|
||||
dvdec->interlaced = !dv_is_progressive (dvdec->decoder);
|
||||
|
||||
/* negotiate if not done yet */
|
||||
if (!dvdec->src_negotiated) {
|
||||
if (!gst_dvdec_src_negotiate (dvdec))
|
||||
goto not_negotiated;
|
||||
}
|
||||
|
||||
if (gst_pad_check_reconfigure (dvdec->srcpad)) {
|
||||
GstCaps *caps;
|
||||
|
||||
caps = gst_pad_get_current_caps (dvdec->srcpad);
|
||||
if (!caps)
|
||||
goto flushing;
|
||||
|
||||
gst_dvdec_negotiate_pool (dvdec, caps, &dvdec->vinfo);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
if (dvdec->need_segment) {
|
||||
gst_pad_push_event (dvdec->srcpad, gst_event_new_segment (&dvdec->segment));
|
||||
dvdec->need_segment = FALSE;
|
||||
}
|
||||
|
||||
ret = gst_buffer_pool_acquire_buffer (dvdec->pool, &outbuf, NULL);
|
||||
if (G_UNLIKELY (ret != GST_FLOW_OK))
|
||||
goto no_buffer;
|
||||
|
||||
gst_video_frame_map (&frame, &dvdec->vinfo, outbuf, GST_MAP_WRITE);
|
||||
|
||||
outframe_ptrs[0] = GST_VIDEO_FRAME_COMP_DATA (&frame, 0);
|
||||
outframe_pitches[0] = GST_VIDEO_FRAME_COMP_STRIDE (&frame, 0);
|
||||
|
||||
/* the rest only matters for YUY2 */
|
||||
if (dvdec->bpp < 3) {
|
||||
outframe_ptrs[1] = GST_VIDEO_FRAME_COMP_DATA (&frame, 1);
|
||||
outframe_ptrs[2] = GST_VIDEO_FRAME_COMP_DATA (&frame, 2);
|
||||
|
||||
outframe_pitches[1] = GST_VIDEO_FRAME_COMP_STRIDE (&frame, 1);
|
||||
outframe_pitches[2] = GST_VIDEO_FRAME_COMP_STRIDE (&frame, 2);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (dvdec, "decoding and pushing buffer");
|
||||
dv_decode_full_frame (dvdec->decoder, inframe,
|
||||
e_dv_color_yuv, outframe_ptrs, outframe_pitches);
|
||||
|
||||
gst_video_frame_unmap (&frame);
|
||||
|
||||
GST_BUFFER_FLAG_UNSET (outbuf, GST_VIDEO_BUFFER_FLAG_TFF);
|
||||
|
||||
GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buf);
|
||||
GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_END (buf);
|
||||
|
||||
/* FIXME : Compute values when using non-TIME segments,
|
||||
* but for the moment make sure we at least don't set bogus values
|
||||
*/
|
||||
if (GST_CLOCK_TIME_IS_VALID (cstart)) {
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = cstart;
|
||||
if (GST_CLOCK_TIME_IS_VALID (cstop))
|
||||
GST_BUFFER_DURATION (outbuf) = cstop - cstart;
|
||||
}
|
||||
|
||||
ret = gst_pad_push (dvdec->srcpad, outbuf);
|
||||
|
||||
skip:
|
||||
dvdec->video_offset++;
|
||||
|
||||
done:
|
||||
gst_buffer_unmap (buf, &map);
|
||||
gst_buffer_unref (buf);
|
||||
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
wrong_size:
|
||||
{
|
||||
GST_ELEMENT_ERROR (dvdec, STREAM, DECODE,
|
||||
(NULL), ("Input buffer too small"));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
parse_header_error:
|
||||
{
|
||||
GST_ELEMENT_ERROR (dvdec, STREAM, DECODE,
|
||||
(NULL), ("Error parsing DV header"));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
not_negotiated:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dvdec, "could not negotiate output");
|
||||
if (GST_PAD_IS_FLUSHING (dvdec->srcpad))
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
else
|
||||
ret = GST_FLOW_NOT_NEGOTIATED;
|
||||
goto done;
|
||||
}
|
||||
flushing:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dvdec, "have no current caps");
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
goto done;
|
||||
}
|
||||
no_buffer:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dvdec, "could not allocate buffer");
|
||||
goto done;
|
||||
}
|
||||
|
||||
dropping:
|
||||
{
|
||||
GST_DEBUG_OBJECT (dvdec,
|
||||
"dropping buffer since it's out of the configured segment");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_dvdec_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstDVDec *dvdec = GST_DVDEC (element);
|
||||
GstStateChangeReturn ret;
|
||||
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
dvdec->decoder =
|
||||
dv_decoder_new (0, dvdec->clamp_luma, dvdec->clamp_chroma);
|
||||
dvdec->decoder->quality = qualities[dvdec->quality];
|
||||
dv_set_error_log (dvdec->decoder, NULL);
|
||||
gst_video_info_init (&dvdec->vinfo);
|
||||
gst_segment_init (&dvdec->segment, GST_FORMAT_UNDEFINED);
|
||||
dvdec->src_negotiated = FALSE;
|
||||
dvdec->sink_negotiated = FALSE;
|
||||
dvdec->need_segment = FALSE;
|
||||
/*
|
||||
* Enable this function call when libdv2 0.100 or higher is more
|
||||
* common
|
||||
*/
|
||||
/* dv_set_quality (dvdec->decoder, qualities [dvdec->quality]); */
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
dv_decoder_free (dvdec->decoder);
|
||||
dvdec->decoder = NULL;
|
||||
if (dvdec->pool) {
|
||||
gst_buffer_pool_set_active (dvdec->pool, FALSE);
|
||||
gst_object_unref (dvdec->pool);
|
||||
dvdec->pool = NULL;
|
||||
}
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dvdec_set_property (GObject * object, guint prop_id, const GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstDVDec *dvdec = GST_DVDEC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CLAMP_LUMA:
|
||||
dvdec->clamp_luma = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_CLAMP_CHROMA:
|
||||
dvdec->clamp_chroma = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_QUALITY:
|
||||
dvdec->quality = g_value_get_enum (value);
|
||||
if ((dvdec->quality < 0) || (dvdec->quality > 5))
|
||||
dvdec->quality = 0;
|
||||
break;
|
||||
case PROP_DECODE_NTH:
|
||||
dvdec->drop_factor = g_value_get_int (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dvdec_get_property (GObject * object, guint prop_id, GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstDVDec *dvdec = GST_DVDEC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CLAMP_LUMA:
|
||||
g_value_set_boolean (value, dvdec->clamp_luma);
|
||||
break;
|
||||
case PROP_CLAMP_CHROMA:
|
||||
g_value_set_boolean (value, dvdec->clamp_chroma);
|
||||
break;
|
||||
case PROP_QUALITY:
|
||||
g_value_set_enum (value, dvdec->quality);
|
||||
break;
|
||||
case PROP_DECODE_NTH:
|
||||
g_value_set_int (value, dvdec->drop_factor);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
76
ext/dv/gstdvdec.h
Normal file
76
ext/dv/gstdvdec.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_DVDEC_H__
|
||||
#define __GST_DVDEC_H__
|
||||
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include <libdv/dv.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_DVDEC (gst_dvdec_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstDVDec, gst_dvdec, GST, DVDEC, GstElement)
|
||||
|
||||
struct _GstDVDec {
|
||||
GstElement element;
|
||||
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
|
||||
dv_decoder_t *decoder;
|
||||
gboolean clamp_luma;
|
||||
gboolean clamp_chroma;
|
||||
gint quality;
|
||||
|
||||
gboolean PAL;
|
||||
gboolean interlaced;
|
||||
gboolean wide;
|
||||
|
||||
/* input caps */
|
||||
gboolean sink_negotiated;
|
||||
GstVideoInfo vinfo;
|
||||
gint framerate_numerator;
|
||||
gint framerate_denominator;
|
||||
gint height;
|
||||
gint par_x;
|
||||
gint par_y;
|
||||
gboolean need_par;
|
||||
|
||||
/* negotiated output */
|
||||
gint bpp;
|
||||
gboolean src_negotiated;
|
||||
|
||||
gint video_offset;
|
||||
gint drop_factor;
|
||||
|
||||
GstBufferPool *pool;
|
||||
GstSegment segment;
|
||||
gboolean need_segment;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
#endif /* __GST_DVDEC_H__ */
|
2050
ext/dv/gstdvdemux.c
Normal file
2050
ext/dv/gstdvdemux.c
Normal file
File diff suppressed because it is too large
Load diff
83
ext/dv/gstdvdemux.h
Normal file
83
ext/dv/gstdvdemux.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_DVDEMUX_H__
|
||||
#define __GST_DVDEMUX_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <libdv/dv.h>
|
||||
#include <gst/base/gstadapter.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_DVDEMUX (gst_dvdemux_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstDVDemux, gst_dvdemux, GST, DVDEMUX, GstElement)
|
||||
|
||||
typedef gboolean (*GstDVDemuxSeekHandler) (GstDVDemux *demux, GstPad * pad, GstEvent * event);
|
||||
|
||||
struct _GstDVDemux {
|
||||
GstElement element;
|
||||
|
||||
GstPad *sinkpad;
|
||||
GstPad *videosrcpad;
|
||||
GstPad *audiosrcpad;
|
||||
|
||||
gboolean have_group_id;
|
||||
guint group_id;
|
||||
|
||||
dv_decoder_t *decoder;
|
||||
|
||||
GstAdapter *adapter;
|
||||
gint frame_len;
|
||||
|
||||
/* video params */
|
||||
gint framerate_numerator;
|
||||
gint framerate_denominator;
|
||||
gint height;
|
||||
gboolean wide;
|
||||
/* audio params */
|
||||
gint frequency;
|
||||
gint channels;
|
||||
|
||||
gboolean discont;
|
||||
gint64 frame_offset;
|
||||
gint64 audio_offset;
|
||||
gint64 video_offset;
|
||||
|
||||
GstDVDemuxSeekHandler seek_handler;
|
||||
GstSegment byte_segment;
|
||||
gboolean upstream_time_segment;
|
||||
GstSegment time_segment;
|
||||
gboolean need_segment;
|
||||
guint32 segment_seqnum;
|
||||
gboolean new_media;
|
||||
int frames_since_new_media;
|
||||
|
||||
gint found_header; /* ATOMIC */
|
||||
GstEvent *seek_event;
|
||||
GstEvent *pending_segment;
|
||||
GstEvent *tag_event;
|
||||
|
||||
gint16 *audio_buffers[4];
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_DVDEMUX_H__ */
|
36
ext/dv/gstdvelement.c
Normal file
36
ext/dv/gstdvelement.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2005> Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <libdv/dv.h>
|
||||
#include "gstdvelements.h"
|
||||
|
||||
void
|
||||
dv_element_init (GstPlugin * plugin)
|
||||
{
|
||||
static gsize res = FALSE;
|
||||
if (g_once_init_enter (&res)) {
|
||||
dv_init (0, 0);
|
||||
g_once_init_leave (&res, TRUE);
|
||||
}
|
||||
}
|
42
ext/dv/gstdvelements.h
Normal file
42
ext/dv/gstdvelements.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2020 Huawei Technologies Co., Ltd.
|
||||
* @Author: Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_DV_ELEMENTS_H__
|
||||
#define __GST_DV_ELEMENTS_H__
|
||||
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void dv_element_init (GstPlugin * plugin);
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (dvdemux);
|
||||
GST_ELEMENT_REGISTER_DECLARE (dvdec);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
#endif /* __GST_DV_ELEMENTS_H__ */
|
240
ext/dv/gstsmptetimecode.c
Normal file
240
ext/dv/gstsmptetimecode.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2009 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Utility functions for handing SMPTE Time Codes, as described in
|
||||
* SMPTE Standard 12M-1999.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstsmptetimecode.h"
|
||||
|
||||
#define NTSC_FRAMES_PER_10_MINS (10*60*30 - 10*2 + 2)
|
||||
#define NTSC_FRAMES_PER_HOUR (6*NTSC_FRAMES_PER_10_MINS)
|
||||
|
||||
/**
|
||||
* gst_smpte_time_code_from_frame_number:
|
||||
* @system: SMPTE Time Code system
|
||||
* @time_code: pointer to time code structure
|
||||
* @frame_number: integer frame number
|
||||
*
|
||||
* Converts a frame number to a time code.
|
||||
*
|
||||
* Returns: TRUE if the conversion was successful
|
||||
*/
|
||||
gboolean
|
||||
gst_smpte_time_code_from_frame_number (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode * time_code, int frame_number)
|
||||
{
|
||||
int ten_mins;
|
||||
int n;
|
||||
|
||||
g_return_val_if_fail (time_code != NULL, FALSE);
|
||||
g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);
|
||||
|
||||
time_code->hours = 99;
|
||||
time_code->minutes = 99;
|
||||
time_code->seconds = 99;
|
||||
time_code->frames = 99;
|
||||
|
||||
if (frame_number < 0)
|
||||
return FALSE;
|
||||
|
||||
switch (system) {
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_30:
|
||||
if (frame_number >= 24 * NTSC_FRAMES_PER_HOUR)
|
||||
return FALSE;
|
||||
|
||||
ten_mins = frame_number / NTSC_FRAMES_PER_10_MINS;
|
||||
frame_number -= ten_mins * NTSC_FRAMES_PER_10_MINS;
|
||||
|
||||
time_code->hours = ten_mins / 6;
|
||||
time_code->minutes = 10 * (ten_mins % 6);
|
||||
|
||||
if (frame_number < 2) {
|
||||
/* treat the first two frames of each ten minutes specially */
|
||||
time_code->seconds = 0;
|
||||
time_code->frames = frame_number;
|
||||
} else {
|
||||
n = (frame_number - 2) / (60 * 30 - 2);
|
||||
time_code->minutes += n;
|
||||
frame_number -= n * (60 * 30 - 2);
|
||||
|
||||
time_code->seconds = frame_number / 30;
|
||||
time_code->frames = frame_number % 30;
|
||||
}
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_25:
|
||||
if (frame_number >= 24 * 60 * 60 * 25)
|
||||
return FALSE;
|
||||
|
||||
time_code->frames = frame_number % 25;
|
||||
frame_number /= 25;
|
||||
time_code->seconds = frame_number % 60;
|
||||
frame_number /= 60;
|
||||
time_code->minutes = frame_number % 60;
|
||||
frame_number /= 60;
|
||||
time_code->hours = frame_number;
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_24:
|
||||
if (frame_number >= 24 * 60 * 60 * 24)
|
||||
return FALSE;
|
||||
|
||||
time_code->frames = frame_number % 24;
|
||||
frame_number /= 24;
|
||||
time_code->seconds = frame_number % 60;
|
||||
frame_number /= 60;
|
||||
time_code->minutes = frame_number % 60;
|
||||
frame_number /= 60;
|
||||
time_code->hours = frame_number;
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_smpte_time_code_is_valid:
|
||||
* @system: SMPTE Time Code system
|
||||
* @time_code: pointer to time code structure
|
||||
*
|
||||
* Checks that the time code represents a valid time code.
|
||||
*
|
||||
* Returns: TRUE if the time code is valid
|
||||
*/
|
||||
gboolean
|
||||
gst_smpte_time_code_is_valid (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode * time_code)
|
||||
{
|
||||
g_return_val_if_fail (time_code != NULL, FALSE);
|
||||
g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);
|
||||
|
||||
if (time_code->hours < 0 || time_code->hours >= 24)
|
||||
return FALSE;
|
||||
if (time_code->minutes < 0 || time_code->minutes >= 60)
|
||||
return FALSE;
|
||||
if (time_code->seconds < 0 || time_code->seconds >= 60)
|
||||
return FALSE;
|
||||
if (time_code->frames < 0)
|
||||
return FALSE;
|
||||
|
||||
switch (system) {
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_30:
|
||||
if (time_code->frames >= 30)
|
||||
return FALSE;
|
||||
if (time_code->frames >= 2 || time_code->seconds > 0)
|
||||
return TRUE;
|
||||
if (time_code->minutes % 10 != 0)
|
||||
return FALSE;
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_25:
|
||||
if (time_code->frames >= 25)
|
||||
return FALSE;
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_24:
|
||||
if (time_code->frames >= 24)
|
||||
return FALSE;
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_smpte_time_get_frame_number:
|
||||
* @system: SMPTE Time Code system
|
||||
* @frame_number: pointer to frame number
|
||||
* @time_code: pointer to time code structure
|
||||
*
|
||||
* Converts the time code structure to a linear frame number.
|
||||
*
|
||||
* Returns: TRUE if the time code could be converted
|
||||
*/
|
||||
gboolean
|
||||
gst_smpte_time_code_get_frame_number (GstSMPTETimeCodeSystem system,
|
||||
int *frame_number, GstSMPTETimeCode * time_code)
|
||||
{
|
||||
int frame = 0;
|
||||
|
||||
g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);
|
||||
g_return_val_if_fail (time_code != NULL, FALSE);
|
||||
|
||||
if (!gst_smpte_time_code_is_valid (system, time_code)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (system) {
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_30:
|
||||
frame = time_code->hours * NTSC_FRAMES_PER_HOUR;
|
||||
frame += (time_code->minutes / 10) * NTSC_FRAMES_PER_10_MINS;
|
||||
frame += (time_code->minutes % 10) * (30 * 60 - 2);
|
||||
frame += time_code->seconds * 30;
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_25:
|
||||
time_code->frames =
|
||||
25 * ((time_code->hours * 60 + time_code->minutes) * 60 +
|
||||
time_code->seconds);
|
||||
break;
|
||||
case GST_SMPTE_TIME_CODE_SYSTEM_24:
|
||||
time_code->frames =
|
||||
24 * ((time_code->hours * 60 + time_code->minutes) * 60 +
|
||||
time_code->seconds);
|
||||
break;
|
||||
}
|
||||
frame += time_code->frames;
|
||||
|
||||
if (frame_number) {
|
||||
*frame_number = frame;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_smpte_time_get_timestamp:
|
||||
* @system: SMPTE Time Code system
|
||||
* @time_code: pointer to time code structure
|
||||
*
|
||||
* Converts the time code structure to a timestamp.
|
||||
*
|
||||
* Returns: Time stamp for time code, or GST_CLOCK_TIME_NONE if time
|
||||
* code is invalid.
|
||||
*/
|
||||
GstClockTime
|
||||
gst_smpte_time_code_get_timestamp (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode * time_code)
|
||||
{
|
||||
int frame_number;
|
||||
|
||||
g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system),
|
||||
GST_CLOCK_TIME_NONE);
|
||||
g_return_val_if_fail (time_code != NULL, GST_CLOCK_TIME_NONE);
|
||||
|
||||
if (gst_smpte_time_code_get_frame_number (system, &frame_number, time_code)) {
|
||||
static const int framerate_n[3] = { 3000, 25, 24 };
|
||||
static const int framerate_d[3] = { 1001, 1, 1 };
|
||||
|
||||
return gst_util_uint64_scale (frame_number,
|
||||
GST_SECOND * framerate_d[system], framerate_n[system]);
|
||||
}
|
||||
|
||||
return GST_CLOCK_TIME_NONE;
|
||||
}
|
70
ext/dv/gstsmptetimecode.h
Normal file
70
ext/dv/gstsmptetimecode.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2009 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _GST_SMPTE_TIME_CODE_H_
|
||||
#define _GST_SMPTE_TIME_CODE_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GstSMPTETimeCode GstSMPTETimeCode;
|
||||
|
||||
/**
|
||||
* GstSMPTETimeCode:
|
||||
* @GST_SMPTE_TIME_CODE_SYSTEM_30: 29.97 frame per second system (NTSC)
|
||||
* @GST_SMPTE_TIME_CODE_SYSTEM_25: 25 frame per second system (PAL)
|
||||
* @GST_SMPTE_TIME_CODE_SYSTEM_24: 24 frame per second system
|
||||
*
|
||||
* Enum value representing SMPTE Time Code system.
|
||||
*/
|
||||
typedef enum {
|
||||
GST_SMPTE_TIME_CODE_SYSTEM_30 = 0,
|
||||
GST_SMPTE_TIME_CODE_SYSTEM_25,
|
||||
GST_SMPTE_TIME_CODE_SYSTEM_24
|
||||
} GstSMPTETimeCodeSystem;
|
||||
|
||||
struct _GstSMPTETimeCode {
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
int frames;
|
||||
};
|
||||
|
||||
#define GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID(x) \
|
||||
((x) >= GST_SMPTE_TIME_CODE_SYSTEM_30 && (x) <= GST_SMPTE_TIME_CODE_SYSTEM_24)
|
||||
|
||||
#define GST_SMPTE_TIME_CODE_FORMAT "02d:%02d:%02d:%02d"
|
||||
#define GST_SMPTE_TIME_CODE_ARGS(timecode) \
|
||||
(timecode)->hours, (timecode)->minutes, \
|
||||
(timecode)->seconds, (timecode)->frames
|
||||
|
||||
gboolean gst_smpte_time_code_is_valid (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode *time_code);
|
||||
gboolean gst_smpte_time_code_from_frame_number (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode *time_code, int frame_number);
|
||||
gboolean gst_smpte_time_code_get_frame_number (GstSMPTETimeCodeSystem system,
|
||||
int *frame_number, GstSMPTETimeCode *time_code);
|
||||
GstClockTime gst_smpte_time_code_get_timestamp (GstSMPTETimeCodeSystem system,
|
||||
GstSMPTETimeCode *time_code);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
29
ext/dv/meson.build
Normal file
29
ext/dv/meson.build
Normal file
|
@ -0,0 +1,29 @@
|
|||
dv_sources = [
|
||||
'gstdv.c',
|
||||
'gstdvelement.c',
|
||||
'gstdvdec.c',
|
||||
'gstdvdemux.c',
|
||||
'gstsmptetimecode.c',
|
||||
'smpte_test.c',
|
||||
]
|
||||
|
||||
dv_dep = dependency('libdv', version : '>= 0.100', required : get_option('dv'),
|
||||
fallback: ['dv', 'dv_dep'])
|
||||
|
||||
if dv_dep.found()
|
||||
gstdv = library('gstdv',
|
||||
dv_sources,
|
||||
c_args : gst_plugins_good_args,
|
||||
include_directories : [configinc, libsinc],
|
||||
dependencies : [gstbase_dep, gsttag_dep, gstaudio_dep, gstvideo_dep, dv_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstdv, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstdv]
|
||||
# FIXME
|
||||
#executable('smpte_test',
|
||||
# 'smpte_test.c', 'gstsmptetimecode.c',
|
||||
# dependencies : [gstbase_dep, gsttag_dep, gstaudio_dep, gstvideo_dep, dv_dep],
|
||||
# install : false)
|
||||
endif
|
81
ext/dv/smpte_test.c
Normal file
81
ext/dv/smpte_test.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "gstsmptetimecode.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#define NTSC_FRAMES_PER_10_MINS (10*60*30 - 10*2 + 2)
|
||||
#define NTSC_FRAMES_PER_HOUR (6*NTSC_FRAMES_PER_10_MINS)
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GstSMPTETimeCode tc;
|
||||
int i;
|
||||
int min;
|
||||
|
||||
for (min = 0; min < 3; min++) {
|
||||
g_print ("--- minute %d ---\n", min);
|
||||
for (i = min * 60 * 30 - 5; i <= min * 60 * 30 + 5; i++) {
|
||||
gst_smpte_time_code_from_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30, &tc,
|
||||
i);
|
||||
g_print ("%d %02d:%02d:%02d:%02d\n", i, tc.hours, tc.minutes, tc.seconds,
|
||||
tc.frames);
|
||||
}
|
||||
}
|
||||
|
||||
for (min = 9; min < 12; min++) {
|
||||
g_print ("--- minute %d ---\n", min);
|
||||
for (i = min * 60 * 30 - 5 - 18; i <= min * 60 * 30 + 5 - 18; i++) {
|
||||
gst_smpte_time_code_from_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30, &tc,
|
||||
i);
|
||||
g_print ("%d %02d:%02d:%02d:%02d\n", i, tc.hours, tc.minutes, tc.seconds,
|
||||
tc.frames);
|
||||
}
|
||||
}
|
||||
|
||||
for (min = -1; min < 2; min++) {
|
||||
int offset = NTSC_FRAMES_PER_HOUR;
|
||||
|
||||
g_print ("--- minute %d ---\n", min);
|
||||
for (i = offset + min * 60 * 30 - 5; i <= offset + min * 60 * 30 + 5; i++) {
|
||||
gst_smpte_time_code_from_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30, &tc,
|
||||
i);
|
||||
g_print ("%d %02d:%02d:%02d:%02d\n", i, tc.hours, tc.minutes, tc.seconds,
|
||||
tc.frames);
|
||||
}
|
||||
}
|
||||
|
||||
for (min = 0; min < 1; min++) {
|
||||
int offset = NTSC_FRAMES_PER_HOUR;
|
||||
|
||||
g_print ("--- minute %d ---\n", min);
|
||||
for (i = 24 * offset - 5; i <= 24 * offset + 5; i++) {
|
||||
gst_smpte_time_code_from_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30, &tc,
|
||||
i);
|
||||
g_print ("%d %02d:%02d:%02d:%02d\n", i, tc.hours, tc.minutes, tc.seconds,
|
||||
tc.frames);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 24 * NTSC_FRAMES_PER_HOUR; i++) {
|
||||
int fn;
|
||||
int ret;
|
||||
|
||||
gst_smpte_time_code_from_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30, &tc,
|
||||
i);
|
||||
|
||||
ret = gst_smpte_time_code_get_frame_number (GST_SMPTE_TIME_CODE_SYSTEM_30,
|
||||
&fn, &tc);
|
||||
if (!ret) {
|
||||
g_print ("bad valid at %d\n", i);
|
||||
}
|
||||
if (fn != i) {
|
||||
g_print ("index mismatch %d != %d\n", fn, i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
42
ext/flac/gstflac.c
Normal file
42
ext/flac/gstflac.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstflacelements.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (flacdec, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (flacenc, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (flactag, plugin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
flac,
|
||||
"The FLAC Lossless compressor Codec",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
850
ext/flac/gstflacdec.c
Normal file
850
ext/flac/gstflacdec.c
Normal file
|
@ -0,0 +1,850 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2006,2011> Tim-Philipp Müller <tim centricular net>
|
||||
* Copyright (C) <2006> Jan Schmidt <thaytan at mad scientist com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-flacdec
|
||||
* @title: flacdec
|
||||
* @see_also: #GstFlacEnc
|
||||
*
|
||||
* flacdec decodes FLAC streams.
|
||||
* [FLAC](http://flac.sourceforge.net/) is a Free Lossless Audio Codec.
|
||||
*
|
||||
* ## Example launch line
|
||||
* |[
|
||||
* gst-launch-1.0 filesrc location=media/small/dark.441-16-s.flac ! flacparse ! flacdec ! audioconvert ! audioresample ! autoaudiosink
|
||||
* ]|
|
||||
* |[
|
||||
* gst-launch-1.0 souphttpsrc location=http://gstreamer.freedesktop.org/media/small/dark.441-16-s.flac ! flacparse ! flacdec ! audioconvert ! audioresample ! queue min-threshold-buffers=10 ! autoaudiosink
|
||||
* ]|
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gstflacdec.h"
|
||||
#include <gst/gst-i18n-plugin.h>
|
||||
#include <gst/tag/tag.h>
|
||||
|
||||
#include "gstflacelements.h"
|
||||
|
||||
/* Taken from http://flac.sourceforge.net/format.html#frame_header */
|
||||
static const GstAudioChannelPosition channel_positions[8][8] = {
|
||||
{GST_AUDIO_CHANNEL_POSITION_MONO},
|
||||
{GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}, {
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
|
||||
/* FIXME: 7/8 channel layouts are not defined in the FLAC specs */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}, {
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
|
||||
};
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (flacdec_debug);
|
||||
#define GST_CAT_DEFAULT flacdec_debug
|
||||
|
||||
static FLAC__StreamDecoderReadStatus
|
||||
gst_flac_dec_read_stream (const FLAC__StreamDecoder * decoder,
|
||||
FLAC__byte buffer[], size_t * bytes, void *client_data);
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
gst_flac_dec_write_stream (const FLAC__StreamDecoder * decoder,
|
||||
const FLAC__Frame * frame,
|
||||
const FLAC__int32 * const buffer[], void *client_data);
|
||||
static gboolean
|
||||
gst_flac_dec_handle_decoder_error (GstFlacDec * dec, gboolean msg);
|
||||
static void gst_flac_dec_metadata_cb (const FLAC__StreamDecoder *
|
||||
decoder, const FLAC__StreamMetadata * metadata, void *client_data);
|
||||
static void gst_flac_dec_error_cb (const FLAC__StreamDecoder *
|
||||
decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
||||
|
||||
static void gst_flac_dec_flush (GstAudioDecoder * audio_dec, gboolean hard);
|
||||
static gboolean gst_flac_dec_set_format (GstAudioDecoder * dec, GstCaps * caps);
|
||||
static gboolean gst_flac_dec_start (GstAudioDecoder * dec);
|
||||
static gboolean gst_flac_dec_stop (GstAudioDecoder * dec);
|
||||
static GstFlowReturn gst_flac_dec_handle_frame (GstAudioDecoder * audio_dec,
|
||||
GstBuffer * buf);
|
||||
|
||||
G_DEFINE_TYPE (GstFlacDec, gst_flac_dec, GST_TYPE_AUDIO_DECODER);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (flacdec, "flacdec", GST_RANK_PRIMARY,
|
||||
GST_TYPE_FLAC_DEC, flac_element_init (plugin));
|
||||
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define FORMATS "{ S8, S16LE, S24_32LE, S32LE } "
|
||||
#else
|
||||
#define FORMATS "{ S8, S16BE, S24_32BE, S32BE } "
|
||||
#endif
|
||||
|
||||
#define GST_FLAC_DEC_SRC_CAPS \
|
||||
"audio/x-raw, " \
|
||||
"format = (string) " FORMATS ", " \
|
||||
"layout = (string) interleaved, " \
|
||||
"rate = (int) [ 1, 655350 ], " \
|
||||
"channels = (int) [ 1, 8 ]"
|
||||
|
||||
#define GST_FLAC_DEC_SINK_CAPS \
|
||||
"audio/x-flac, " \
|
||||
"framed = (boolean) true, " \
|
||||
"rate = (int) [ 1, 655350 ], " \
|
||||
"channels = (int) [ 1, 8 ]"
|
||||
|
||||
static GstStaticPadTemplate flac_dec_src_factory =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_FLAC_DEC_SRC_CAPS));
|
||||
static GstStaticPadTemplate flac_dec_sink_factory =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_FLAC_DEC_SINK_CAPS));
|
||||
|
||||
static void
|
||||
gst_flac_dec_class_init (GstFlacDecClass * klass)
|
||||
{
|
||||
GstAudioDecoderClass *audiodecoder_class;
|
||||
GstElementClass *gstelement_class;
|
||||
|
||||
audiodecoder_class = (GstAudioDecoderClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (flacdec_debug, "flacdec", 0, "flac decoder");
|
||||
|
||||
audiodecoder_class->stop = GST_DEBUG_FUNCPTR (gst_flac_dec_stop);
|
||||
audiodecoder_class->start = GST_DEBUG_FUNCPTR (gst_flac_dec_start);
|
||||
audiodecoder_class->flush = GST_DEBUG_FUNCPTR (gst_flac_dec_flush);
|
||||
audiodecoder_class->set_format = GST_DEBUG_FUNCPTR (gst_flac_dec_set_format);
|
||||
audiodecoder_class->handle_frame =
|
||||
GST_DEBUG_FUNCPTR (gst_flac_dec_handle_frame);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&flac_dec_src_factory);
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&flac_dec_sink_factory);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "FLAC audio decoder",
|
||||
"Codec/Decoder/Audio", "Decodes FLAC lossless audio streams",
|
||||
"Tim-Philipp Müller <tim@centricular.net>, "
|
||||
"Wim Taymans <wim.taymans@gmail.com>");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_flac_dec_init (GstFlacDec * flacdec)
|
||||
{
|
||||
flacdec->do_resync = FALSE;
|
||||
gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (flacdec), TRUE);
|
||||
gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST
|
||||
(flacdec), TRUE);
|
||||
GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (flacdec));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_flac_dec_start (GstAudioDecoder * audio_dec)
|
||||
{
|
||||
FLAC__StreamDecoderInitStatus s;
|
||||
GstFlacDec *dec;
|
||||
|
||||
dec = GST_FLAC_DEC (audio_dec);
|
||||
|
||||
dec->adapter = gst_adapter_new ();
|
||||
|
||||
dec->decoder = FLAC__stream_decoder_new ();
|
||||
|
||||
gst_audio_info_init (&dec->info);
|
||||
dec->depth = 0;
|
||||
|
||||
/* no point calculating MD5 since it's never checked here */
|
||||
FLAC__stream_decoder_set_md5_checking (dec->decoder, false);
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "initializing decoder");
|
||||
s = FLAC__stream_decoder_init_stream (dec->decoder,
|
||||
gst_flac_dec_read_stream, NULL, NULL, NULL, NULL,
|
||||
gst_flac_dec_write_stream, gst_flac_dec_metadata_cb,
|
||||
gst_flac_dec_error_cb, dec);
|
||||
|
||||
if (s != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
GST_ELEMENT_ERROR (GST_ELEMENT (dec), LIBRARY, INIT, (NULL), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dec->got_headers = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_flac_dec_stop (GstAudioDecoder * dec)
|
||||
{
|
||||
GstFlacDec *flacdec = GST_FLAC_DEC (dec);
|
||||
|
||||
if (flacdec->decoder) {
|
||||
FLAC__stream_decoder_delete (flacdec->decoder);
|
||||
flacdec->decoder = NULL;
|
||||
}
|
||||
|
||||
if (flacdec->adapter) {
|
||||
gst_adapter_clear (flacdec->adapter);
|
||||
g_object_unref (flacdec->adapter);
|
||||
flacdec->adapter = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gint64
|
||||
gst_flac_dec_get_latency (GstFlacDec * flacdec)
|
||||
{
|
||||
/* The FLAC specification states that the data is processed in blocks,
|
||||
* regardless of the number of channels. Thus, The latency can be calculated
|
||||
* using the blocksize and rate. For example a 1 second block sampled at
|
||||
* 44.1KHz has a blocksize of 44100 */
|
||||
|
||||
/* Make sure the rate is valid */
|
||||
if (!flacdec->info.rate)
|
||||
return 0;
|
||||
|
||||
/* Calculate the latecy */
|
||||
return (flacdec->max_blocksize * GST_SECOND) / flacdec->info.rate;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_flac_dec_set_format (GstAudioDecoder * dec, GstCaps * caps)
|
||||
{
|
||||
const GValue *headers;
|
||||
GstFlacDec *flacdec;
|
||||
GstStructure *s;
|
||||
guint i, num;
|
||||
|
||||
flacdec = GST_FLAC_DEC (dec);
|
||||
|
||||
GST_LOG_OBJECT (dec, "sink caps: %" GST_PTR_FORMAT, caps);
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
headers = gst_structure_get_value (s, "streamheader");
|
||||
if (headers == NULL || !GST_VALUE_HOLDS_ARRAY (headers)) {
|
||||
GST_WARNING_OBJECT (dec, "no 'streamheader' field in input caps, try "
|
||||
"adding a flacparse element upstream");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (gst_adapter_available (flacdec->adapter) > 0) {
|
||||
GST_WARNING_OBJECT (dec, "unexpected data left in adapter");
|
||||
gst_adapter_clear (flacdec->adapter);
|
||||
}
|
||||
|
||||
FLAC__stream_decoder_reset (flacdec->decoder);
|
||||
flacdec->got_headers = FALSE;
|
||||
|
||||
num = gst_value_array_get_size (headers);
|
||||
for (i = 0; i < num; ++i) {
|
||||
const GValue *header_val;
|
||||
GstBuffer *header_buf;
|
||||
|
||||
header_val = gst_value_array_get_value (headers, i);
|
||||
if (header_val == NULL || !GST_VALUE_HOLDS_BUFFER (header_val))
|
||||
return FALSE;
|
||||
|
||||
header_buf = g_value_dup_boxed (header_val);
|
||||
GST_INFO_OBJECT (dec, "pushing header buffer of %" G_GSIZE_FORMAT " bytes "
|
||||
"into adapter", gst_buffer_get_size (header_buf));
|
||||
gst_adapter_push (flacdec->adapter, header_buf);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "Processing headers and metadata");
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata (flacdec->decoder)) {
|
||||
GST_WARNING_OBJECT (dec, "process_until_end_of_metadata failed");
|
||||
if (FLAC__stream_decoder_get_state (flacdec->decoder) ==
|
||||
FLAC__STREAM_DECODER_ABORTED) {
|
||||
GST_WARNING_OBJECT (flacdec, "Read callback caused internal abort");
|
||||
/* allow recovery */
|
||||
gst_adapter_clear (flacdec->adapter);
|
||||
FLAC__stream_decoder_flush (flacdec->decoder);
|
||||
gst_flac_dec_handle_decoder_error (flacdec, TRUE);
|
||||
}
|
||||
}
|
||||
GST_INFO_OBJECT (dec, "headers and metadata are now processed");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */
|
||||
static const guint8 crc8_table[256] = {
|
||||
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
|
||||
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
||||
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
|
||||
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
||||
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
|
||||
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
||||
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
|
||||
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
||||
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
|
||||
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
||||
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
|
||||
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
||||
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
|
||||
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
||||
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
|
||||
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
||||
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
|
||||
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
||||
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
|
||||
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
||||
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
|
||||
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
||||
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
|
||||
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
||||
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
|
||||
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
||||
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
|
||||
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
||||
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
|
||||
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
||||
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
|
||||
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
||||
};
|
||||
|
||||
static guint8
|
||||
gst_flac_calculate_crc8 (const guint8 * data, guint length)
|
||||
{
|
||||
guint8 crc = 0;
|
||||
|
||||
while (length--) {
|
||||
crc = crc8_table[crc ^ *data];
|
||||
++data;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* FIXME: for our purposes it's probably enough to just check for the sync
|
||||
* marker - we just want to know if it's a header frame or not */
|
||||
static gboolean
|
||||
gst_flac_dec_scan_got_frame (GstFlacDec * flacdec, const guint8 * data,
|
||||
guint size)
|
||||
{
|
||||
guint headerlen;
|
||||
guint sr_from_end = 0; /* can be 0, 8 or 16 */
|
||||
guint bs_from_end = 0; /* can be 0, 8 or 16 */
|
||||
guint32 val = 0;
|
||||
guint8 bs, sr, ca, ss, pb;
|
||||
gboolean vbs;
|
||||
|
||||
if (size < 10)
|
||||
return FALSE;
|
||||
|
||||
/* sync */
|
||||
if (data[0] != 0xFF || (data[1] & 0xFC) != 0xF8)
|
||||
return FALSE;
|
||||
|
||||
vbs = ! !(data[1] & 1); /* variable blocksize */
|
||||
bs = (data[2] & 0xF0) >> 4; /* blocksize marker */
|
||||
sr = (data[2] & 0x0F); /* samplerate marker */
|
||||
ca = (data[3] & 0xF0) >> 4; /* channel assignment */
|
||||
ss = (data[3] & 0x0F) >> 1; /* sample size marker */
|
||||
pb = (data[3] & 0x01); /* padding bit */
|
||||
|
||||
GST_LOG_OBJECT (flacdec,
|
||||
"got sync, vbs=%d,bs=%x,sr=%x,ca=%x,ss=%x,pb=%x", vbs, bs, sr, ca, ss,
|
||||
pb);
|
||||
|
||||
if (bs == 0 || sr == 0x0F || ca >= 0x0B || ss == 0x03 || ss == 0x07) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* read block size from end of header? */
|
||||
if (bs == 6)
|
||||
bs_from_end = 8;
|
||||
else if (bs == 7)
|
||||
bs_from_end = 16;
|
||||
|
||||
/* read sample rate from end of header? */
|
||||
if (sr == 0x0C)
|
||||
sr_from_end = 8;
|
||||
else if (sr == 0x0D || sr == 0x0E)
|
||||
sr_from_end = 16;
|
||||
|
||||
val = data[4];
|
||||
/* This is slightly faster than a loop */
|
||||
if (!(val & 0x80)) {
|
||||
val = 0;
|
||||
} else if ((val & 0xc0) && !(val & 0x20)) {
|
||||
val = 1;
|
||||
} else if ((val & 0xe0) && !(val & 0x10)) {
|
||||
val = 2;
|
||||
} else if ((val & 0xf0) && !(val & 0x08)) {
|
||||
val = 3;
|
||||
} else if ((val & 0xf8) && !(val & 0x04)) {
|
||||
val = 4;
|
||||
} else if ((val & 0xfc) && !(val & 0x02)) {
|
||||
val = 5;
|
||||
} else if ((val & 0xfe) && !(val & 0x01)) {
|
||||
val = 6;
|
||||
} else {
|
||||
GST_LOG_OBJECT (flacdec, "failed to read sample/frame");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
val++;
|
||||
headerlen = 4 + val + (bs_from_end / 8) + (sr_from_end / 8);
|
||||
|
||||
if (gst_flac_calculate_crc8 (data, headerlen) != data[headerlen]) {
|
||||
GST_LOG_OBJECT (flacdec, "invalid checksum");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_flac_dec_handle_decoder_error (GstFlacDec * dec, gboolean msg)
|
||||
{
|
||||
gboolean ret;
|
||||
|
||||
dec->error_count++;
|
||||
if (dec->error_count > 10) {
|
||||
if (msg)
|
||||
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), (NULL));
|
||||
dec->last_flow = GST_FLOW_ERROR;
|
||||
ret = TRUE;
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (dec, "ignoring error for now at count %d",
|
||||
dec->error_count);
|
||||
ret = FALSE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_flac_dec_metadata_cb (const FLAC__StreamDecoder * decoder,
|
||||
const FLAC__StreamMetadata * metadata, void *client_data)
|
||||
{
|
||||
GstFlacDec *flacdec = GST_FLAC_DEC (client_data);
|
||||
GstAudioDecoder *dec = GST_AUDIO_DECODER (client_data);
|
||||
GstAudioChannelPosition position[8];
|
||||
guint64 curr_latency = 0, old_latency = gst_flac_dec_get_latency (flacdec);
|
||||
|
||||
GST_LOG_OBJECT (flacdec, "metadata type: %d", metadata->type);
|
||||
|
||||
switch (metadata->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:{
|
||||
gint64 samples;
|
||||
guint depth, width, gdepth, channels;
|
||||
|
||||
samples = metadata->data.stream_info.total_samples;
|
||||
|
||||
flacdec->min_blocksize = metadata->data.stream_info.min_blocksize;
|
||||
flacdec->max_blocksize = metadata->data.stream_info.max_blocksize;
|
||||
flacdec->depth = depth = metadata->data.stream_info.bits_per_sample;
|
||||
|
||||
if (depth < 9) {
|
||||
gdepth = width = 8;
|
||||
} else if (depth < 17) {
|
||||
gdepth = width = 16;
|
||||
} else if (depth < 25) {
|
||||
gdepth = 24;
|
||||
width = 32;
|
||||
} else {
|
||||
gdepth = width = 32;
|
||||
}
|
||||
|
||||
channels = metadata->data.stream_info.channels;
|
||||
memcpy (position, channel_positions[channels - 1], sizeof (position));
|
||||
gst_audio_channel_positions_to_valid_order (position, channels);
|
||||
/* Note: we create the inverse reordering map here */
|
||||
gst_audio_get_channel_reorder_map (channels,
|
||||
position, channel_positions[channels - 1],
|
||||
flacdec->channel_reorder_map);
|
||||
|
||||
gst_audio_info_set_format (&flacdec->info,
|
||||
gst_audio_format_build_integer (TRUE, G_BYTE_ORDER, width, gdepth),
|
||||
metadata->data.stream_info.sample_rate,
|
||||
metadata->data.stream_info.channels, position);
|
||||
|
||||
gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (flacdec),
|
||||
&flacdec->info);
|
||||
|
||||
gst_audio_decoder_negotiate (GST_AUDIO_DECODER (flacdec));
|
||||
|
||||
GST_DEBUG_OBJECT (flacdec, "blocksize: min=%u, max=%u",
|
||||
flacdec->min_blocksize, flacdec->max_blocksize);
|
||||
GST_DEBUG_OBJECT (flacdec, "sample rate: %u, channels: %u",
|
||||
flacdec->info.rate, flacdec->info.channels);
|
||||
GST_DEBUG_OBJECT (flacdec, "depth: %u, width: %u", flacdec->depth,
|
||||
flacdec->info.finfo->width);
|
||||
|
||||
GST_DEBUG_OBJECT (flacdec, "total samples = %" G_GINT64_FORMAT, samples);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update the latency if it has changed */
|
||||
curr_latency = gst_flac_dec_get_latency (flacdec);
|
||||
if (old_latency != curr_latency)
|
||||
gst_audio_decoder_set_latency (dec, curr_latency, curr_latency);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_flac_dec_error_cb (const FLAC__StreamDecoder * d,
|
||||
FLAC__StreamDecoderErrorStatus status, void *client_data)
|
||||
{
|
||||
const gchar *error;
|
||||
GstFlacDec *dec;
|
||||
|
||||
dec = GST_FLAC_DEC (client_data);
|
||||
|
||||
switch (status) {
|
||||
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
|
||||
dec->do_resync = TRUE;
|
||||
return;
|
||||
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
|
||||
error = "bad header";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
|
||||
error = "CRC mismatch";
|
||||
break;
|
||||
default:
|
||||
error = "unknown error";
|
||||
break;
|
||||
}
|
||||
|
||||
if (gst_flac_dec_handle_decoder_error (dec, FALSE))
|
||||
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("%s (%d)", error, status));
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderReadStatus
|
||||
gst_flac_dec_read_stream (const FLAC__StreamDecoder * decoder,
|
||||
FLAC__byte buffer[], size_t * bytes, void *client_data)
|
||||
{
|
||||
GstFlacDec *dec = GST_FLAC_DEC (client_data);
|
||||
guint len;
|
||||
|
||||
len = MIN (gst_adapter_available (dec->adapter), *bytes);
|
||||
|
||||
if (len == 0) {
|
||||
GST_LOG_OBJECT (dec, "0 bytes available at the moment");
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (dec, "feeding %u bytes to decoder "
|
||||
"(available=%" G_GSIZE_FORMAT ", bytes=%u)",
|
||||
len, gst_adapter_available (dec->adapter), (guint) * bytes);
|
||||
gst_adapter_copy (dec->adapter, buffer, 0, len);
|
||||
*bytes = len;
|
||||
|
||||
gst_adapter_flush (dec->adapter, len);
|
||||
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
gst_flac_dec_write (GstFlacDec * flacdec, const FLAC__Frame * frame,
|
||||
const FLAC__int32 * const buffer[])
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
GstBuffer *outbuf;
|
||||
guint depth = frame->header.bits_per_sample;
|
||||
guint width, gdepth;
|
||||
guint sample_rate = frame->header.sample_rate;
|
||||
guint channels = frame->header.channels;
|
||||
guint samples = frame->header.blocksize;
|
||||
guint j, i;
|
||||
GstMapInfo map;
|
||||
gboolean caps_changed;
|
||||
GstAudioChannelPosition chanpos[8];
|
||||
|
||||
GST_LOG_OBJECT (flacdec, "samples in frame header: %d", samples);
|
||||
|
||||
if (depth == 0) {
|
||||
if (flacdec->depth < 4 || flacdec->depth > 32) {
|
||||
GST_ERROR_OBJECT (flacdec, "unsupported depth %d from STREAMINFO",
|
||||
flacdec->depth);
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
depth = flacdec->depth;
|
||||
}
|
||||
|
||||
switch (depth) {
|
||||
case 8:
|
||||
gdepth = width = 8;
|
||||
break;
|
||||
case 12:
|
||||
case 16:
|
||||
gdepth = width = 16;
|
||||
break;
|
||||
case 20:
|
||||
case 24:
|
||||
gdepth = 24;
|
||||
width = 32;
|
||||
break;
|
||||
case 32:
|
||||
gdepth = width = 32;
|
||||
break;
|
||||
default:
|
||||
GST_ERROR_OBJECT (flacdec, "unsupported depth %d", depth);
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (sample_rate == 0) {
|
||||
if (flacdec->info.rate != 0) {
|
||||
sample_rate = flacdec->info.rate;
|
||||
} else {
|
||||
GST_ERROR_OBJECT (flacdec, "unknown sample rate");
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
caps_changed = (sample_rate != GST_AUDIO_INFO_RATE (&flacdec->info))
|
||||
|| (width != GST_AUDIO_INFO_WIDTH (&flacdec->info))
|
||||
|| (gdepth != GST_AUDIO_INFO_DEPTH (&flacdec->info))
|
||||
|| (channels != GST_AUDIO_INFO_CHANNELS (&flacdec->info));
|
||||
|
||||
if (caps_changed
|
||||
|| !gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (flacdec))) {
|
||||
GST_DEBUG_OBJECT (flacdec, "Negotiating %d Hz @ %d channels", sample_rate,
|
||||
channels);
|
||||
|
||||
memcpy (chanpos, channel_positions[channels - 1], sizeof (chanpos));
|
||||
gst_audio_channel_positions_to_valid_order (chanpos, channels);
|
||||
gst_audio_info_set_format (&flacdec->info,
|
||||
gst_audio_format_build_integer (TRUE, G_BYTE_ORDER, width, gdepth),
|
||||
sample_rate, channels, chanpos);
|
||||
|
||||
/* Note: we create the inverse reordering map here */
|
||||
gst_audio_get_channel_reorder_map (flacdec->info.channels,
|
||||
flacdec->info.position, channel_positions[flacdec->info.channels - 1],
|
||||
flacdec->channel_reorder_map);
|
||||
|
||||
flacdec->depth = depth;
|
||||
|
||||
gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (flacdec),
|
||||
&flacdec->info);
|
||||
}
|
||||
|
||||
outbuf =
|
||||
gst_buffer_new_allocate (NULL, samples * channels * (width / 8), NULL);
|
||||
|
||||
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
||||
if (width == 8) {
|
||||
gint8 *outbuffer = (gint8 *) map.data;
|
||||
gint *reorder_map = flacdec->channel_reorder_map;
|
||||
|
||||
g_assert (gdepth == 8 && depth == 8);
|
||||
for (i = 0; i < samples; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
*outbuffer++ = (gint8) buffer[reorder_map[j]][i];
|
||||
}
|
||||
}
|
||||
} else if (width == 16) {
|
||||
gint16 *outbuffer = (gint16 *) map.data;
|
||||
gint *reorder_map = flacdec->channel_reorder_map;
|
||||
|
||||
if (gdepth != depth) {
|
||||
for (i = 0; i < samples; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
*outbuffer++ =
|
||||
(gint16) (buffer[reorder_map[j]][i] << (gdepth - depth));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < samples; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
*outbuffer++ = (gint16) buffer[reorder_map[j]][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (width == 32) {
|
||||
gint32 *outbuffer = (gint32 *) map.data;
|
||||
gint *reorder_map = flacdec->channel_reorder_map;
|
||||
|
||||
if (gdepth != depth) {
|
||||
for (i = 0; i < samples; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
*outbuffer++ =
|
||||
(gint32) (buffer[reorder_map[j]][i] << (gdepth - depth));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < samples; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
*outbuffer++ = (gint32) buffer[reorder_map[j]][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
gst_buffer_unmap (outbuf, &map);
|
||||
|
||||
GST_DEBUG_OBJECT (flacdec, "pushing %d samples", samples);
|
||||
if (flacdec->error_count)
|
||||
flacdec->error_count--;
|
||||
|
||||
ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (flacdec), outbuf, 1);
|
||||
|
||||
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
|
||||
GST_DEBUG_OBJECT (flacdec, "finish_frame flow %s", gst_flow_get_name (ret));
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
/* we act on the flow return value later in the handle_frame function, as we
|
||||
* don't want to mess up the internal decoder state by returning ABORT when
|
||||
* the error is in fact non-fatal (like a pad in flushing mode) and we want
|
||||
* to continue later. So just pretend everything's dandy and act later. */
|
||||
flacdec->last_flow = ret;
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
gst_flac_dec_write_stream (const FLAC__StreamDecoder * decoder,
|
||||
const FLAC__Frame * frame,
|
||||
const FLAC__int32 * const buffer[], void *client_data)
|
||||
{
|
||||
return gst_flac_dec_write (GST_FLAC_DEC (client_data), frame, buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_flac_dec_flush (GstAudioDecoder * audio_dec, gboolean hard)
|
||||
{
|
||||
GstFlacDec *dec = GST_FLAC_DEC (audio_dec);
|
||||
|
||||
if (!hard) {
|
||||
guint available = gst_adapter_available (dec->adapter);
|
||||
|
||||
if (available > 0) {
|
||||
GST_INFO_OBJECT (dec, "draining, %u bytes left in adapter", available);
|
||||
FLAC__stream_decoder_process_until_end_of_stream (dec->decoder);
|
||||
}
|
||||
}
|
||||
|
||||
dec->do_resync = FALSE;
|
||||
FLAC__stream_decoder_flush (dec->decoder);
|
||||
gst_adapter_clear (dec->adapter);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_flac_dec_handle_frame (GstAudioDecoder * audio_dec, GstBuffer * buf)
|
||||
{
|
||||
GstFlacDec *dec;
|
||||
|
||||
dec = GST_FLAC_DEC (audio_dec);
|
||||
|
||||
/* drain remaining data? */
|
||||
if (G_UNLIKELY (buf == NULL)) {
|
||||
gst_flac_dec_flush (audio_dec, FALSE);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
if (dec->do_resync) {
|
||||
GST_WARNING_OBJECT (dec, "Lost sync, flushing decoder");
|
||||
FLAC__stream_decoder_flush (dec->decoder);
|
||||
dec->do_resync = FALSE;
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (dec, "frame: ts %" GST_TIME_FORMAT ", flags 0x%04x, "
|
||||
"%" G_GSIZE_FORMAT " bytes", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
|
||||
GST_BUFFER_FLAGS (buf), gst_buffer_get_size (buf));
|
||||
|
||||
/* drop any in-stream headers, we've processed those in set_format already */
|
||||
if (G_UNLIKELY (!dec->got_headers)) {
|
||||
gboolean got_audio_frame;
|
||||
GstMapInfo map;
|
||||
|
||||
/* check if this is a flac audio frame (rather than a header or junk) */
|
||||
gst_buffer_map (buf, &map, GST_MAP_READ);
|
||||
got_audio_frame = gst_flac_dec_scan_got_frame (dec, map.data, map.size);
|
||||
gst_buffer_unmap (buf, &map);
|
||||
|
||||
if (!got_audio_frame) {
|
||||
GST_INFO_OBJECT (dec, "dropping in-stream header, %" G_GSIZE_FORMAT " "
|
||||
"bytes", map.size);
|
||||
gst_audio_decoder_finish_frame (audio_dec, NULL, 1);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (dec, "first audio frame, got all in-stream headers now");
|
||||
dec->got_headers = TRUE;
|
||||
}
|
||||
|
||||
gst_adapter_push (dec->adapter, gst_buffer_ref (buf));
|
||||
buf = NULL;
|
||||
|
||||
dec->last_flow = GST_FLOW_OK;
|
||||
|
||||
/* framed - there should always be enough data to decode something */
|
||||
GST_LOG_OBJECT (dec, "%" G_GSIZE_FORMAT " bytes available",
|
||||
gst_adapter_available (dec->adapter));
|
||||
|
||||
if (!FLAC__stream_decoder_process_single (dec->decoder)) {
|
||||
GST_INFO_OBJECT (dec, "process_single failed");
|
||||
if (FLAC__stream_decoder_get_state (dec->decoder) ==
|
||||
FLAC__STREAM_DECODER_ABORTED) {
|
||||
GST_WARNING_OBJECT (dec, "Read callback caused internal abort");
|
||||
/* allow recovery */
|
||||
gst_adapter_clear (dec->adapter);
|
||||
FLAC__stream_decoder_flush (dec->decoder);
|
||||
gst_flac_dec_handle_decoder_error (dec, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
return dec->last_flow;
|
||||
}
|
62
ext/flac/gstflacdec.h
Normal file
62
ext/flac/gstflacdec.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2011> Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_FLAC_DEC_H__
|
||||
#define __GST_FLAC_DEC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/audio.h>
|
||||
#include <gst/audio/gstaudiodecoder.h>
|
||||
|
||||
#include <FLAC/all.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_FLAC_DEC gst_flac_dec_get_type()
|
||||
G_DECLARE_FINAL_TYPE (GstFlacDec, gst_flac_dec, GST, FLAC_DEC, GstAudioDecoder)
|
||||
|
||||
struct _GstFlacDec {
|
||||
GstAudioDecoder audiodecoder;
|
||||
|
||||
/*< private >*/
|
||||
FLAC__StreamDecoder *decoder;
|
||||
GstAdapter *adapter;
|
||||
|
||||
gboolean got_headers; /* have we received all the header buffers yet? */
|
||||
|
||||
GstFlowReturn last_flow; /* to marshal flow return from finis_frame to
|
||||
* handle_frame via flac callbacks */
|
||||
|
||||
GstAudioInfo info;
|
||||
gint channel_reorder_map[8];
|
||||
gint depth;
|
||||
|
||||
/* from the stream info, needed for scanning */
|
||||
guint16 min_blocksize;
|
||||
guint16 max_blocksize;
|
||||
|
||||
gboolean do_resync;
|
||||
gint error_count;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_FLAC_DEC_H__ */
|
43
ext/flac/gstflacelement.c
Normal file
43
ext/flac/gstflacelement.c
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstflacelements.h"
|
||||
|
||||
#include <gst/tag/tag.h>
|
||||
#include <gst/gst-i18n-plugin.h>
|
||||
|
||||
void
|
||||
flac_element_init (GstPlugin * plugin)
|
||||
{
|
||||
static gsize res = FALSE;
|
||||
if (g_once_init_enter (&res)) {
|
||||
#ifdef ENABLE_NLS
|
||||
GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
|
||||
LOCALEDIR);
|
||||
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
|
||||
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
||||
#endif
|
||||
gst_tag_register_musicbrainz_tags ();
|
||||
g_once_init_leave (&res, TRUE);
|
||||
}
|
||||
}
|
36
ext/flac/gstflacelements.h
Normal file
36
ext/flac/gstflacelements.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Huawei Technologies Co., Ltd.
|
||||
* @Author: Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_FLAC_ELEMENTS_H__
|
||||
#define __GST_FLAC_ELEMENTS_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
void flac_element_init (GstPlugin * plugin);
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (flacenc);
|
||||
GST_ELEMENT_REGISTER_DECLARE (flacdec);
|
||||
GST_ELEMENT_REGISTER_DECLARE (flactag);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_FLAC_ELEMENTS_H__ */
|
1620
ext/flac/gstflacenc.c
Normal file
1620
ext/flac/gstflacenc.c
Normal file
File diff suppressed because it is too large
Load diff
68
ext/flac/gstflacenc.h
Normal file
68
ext/flac/gstflacenc.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_FLAC_ENC_H__
|
||||
#define __GST_FLAC_ENC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudioencoder.h>
|
||||
|
||||
#include <FLAC/all.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_FLAC_ENC (gst_flac_enc_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstFlacEnc, gst_flac_enc, GST, FLAC_ENC, GstAudioEncoder)
|
||||
|
||||
struct _GstFlacEnc {
|
||||
GstAudioEncoder element;
|
||||
|
||||
/* < private > */
|
||||
|
||||
GstFlowReturn last_flow; /* save flow from last push so we can pass the
|
||||
* correct flow return upstream in case the push
|
||||
* fails for some reason */
|
||||
|
||||
guint64 offset;
|
||||
gint quality;
|
||||
gboolean stopped;
|
||||
guint padding;
|
||||
gint seekpoints;
|
||||
|
||||
FLAC__StreamEncoder *encoder;
|
||||
|
||||
FLAC__StreamMetadata **meta;
|
||||
|
||||
GstTagList * tags;
|
||||
GstToc * toc;
|
||||
|
||||
guint64 samples_in;
|
||||
guint64 samples_out;
|
||||
gboolean eos;
|
||||
/* queue headers until we have them all so we can add streamheaders to caps */
|
||||
gboolean got_headers;
|
||||
GList *headers;
|
||||
|
||||
gint channel_reorder_map[8];
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_FLAC_ENC_H__ */
|
504
ext/flac/gstflactag.c
Normal file
504
ext/flac/gstflactag.c
Normal file
|
@ -0,0 +1,504 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2003 Christophe Fergeau <teuf@gnome.org>
|
||||
* Copyright (C) 2008 Jonathan Matthew <jonathan@d14n.org>
|
||||
* Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
*
|
||||
* gstflactag.c: plug-in for reading/modifying vorbis comments in flac files
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-flactag
|
||||
* @title: flactag
|
||||
* @see_also: #flacenc, #flacdec, #GstTagSetter
|
||||
*
|
||||
* The flactag element can change the tag contained within a raw
|
||||
* FLAC stream. Specifically, it modifies the comments header packet
|
||||
* of the FLAC stream.
|
||||
*
|
||||
* Applications can set the tags to write using the #GstTagSetter interface.
|
||||
* Tags contained within the FLAC bitstream will be picked up
|
||||
* automatically (and merged according to the merge mode set via the tag
|
||||
* setter interface).
|
||||
*
|
||||
* ## Example pipelines
|
||||
* |[
|
||||
* gst-launch-1.0 -v filesrc location=foo.flac ! flactag ! filesink location=bar.flac
|
||||
* ]| This element is not useful with gst-launch, because it does not support
|
||||
* setting the tags on a #GstTagSetter interface. Conceptually, the element
|
||||
* will usually be used in this order though.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gsttagsetter.h>
|
||||
#include <gst/base/gstadapter.h>
|
||||
#include <gst/tag/tag.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gstflacelements.h"
|
||||
#include "gstflactag.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (flactag_debug);
|
||||
#define GST_CAT_DEFAULT flactag_debug
|
||||
|
||||
/* elementfactory information */
|
||||
static GstStaticPadTemplate flac_tag_src_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("audio/x-flac")
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate flac_tag_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("audio/x-flac")
|
||||
);
|
||||
|
||||
|
||||
static void gst_flac_tag_dispose (GObject * object);
|
||||
|
||||
static GstFlowReturn gst_flac_tag_chain (GstPad * pad, GstObject * parent,
|
||||
GstBuffer * buffer);
|
||||
|
||||
static GstStateChangeReturn gst_flac_tag_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static gboolean gst_flac_tag_sink_event (GstPad * pad, GstObject * parent,
|
||||
GstEvent * event);
|
||||
|
||||
#define gst_flac_tag_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstFlacTag, gst_flac_tag, GST_TYPE_ELEMENT,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (flactag, "flactag", GST_RANK_PRIMARY,
|
||||
GST_TYPE_FLAC_TAG, flac_element_init (plugin));
|
||||
|
||||
|
||||
static void
|
||||
gst_flac_tag_class_init (GstFlacTagClass * klass)
|
||||
{
|
||||
GstElementClass *gstelement_class;
|
||||
GObjectClass *gobject_class;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (flactag_debug, "flactag", 0, "flac tag rewriter");
|
||||
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
|
||||
gobject_class->dispose = gst_flac_tag_dispose;
|
||||
gstelement_class->change_state = gst_flac_tag_change_state;
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "FLAC tagger",
|
||||
"Formatter/Metadata",
|
||||
"Rewrite tags in a FLAC file", "Christophe Fergeau <teuf@gnome.org>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&flac_tag_sink_template);
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&flac_tag_src_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_flac_tag_dispose (GObject * object)
|
||||
{
|
||||
GstFlacTag *tag = GST_FLAC_TAG (object);
|
||||
|
||||
if (tag->adapter) {
|
||||
g_object_unref (tag->adapter);
|
||||
tag->adapter = NULL;
|
||||
}
|
||||
if (tag->vorbiscomment) {
|
||||
gst_buffer_unref (tag->vorbiscomment);
|
||||
tag->vorbiscomment = NULL;
|
||||
}
|
||||
if (tag->tags) {
|
||||
gst_tag_list_unref (tag->tags);
|
||||
tag->tags = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_flac_tag_init (GstFlacTag * tag)
|
||||
{
|
||||
/* create the sink and src pads */
|
||||
tag->sinkpad =
|
||||
gst_pad_new_from_static_template (&flac_tag_sink_template, "sink");
|
||||
gst_pad_set_chain_function (tag->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (gst_flac_tag_chain));
|
||||
gst_pad_set_event_function (tag->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (gst_flac_tag_sink_event));
|
||||
gst_element_add_pad (GST_ELEMENT (tag), tag->sinkpad);
|
||||
|
||||
tag->srcpad =
|
||||
gst_pad_new_from_static_template (&flac_tag_src_template, "src");
|
||||
gst_element_add_pad (GST_ELEMENT (tag), tag->srcpad);
|
||||
|
||||
tag->adapter = gst_adapter_new ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_flac_tag_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
||||
{
|
||||
GstFlacTag *tag;
|
||||
gboolean ret;
|
||||
|
||||
tag = GST_FLAC_TAG (parent);
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "Received %s event on sinkpad, %" GST_PTR_FORMAT,
|
||||
GST_EVENT_TYPE_NAME (event), event);
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_CAPS:
|
||||
/* FIXME: parse and store the caps. Once we parsed and built the headers,
|
||||
* update the "streamheader" field in the caps and send a new caps event
|
||||
*/
|
||||
ret = gst_pad_push_event (tag->srcpad, event);
|
||||
break;
|
||||
default:
|
||||
ret = gst_pad_event_default (pad, parent, event);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define FLAC_MAGIC "fLaC"
|
||||
#define FLAC_MAGIC_SIZE (sizeof (FLAC_MAGIC) - 1)
|
||||
|
||||
static GstFlowReturn
|
||||
gst_flac_tag_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
||||
{
|
||||
GstFlacTag *tag;
|
||||
GstFlowReturn ret;
|
||||
GstMapInfo map;
|
||||
gsize size;
|
||||
|
||||
ret = GST_FLOW_OK;
|
||||
tag = GST_FLAC_TAG (parent);
|
||||
|
||||
gst_adapter_push (tag->adapter, buffer);
|
||||
|
||||
GST_LOG_OBJECT (pad, "state: %d", tag->state);
|
||||
|
||||
/* Initial state, we don't even know if we are dealing with a flac file */
|
||||
if (tag->state == GST_FLAC_TAG_STATE_INIT) {
|
||||
GstBuffer *id_buffer;
|
||||
|
||||
if (gst_adapter_available (tag->adapter) < sizeof (FLAC_MAGIC))
|
||||
goto cleanup;
|
||||
|
||||
id_buffer = gst_adapter_take_buffer (tag->adapter, FLAC_MAGIC_SIZE);
|
||||
GST_DEBUG_OBJECT (tag, "looking for " FLAC_MAGIC " identifier");
|
||||
if (gst_buffer_memcmp (id_buffer, 0, FLAC_MAGIC, FLAC_MAGIC_SIZE) == 0) {
|
||||
|
||||
GST_DEBUG_OBJECT (tag, "pushing " FLAC_MAGIC " identifier buffer");
|
||||
ret = gst_pad_push (tag->srcpad, id_buffer);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto cleanup;
|
||||
|
||||
tag->state = GST_FLAC_TAG_STATE_METADATA_BLOCKS;
|
||||
} else {
|
||||
/* FIXME: does that work well with FLAC files containing ID3v2 tags ? */
|
||||
gst_buffer_unref (id_buffer);
|
||||
GST_ELEMENT_ERROR (tag, STREAM, WRONG_TYPE, (NULL), (NULL));
|
||||
ret = GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The fLaC magic string has been skipped, try to detect the beginning
|
||||
* of a metadata block
|
||||
*/
|
||||
if (tag->state == GST_FLAC_TAG_STATE_METADATA_BLOCKS) {
|
||||
guint type;
|
||||
gboolean is_last;
|
||||
const guint8 *block_header;
|
||||
|
||||
g_assert (tag->metadata_block_size == 0);
|
||||
g_assert (tag->metadata_last_block == FALSE);
|
||||
|
||||
/* The header of a flac metadata block is 4 bytes long:
|
||||
* 1st bit: indicates whether this is the last metadata info block
|
||||
* 7 next bits: 4 if vorbis comment block
|
||||
* 24 next bits: size of the metadata to follow (big endian)
|
||||
*/
|
||||
if (gst_adapter_available (tag->adapter) < 4)
|
||||
goto cleanup;
|
||||
|
||||
block_header = gst_adapter_map (tag->adapter, 4);
|
||||
|
||||
is_last = ((block_header[0] & 0x80) == 0x80);
|
||||
type = block_header[0] & 0x7F;
|
||||
size = (block_header[1] << 16)
|
||||
| (block_header[2] << 8)
|
||||
| block_header[3];
|
||||
gst_adapter_unmap (tag->adapter);
|
||||
|
||||
/* The 4 bytes long header isn't included in the metadata size */
|
||||
tag->metadata_block_size = size + 4;
|
||||
tag->metadata_last_block = is_last;
|
||||
|
||||
GST_DEBUG_OBJECT (tag,
|
||||
"got metadata block: %" G_GSIZE_FORMAT " bytes, type %d, "
|
||||
"is vorbiscomment: %d, is last: %d",
|
||||
size, type, (type == 0x04), is_last);
|
||||
|
||||
/* Metadata blocks of type 4 are vorbis comment blocks */
|
||||
if (type == 0x04) {
|
||||
tag->state = GST_FLAC_TAG_STATE_VC_METADATA_BLOCK;
|
||||
} else {
|
||||
tag->state = GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Reads a metadata block */
|
||||
if ((tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) ||
|
||||
(tag->state == GST_FLAC_TAG_STATE_VC_METADATA_BLOCK)) {
|
||||
GstBuffer *metadata_buffer;
|
||||
|
||||
if (gst_adapter_available (tag->adapter) < tag->metadata_block_size)
|
||||
goto cleanup;
|
||||
|
||||
metadata_buffer = gst_adapter_take_buffer (tag->adapter,
|
||||
tag->metadata_block_size);
|
||||
/* clear the is-last flag, as the last metadata block will
|
||||
* be the vorbis comment block which we will build ourselves.
|
||||
*/
|
||||
gst_buffer_map (metadata_buffer, &map, GST_MAP_READWRITE);
|
||||
map.data[0] &= (~0x80);
|
||||
gst_buffer_unmap (metadata_buffer, &map);
|
||||
|
||||
if (tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) {
|
||||
GST_DEBUG_OBJECT (tag, "pushing metadata block buffer");
|
||||
ret = gst_pad_push (tag->srcpad, metadata_buffer);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto cleanup;
|
||||
} else {
|
||||
tag->vorbiscomment = metadata_buffer;
|
||||
}
|
||||
tag->metadata_block_size = 0;
|
||||
tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
|
||||
}
|
||||
|
||||
/* This state is mainly used to be able to stop as soon as we read
|
||||
* a vorbiscomment block from the flac file if we are in an only output
|
||||
* tags mode
|
||||
*/
|
||||
if (tag->state == GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK) {
|
||||
/* Check if in the previous iteration we read a vorbis comment metadata
|
||||
* block, and stop now if the user only wants to read tags
|
||||
*/
|
||||
if (tag->vorbiscomment != NULL) {
|
||||
guint8 id_data[4];
|
||||
/* We found some tags, try to parse them and notify the other elements
|
||||
* that we encountered some tags
|
||||
*/
|
||||
GST_DEBUG_OBJECT (tag, "emitting vorbiscomment tags");
|
||||
gst_buffer_extract (tag->vorbiscomment, 0, id_data, 4);
|
||||
tag->tags = gst_tag_list_from_vorbiscomment_buffer (tag->vorbiscomment,
|
||||
id_data, 4, NULL);
|
||||
if (tag->tags != NULL) {
|
||||
gst_pad_push_event (tag->srcpad,
|
||||
gst_event_new_tag (gst_tag_list_ref (tag->tags)));
|
||||
}
|
||||
|
||||
gst_buffer_unref (tag->vorbiscomment);
|
||||
tag->vorbiscomment = NULL;
|
||||
}
|
||||
|
||||
/* Skip to next state */
|
||||
if (tag->metadata_last_block == FALSE) {
|
||||
tag->state = GST_FLAC_TAG_STATE_METADATA_BLOCKS;
|
||||
} else {
|
||||
tag->state = GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Creates a vorbis comment block from the metadata which was set
|
||||
* on the gstreamer element, and add it to the flac stream
|
||||
*/
|
||||
if (tag->state == GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT) {
|
||||
GstBuffer *buffer;
|
||||
const GstTagList *user_tags;
|
||||
GstTagList *merged_tags;
|
||||
|
||||
/* merge the tag lists */
|
||||
user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag));
|
||||
if (user_tags != NULL) {
|
||||
merged_tags = gst_tag_list_merge (user_tags, tag->tags,
|
||||
gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (tag)));
|
||||
} else {
|
||||
merged_tags = gst_tag_list_copy (tag->tags);
|
||||
}
|
||||
|
||||
if (merged_tags == NULL) {
|
||||
/* If we get a NULL list of tags, we must generate a padding block
|
||||
* which is marked as the last metadata block, otherwise we'll
|
||||
* end up with a corrupted flac file.
|
||||
*/
|
||||
GST_WARNING_OBJECT (tag, "No tags found");
|
||||
buffer = gst_buffer_new_and_alloc (12);
|
||||
if (buffer == NULL)
|
||||
goto no_buffer;
|
||||
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
memset (map.data, 0, map.size);
|
||||
map.data[0] = 0x81; /* 0x80 = Last metadata block,
|
||||
* 0x01 = padding block */
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
} else {
|
||||
guchar header[4];
|
||||
guint8 fbit[1];
|
||||
|
||||
memset (header, 0, sizeof (header));
|
||||
header[0] = 0x84; /* 0x80 = Last metadata block,
|
||||
* 0x04 = vorbiscomment block */
|
||||
buffer = gst_tag_list_to_vorbiscomment_buffer (merged_tags, header,
|
||||
sizeof (header), NULL);
|
||||
GST_DEBUG_OBJECT (tag, "Writing tags %" GST_PTR_FORMAT, merged_tags);
|
||||
gst_tag_list_unref (merged_tags);
|
||||
if (buffer == NULL)
|
||||
goto no_comment;
|
||||
|
||||
size = gst_buffer_get_size (buffer);
|
||||
if ((size < 4) || ((size - 4) > 0xFFFFFF))
|
||||
goto comment_too_long;
|
||||
|
||||
fbit[0] = 1;
|
||||
/* Get rid of the framing bit at the end of the vorbiscomment buffer
|
||||
* if it exists since libFLAC seems to lose sync because of this
|
||||
* bit in gstflacdec
|
||||
*/
|
||||
if (gst_buffer_memcmp (buffer, size - 1, fbit, 1) == 0) {
|
||||
buffer = gst_buffer_make_writable (buffer);
|
||||
gst_buffer_resize (buffer, 0, size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* The 4 byte metadata block header isn't accounted for in the total
|
||||
* size of the metadata block
|
||||
*/
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
map.data[1] = (((map.size - 4) & 0xFF0000) >> 16);
|
||||
map.data[2] = (((map.size - 4) & 0x00FF00) >> 8);
|
||||
map.data[3] = ((map.size - 4) & 0x0000FF);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
|
||||
GST_DEBUG_OBJECT (tag, "pushing %" G_GSIZE_FORMAT " byte vorbiscomment "
|
||||
"buffer", map.size);
|
||||
|
||||
ret = gst_pad_push (tag->srcpad, buffer);
|
||||
if (ret != GST_FLOW_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
tag->state = GST_FLAC_TAG_STATE_AUDIO_DATA;
|
||||
}
|
||||
|
||||
/* The metadata blocks have been read, now we are reading audio data */
|
||||
if (tag->state == GST_FLAC_TAG_STATE_AUDIO_DATA) {
|
||||
GstBuffer *buffer;
|
||||
guint avail;
|
||||
|
||||
avail = gst_adapter_available (tag->adapter);
|
||||
if (avail > 0) {
|
||||
buffer = gst_adapter_take_buffer (tag->adapter, avail);
|
||||
ret = gst_pad_push (tag->srcpad, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
GST_LOG_OBJECT (pad, "state: %d, ret: %d", tag->state, ret);
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
no_buffer:
|
||||
{
|
||||
GST_ELEMENT_ERROR (tag, CORE, TOO_LAZY, (NULL),
|
||||
("Error creating 12-byte buffer for padding block"));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
no_comment:
|
||||
{
|
||||
GST_ELEMENT_ERROR (tag, CORE, TAG, (NULL),
|
||||
("Error converting tag list to vorbiscomment buffer"));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
comment_too_long:
|
||||
{
|
||||
/* FLAC vorbis comment blocks are limited to 2^24 bytes,
|
||||
* while the vorbis specs allow more than that. Shouldn't
|
||||
* be a real world problem though
|
||||
*/
|
||||
GST_ELEMENT_ERROR (tag, CORE, TAG, (NULL),
|
||||
("Vorbis comment of size %" G_GSIZE_FORMAT " too long", size));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_flac_tag_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstFlacTag *tag;
|
||||
|
||||
tag = GST_FLAC_TAG (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
/* do something to get out of the chain function faster */
|
||||
break;
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
gst_adapter_clear (tag->adapter);
|
||||
if (tag->vorbiscomment) {
|
||||
gst_buffer_unref (tag->vorbiscomment);
|
||||
tag->vorbiscomment = NULL;
|
||||
}
|
||||
if (tag->tags) {
|
||||
gst_tag_list_unref (tag->tags);
|
||||
tag->tags = NULL;
|
||||
}
|
||||
tag->metadata_block_size = 0;
|
||||
tag->metadata_last_block = FALSE;
|
||||
tag->state = GST_FLAC_TAG_STATE_INIT;
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
}
|
65
ext/flac/gstflactag.h
Normal file
65
ext/flac/gstflactag.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2003 Christophe Fergeau <teuf@gnome.org>
|
||||
* Copyright (C) 2008 Jonathan Matthew <jonathan@d14n.org>
|
||||
* Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
*
|
||||
* gstflactag.c: plug-in for reading/modifying vorbis comments in flac files
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef GST_FLAC_TAG_H
|
||||
#define GST_FLAC_TAG_H
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstadapter.h>
|
||||
|
||||
#define GST_TYPE_FLAC_TAG (gst_flac_tag_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstFlacTag, gst_flac_tag, GST, FLAC_TAG, GstElement)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_FLAC_TAG_STATE_INIT,
|
||||
GST_FLAC_TAG_STATE_METADATA_BLOCKS,
|
||||
GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK,
|
||||
GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK,
|
||||
GST_FLAC_TAG_STATE_VC_METADATA_BLOCK,
|
||||
GST_FLAC_TAG_STATE_ADD_VORBIS_COMMENT,
|
||||
GST_FLAC_TAG_STATE_AUDIO_DATA
|
||||
}
|
||||
GstFlacTagState;
|
||||
|
||||
struct _GstFlacTag
|
||||
{
|
||||
GstElement element;
|
||||
|
||||
/* < private > */
|
||||
|
||||
/* pads */
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
|
||||
GstFlacTagState state;
|
||||
|
||||
GstAdapter *adapter;
|
||||
GstBuffer *vorbiscomment;
|
||||
GstTagList *tags;
|
||||
|
||||
guint metadata_block_size;
|
||||
gboolean metadata_last_block;
|
||||
};
|
||||
|
||||
#endif /* GST_FLAC_TAG_H */
|
23
ext/flac/meson.build
Normal file
23
ext/flac/meson.build
Normal file
|
@ -0,0 +1,23 @@
|
|||
flac_sources = [
|
||||
'gstflac.c',
|
||||
'gstflacelement.c',
|
||||
'gstflacdec.c',
|
||||
'gstflacenc.c',
|
||||
'gstflactag.c',
|
||||
]
|
||||
|
||||
flac_dep = dependency('flac', version : '>=1.1.4', required : get_option('flac'))
|
||||
|
||||
if flac_dep.found()
|
||||
gstflac = library('gstflac',
|
||||
flac_sources,
|
||||
c_args : gst_plugins_good_args + ['-DGST_USE_UNSTABLE_API'],
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc, libsinc],
|
||||
dependencies : [gstbase_dep, gsttag_dep, gstaudio_dep, flac_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstflac, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstflac]
|
||||
endif
|
661
ext/gdk_pixbuf/gstgdkanimation.c
Normal file
661
ext/gdk_pixbuf/gstgdkanimation.c
Normal file
|
@ -0,0 +1,661 @@
|
|||
/*
|
||||
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstgdkanimation.h"
|
||||
#include <unistd.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_gdk_animation_debug);
|
||||
#define GST_CAT_DEFAULT gst_gdk_animation_debug
|
||||
|
||||
static void gst_gdk_animation_class_init (gpointer g_class,
|
||||
gpointer class_data);
|
||||
static void gst_gdk_animation_finalize (GObject * object);
|
||||
|
||||
static gboolean gst_gdk_animation_is_static_image (GdkPixbufAnimation *
|
||||
animation);
|
||||
static GdkPixbuf *gst_gdk_animation_get_static_image (GdkPixbufAnimation *
|
||||
animation);
|
||||
static void gst_gdk_animation_get_size (GdkPixbufAnimation * anim, gint * width,
|
||||
gint * height);
|
||||
static GdkPixbufAnimationIter *gst_gdk_animation_get_iter (GdkPixbufAnimation *
|
||||
anim, const GTimeVal * start_time);
|
||||
|
||||
|
||||
static gpointer parent_class;
|
||||
|
||||
GType
|
||||
gst_gdk_animation_get_type (void)
|
||||
{
|
||||
static GType object_type = 0;
|
||||
|
||||
if (!object_type) {
|
||||
static const GTypeInfo object_info = {
|
||||
sizeof (GstGdkAnimationClass),
|
||||
NULL,
|
||||
NULL,
|
||||
gst_gdk_animation_class_init,
|
||||
NULL, /* class_finalize */
|
||||
NULL, /* class_data */
|
||||
sizeof (GstGdkAnimation),
|
||||
0, /* n_preallocs */
|
||||
NULL,
|
||||
};
|
||||
|
||||
object_type = g_type_register_static (GDK_TYPE_PIXBUF_ANIMATION,
|
||||
"GstGdkAnimation", &object_info, 0);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_gdk_animation_debug, "gstloader_animation", 0,
|
||||
"GStreamer GdkPixbuf loader - GdkAnimation class");
|
||||
}
|
||||
|
||||
return object_type;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_class_init (gpointer g_class, gpointer class_data)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (g_class);
|
||||
GdkPixbufAnimationClass *anim_class = GDK_PIXBUF_ANIMATION_CLASS (g_class);
|
||||
|
||||
parent_class = g_type_class_peek_parent (g_class);
|
||||
|
||||
object_class->finalize = gst_gdk_animation_finalize;
|
||||
|
||||
anim_class->is_static_image = gst_gdk_animation_is_static_image;
|
||||
anim_class->get_static_image = gst_gdk_animation_get_static_image;
|
||||
anim_class->get_size = gst_gdk_animation_get_size;
|
||||
anim_class->get_iter = gst_gdk_animation_get_iter;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_finalize (GObject * object)
|
||||
{
|
||||
GstGdkAnimation *ani = GST_GDK_ANIMATION (object);
|
||||
|
||||
if (ani->temp_fd) {
|
||||
close (ani->temp_fd);
|
||||
}
|
||||
if (ani->temp_location) {
|
||||
remove (ani->temp_location);
|
||||
g_free (ani->temp_location);
|
||||
}
|
||||
if (ani->pixbuf) {
|
||||
g_object_unref (ani->pixbuf);
|
||||
ani->pixbuf = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
GstGdkAnimation *
|
||||
gst_gdk_animation_new (GError ** error)
|
||||
{
|
||||
GstGdkAnimation *ani =
|
||||
GST_GDK_ANIMATION (g_object_new (GST_TYPE_GDK_ANIMATION, NULL));
|
||||
|
||||
return ani;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_gdk_animation_add_data (GstGdkAnimation * ani, const guint8 * data,
|
||||
guint size)
|
||||
{
|
||||
return (write (ani->temp_fd, data, size) == size);
|
||||
}
|
||||
|
||||
void
|
||||
gst_gdk_animation_done_adding (GstGdkAnimation * ani)
|
||||
{
|
||||
close (ani->temp_fd);
|
||||
ani->temp_fd = 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_is_static_image (GdkPixbufAnimation * animation)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_get_size (GdkPixbufAnimation * anim, gint * width,
|
||||
int *height)
|
||||
{
|
||||
GstGdkAnimation *ani = GST_GDK_ANIMATION (anim);
|
||||
|
||||
GST_LOG_OBJECT (ani, "get_size called (%p, %p) %d x %d", width, height,
|
||||
ani->width, ani->height);
|
||||
if (width)
|
||||
*width = ani->width;
|
||||
|
||||
if (height)
|
||||
*height = ani->height;
|
||||
}
|
||||
|
||||
|
||||
static void gst_gdk_animation_iter_class_init (gpointer g_class,
|
||||
gpointer class_data);
|
||||
static void gst_gdk_animation_iter_init (GTypeInstance * instance,
|
||||
gpointer g_class);
|
||||
static void gst_gdk_animation_iter_finalize (GObject * object);
|
||||
|
||||
static gint gst_gdk_animation_iter_get_delay_time (GdkPixbufAnimationIter *
|
||||
iter);
|
||||
static GdkPixbuf *gst_gdk_animation_iter_get_pixbuf (GdkPixbufAnimationIter *
|
||||
iter);
|
||||
static gboolean
|
||||
gst_gdk_animation_iter_on_currently_loading_frame (GdkPixbufAnimationIter *
|
||||
iter);
|
||||
static gboolean gst_gdk_animation_iter_advance (GdkPixbufAnimationIter * iter,
|
||||
const GTimeVal * current_time);
|
||||
|
||||
static gpointer iter_parent_class;
|
||||
|
||||
GType
|
||||
gst_gdk_animation_iter_get_type (void)
|
||||
{
|
||||
static GType object_type = 0;
|
||||
|
||||
if (!object_type) {
|
||||
static const GTypeInfo object_info = {
|
||||
sizeof (GstGdkAnimationIterClass),
|
||||
NULL,
|
||||
NULL,
|
||||
gst_gdk_animation_iter_class_init,
|
||||
NULL, /* class_finalize */
|
||||
NULL, /* class_data */
|
||||
sizeof (GstGdkAnimationIter),
|
||||
0, /* n_preallocs */
|
||||
gst_gdk_animation_iter_init,
|
||||
};
|
||||
|
||||
object_type = g_type_register_static (GDK_TYPE_PIXBUF_ANIMATION_ITER,
|
||||
"GdkPixbufAniAnimIter", &object_info, 0);
|
||||
}
|
||||
|
||||
return object_type;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_iter_class_init (gpointer g_class, gpointer class_data)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (g_class);
|
||||
GdkPixbufAnimationIterClass *anim_iter_class =
|
||||
GDK_PIXBUF_ANIMATION_ITER_CLASS (g_class);
|
||||
|
||||
iter_parent_class = g_type_class_peek_parent (g_class);
|
||||
|
||||
object_class->finalize = gst_gdk_animation_iter_finalize;
|
||||
|
||||
anim_iter_class->get_delay_time = gst_gdk_animation_iter_get_delay_time;
|
||||
anim_iter_class->get_pixbuf = gst_gdk_animation_iter_get_pixbuf;
|
||||
anim_iter_class->on_currently_loading_frame =
|
||||
gst_gdk_animation_iter_on_currently_loading_frame;
|
||||
anim_iter_class->advance = gst_gdk_animation_iter_advance;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_iter_init (GTypeInstance * instance, gpointer g_class)
|
||||
{
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (instance);
|
||||
|
||||
iter->buffers = g_queue_new ();
|
||||
iter->eos = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_iter_finalize (GObject * object)
|
||||
{
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (object);
|
||||
|
||||
g_object_unref (iter->ani);
|
||||
|
||||
if (iter->pipeline)
|
||||
g_object_unref (iter->pipeline);
|
||||
if (iter->pixbuf)
|
||||
g_object_unref (iter->pixbuf);
|
||||
while (iter->buffers) {
|
||||
GstBuffer *buffer = GST_BUFFER (g_queue_pop_head (iter->buffers));
|
||||
|
||||
if (buffer) {
|
||||
GST_LOG_OBJECT (iter, "unreffing buffer %p on finalize", buffer);
|
||||
gst_data_unref (GST_DATA (buffer));
|
||||
} else {
|
||||
g_queue_free (iter->buffers);
|
||||
iter->buffers = NULL;
|
||||
}
|
||||
}
|
||||
G_OBJECT_CLASS (iter_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
got_handoff (GstElement * fakesink, GstBuffer * buffer, GstPad * pad,
|
||||
GstGdkAnimationIter * iter)
|
||||
{
|
||||
GST_LOG_OBJECT (iter, "enqueing buffer %p (timestamp %" G_GUINT64_FORMAT ")",
|
||||
buffer, GST_BUFFER_TIMESTAMP (buffer));
|
||||
gst_data_ref (GST_DATA (buffer));
|
||||
g_queue_push_tail (iter->buffers, buffer);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_iter_create_pipeline (GstGdkAnimationIter * iter)
|
||||
{
|
||||
GstElement *src, *typefind, *autoplugger, *sink, *colorspace;
|
||||
GstCaps *caps = GST_CAPS_NEW ("pixbuf_filter32",
|
||||
"video/x-raw-rgb",
|
||||
"endianness", GST_PROPS_INT (G_BIG_ENDIAN),
|
||||
"bpp", GST_PROPS_INT (32),
|
||||
"red_mask", GST_PROPS_INT (0xFF000000),
|
||||
"green_mask", GST_PROPS_INT (0x00FF0000),
|
||||
"blue_mask", GST_PROPS_INT (0x0000FF00)
|
||||
);
|
||||
|
||||
gst_caps_append (caps, GST_CAPS_NEW ("pixbuf_filter24",
|
||||
"video/x-raw-rgb",
|
||||
"endianness", GST_PROPS_INT (G_BIG_ENDIAN),
|
||||
"bpp", GST_PROPS_INT (24),
|
||||
"red_mask", GST_PROPS_INT (0xFF0000),
|
||||
"green_mask", GST_PROPS_INT (0x00FF00),
|
||||
"blue_mask", GST_PROPS_INT (0x0000FF)
|
||||
));
|
||||
|
||||
iter->pipeline = gst_element_factory_make ("pipeline", "main_pipeline");
|
||||
if (iter->pipeline == NULL)
|
||||
return FALSE;
|
||||
|
||||
if (!(src = gst_element_factory_make ("filesrc", "source")))
|
||||
goto error;
|
||||
gst_bin_add (GST_BIN (iter->pipeline), src);
|
||||
if (iter->ani->temp_location) {
|
||||
g_object_set (src, "location", iter->ani->temp_location, NULL);
|
||||
GST_INFO_OBJECT (iter, "using file '%s'", iter->ani->temp_location);
|
||||
} else {
|
||||
gchar *filename = g_strdup_printf ("/proc/self/fd/%d", iter->ani->temp_fd);
|
||||
|
||||
g_object_set (src, "location", filename, NULL);
|
||||
GST_INFO_OBJECT (iter, "using file '%s'", filename);
|
||||
g_free (filename);
|
||||
}
|
||||
|
||||
/* add typefind for correct typefinding */
|
||||
if ((typefind = gst_element_factory_make ("typefind", "typefind"))) {
|
||||
gst_bin_add (GST_BIN (iter->pipeline), typefind);
|
||||
if (!gst_element_link (src, typefind))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!(autoplugger = gst_element_factory_make ("spider", "autoplugger")))
|
||||
goto error;
|
||||
gst_bin_add (GST_BIN (iter->pipeline), autoplugger);
|
||||
if (!gst_element_link (typefind, autoplugger))
|
||||
goto error;
|
||||
|
||||
/* try ffcolorspace if available so we get svq1, too */
|
||||
colorspace = gst_element_factory_make ("ffcolorspace", "ffcolorspace");
|
||||
if (!colorspace)
|
||||
colorspace = gst_element_factory_make ("colorspace", "colorspace");
|
||||
if (!colorspace)
|
||||
goto error;
|
||||
gst_bin_add (GST_BIN (iter->pipeline), colorspace);
|
||||
if (!gst_element_link (autoplugger, colorspace))
|
||||
goto error;
|
||||
|
||||
if (!(sink = gst_element_factory_make ("fakesink", "sink")))
|
||||
goto error;
|
||||
g_object_set (sink, "signal-handoffs", TRUE, NULL);
|
||||
g_signal_connect (sink, "handoff", (GCallback) got_handoff, iter);
|
||||
gst_bin_add (GST_BIN (iter->pipeline), sink);
|
||||
if (!gst_element_link_filtered (colorspace, sink, caps))
|
||||
goto error;
|
||||
if (gst_element_set_state (iter->pipeline,
|
||||
GST_STATE_PLAYING) != GST_STATE_CHANGE_SUCCESS)
|
||||
goto error;
|
||||
|
||||
return TRUE;
|
||||
error:
|
||||
g_object_unref (iter->pipeline);
|
||||
iter->pipeline = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_iter_may_advance (GstGdkAnimationIter * iter)
|
||||
{
|
||||
GstFormat bytes = GST_FORMAT_BYTES;
|
||||
gint64 offset;
|
||||
gint64 data_amount;
|
||||
|
||||
if (iter->ani->temp_fd == 0 || iter->ani->temp_location == NULL)
|
||||
return TRUE;
|
||||
|
||||
data_amount = lseek (iter->ani->temp_fd, 0, SEEK_CUR);
|
||||
g_assert (data_amount >= 0);
|
||||
if (!gst_element_query (gst_bin_get_by_name (GST_BIN (iter->pipeline),
|
||||
"source"), GST_QUERY_POSITION, &bytes, &offset))
|
||||
g_assert_not_reached ();
|
||||
if (data_amount - offset > GST_GDK_BUFFER_SIZE)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_get_more_buffers (GstGdkAnimationIter * iter)
|
||||
{
|
||||
GstBuffer *last = g_queue_peek_tail (iter->buffers);
|
||||
|
||||
do {
|
||||
GST_LOG_OBJECT (iter, "iterating...");
|
||||
if (!gst_gdk_animation_iter_may_advance (iter)) {
|
||||
GST_LOG_OBJECT (iter, "no more data available");
|
||||
break;
|
||||
}
|
||||
if (!gst_bin_iterate (GST_BIN (iter->pipeline))) {
|
||||
GST_LOG_OBJECT (iter, "iterating done, setting EOS");
|
||||
iter->eos = TRUE;
|
||||
break;
|
||||
}
|
||||
} while (last == g_queue_peek_tail (iter->buffers));
|
||||
return last != g_queue_peek_tail (iter->buffers);
|
||||
}
|
||||
|
||||
static void
|
||||
pixbuf_destroy_notify (guchar * pixels, gpointer data)
|
||||
{
|
||||
GST_LOG ("unreffing buffer %p because pixbuf was destroyed", data);
|
||||
gst_data_unref (GST_DATA (data));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_animation_iter_create_pixbuf (GstGdkAnimationIter * iter)
|
||||
{
|
||||
GstBuffer *buf;
|
||||
GstGdkAnimation *ani = iter->ani;
|
||||
|
||||
buf = g_queue_pop_head (iter->buffers);
|
||||
g_assert (buf);
|
||||
if (iter->pixbuf) {
|
||||
GST_LOG_OBJECT (iter, "unreffing pixbuf %p", iter->pixbuf);
|
||||
g_object_unref (iter->pixbuf);
|
||||
}
|
||||
if (ani->width == 0) {
|
||||
GstPad *pad;
|
||||
GstCaps *caps;
|
||||
GstElement *fakesink =
|
||||
gst_bin_get_by_name (GST_BIN (iter->pipeline), "sink");
|
||||
g_assert (fakesink);
|
||||
pad = gst_element_get_pad (fakesink, "sink");
|
||||
g_assert (pad);
|
||||
caps = gst_pad_get_negotiated_caps (pad);
|
||||
g_assert (caps);
|
||||
g_assert (GST_CAPS_IS_FIXED (caps));
|
||||
g_assert (gst_caps_has_fixed_property (caps, "bpp") &&
|
||||
gst_caps_has_fixed_property (caps, "width") &&
|
||||
gst_caps_has_fixed_property (caps, "height"));
|
||||
gst_caps_get_int (caps, "width", &ani->width);
|
||||
gst_caps_get_int (caps, "height", &ani->height);
|
||||
gst_caps_get_int (caps, "bpp", &ani->bpp);
|
||||
GST_DEBUG_OBJECT (ani, "found format (width %d, height %d, bpp %d)",
|
||||
ani->width, ani->height, ani->bpp);
|
||||
}
|
||||
g_assert (GST_BUFFER_SIZE (buf) == ani->width * ani->height * ani->bpp / 8);
|
||||
if (ani->bpp == 32) {
|
||||
gint i;
|
||||
guint32 *data = (guint32 *) GST_BUFFER_DATA (buf);
|
||||
|
||||
/* ensure opacity */
|
||||
for (i = 0; i < ani->width * ani->height; i++) {
|
||||
data[i] |= 0xFF000000;
|
||||
}
|
||||
}
|
||||
iter->pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buf),
|
||||
GDK_COLORSPACE_RGB, ani->bpp == 32, 8, ani->width, ani->height,
|
||||
ani->width * ani->bpp / 8, pixbuf_destroy_notify, buf);
|
||||
GST_LOG_OBJECT (iter, "created pixbuf %p from buffer %p (refcount %d)",
|
||||
iter->pixbuf, buf, GST_DATA_REFCOUNT_VALUE (buf));
|
||||
}
|
||||
|
||||
static GdkPixbufAnimationIter *
|
||||
gst_gdk_animation_get_iter (GdkPixbufAnimation * anim,
|
||||
const GTimeVal * start_time)
|
||||
{
|
||||
GstGdkAnimation *ani = GST_GDK_ANIMATION (anim);
|
||||
GstGdkAnimationIter *iter;
|
||||
|
||||
if (ani->temp_fd != 0 && ani->temp_location != NULL &&
|
||||
lseek (ani->temp_fd, 0, SEEK_CUR) < GST_GDK_BUFFER_SIZE) {
|
||||
GST_DEBUG_OBJECT (ani, "Not enough data to create iterator.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iter = g_object_new (GST_TYPE_GDK_ANIMATION_ITER, NULL);
|
||||
|
||||
iter->start = *start_time;
|
||||
|
||||
iter->ani = ani;
|
||||
g_object_ref (ani);
|
||||
if (!gst_gdk_animation_iter_create_pipeline (iter))
|
||||
goto error;
|
||||
|
||||
if (!gst_gdk_animation_get_more_buffers (iter))
|
||||
goto error;
|
||||
|
||||
gst_gdk_animation_iter_create_pixbuf (iter);
|
||||
|
||||
return GDK_PIXBUF_ANIMATION_ITER (iter);
|
||||
|
||||
error:
|
||||
g_object_unref (iter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_iter_advance (GdkPixbufAnimationIter * anim_iter,
|
||||
const GTimeVal * current_time)
|
||||
{
|
||||
GstClockTime offset;
|
||||
GstBuffer *buffer = NULL;
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (anim_iter);
|
||||
|
||||
/* compute timestamp that next buffer must match */
|
||||
offset =
|
||||
((GstClockTime) current_time->tv_sec - iter->start.tv_sec) * GST_SECOND;
|
||||
if (iter->start.tv_usec > current_time->tv_usec) {
|
||||
offset -=
|
||||
((GstClockTime) iter->start.tv_usec -
|
||||
current_time->tv_usec) * GST_SECOND / G_USEC_PER_SEC;
|
||||
} else {
|
||||
offset +=
|
||||
((GstClockTime) current_time->tv_usec -
|
||||
iter->start.tv_usec) * GST_SECOND / G_USEC_PER_SEC;
|
||||
}
|
||||
GST_DEBUG_OBJECT (iter,
|
||||
"advancing to %ld:%ld (started at %ld:%ld) need offset %"
|
||||
G_GUINT64_FORMAT, current_time->tv_sec, current_time->tv_usec,
|
||||
iter->start.tv_sec, iter->start.tv_usec, offset);
|
||||
if (!iter->just_seeked
|
||||
&& offset - iter->last_timestamp > GST_GDK_MAX_DELAY_TO_SEEK) {
|
||||
GST_INFO_OBJECT (iter,
|
||||
"current pipeline timestamp is too old (%" G_GUINT64_FORMAT " vs %"
|
||||
G_GUINT64_FORMAT "), seeking there", iter->last_timestamp, offset);
|
||||
if (gst_element_send_event (gst_bin_get_by_name (GST_BIN (iter->pipeline),
|
||||
"sink"),
|
||||
gst_event_new_seek (GST_FORMAT_TIME | GST_SEEK_METHOD_SET |
|
||||
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, offset))) {
|
||||
iter->last_timestamp = offset;
|
||||
iter->just_seeked = TRUE;
|
||||
} else {
|
||||
GST_WARNING_OBJECT (iter,
|
||||
"seek to %" G_GUINT64_FORMAT " didn't work. Iterating there...",
|
||||
offset);
|
||||
}
|
||||
} else if (iter->just_seeked) {
|
||||
iter->just_seeked = FALSE;
|
||||
}
|
||||
|
||||
while (TRUE) {
|
||||
if (g_queue_is_empty (iter->buffers)) {
|
||||
if (iter->eos)
|
||||
return FALSE;
|
||||
if (gst_gdk_animation_get_more_buffers (iter))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (GST_BUFFER_TIMESTAMP (g_queue_peek_head (iter->buffers)) > offset)
|
||||
break;
|
||||
if (buffer) {
|
||||
GST_LOG_OBJECT (iter, "unreffing buffer %p, because timestamp too low (%"
|
||||
G_GUINT64_FORMAT " vs %" G_GUINT64_FORMAT ")",
|
||||
buffer, GST_BUFFER_TIMESTAMP (buffer), offset);
|
||||
gst_data_unref (GST_DATA (buffer));
|
||||
}
|
||||
buffer = GST_BUFFER (g_queue_pop_head (iter->buffers));
|
||||
}
|
||||
if (!buffer)
|
||||
return FALSE;
|
||||
if (GST_BUFFER_TIMESTAMP (buffer) < iter->last_timestamp) {
|
||||
gst_data_unref (GST_DATA (buffer));
|
||||
iter->last_timestamp = offset;
|
||||
return FALSE;
|
||||
}
|
||||
iter->last_timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
||||
g_queue_push_head (iter->buffers, buffer);
|
||||
gst_gdk_animation_iter_create_pixbuf (iter);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gint
|
||||
gst_gdk_animation_iter_get_delay_time (GdkPixbufAnimationIter * anim_iter)
|
||||
{
|
||||
gint delay;
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (anim_iter);
|
||||
|
||||
while (g_queue_is_empty (iter->buffers)) {
|
||||
if (iter->eos) {
|
||||
GST_LOG_OBJECT (iter, "returning delay of infinite, we're EOS");
|
||||
return -1;
|
||||
}
|
||||
if (!gst_gdk_animation_get_more_buffers (iter))
|
||||
return -1; /* FIXME? */
|
||||
}
|
||||
|
||||
delay =
|
||||
(GST_BUFFER_TIMESTAMP (g_queue_peek_head (iter->buffers)) -
|
||||
iter->last_timestamp) * 1000 / GST_SECOND;
|
||||
GST_LOG_OBJECT (iter, "returning delay of %d ms", delay);
|
||||
return delay;
|
||||
}
|
||||
|
||||
GdkPixbuf *
|
||||
gst_gdk_animation_iter_get_pixbuf (GdkPixbufAnimationIter * anim_iter)
|
||||
{
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (anim_iter);
|
||||
|
||||
GST_LOG_OBJECT (iter, "returning pixbuf %p", iter->pixbuf);
|
||||
return iter->pixbuf;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_animation_iter_on_currently_loading_frame (GdkPixbufAnimationIter *
|
||||
anim_iter)
|
||||
{
|
||||
GstGdkAnimationIter *iter = GST_GDK_ANIMATION_ITER (anim_iter);
|
||||
|
||||
/* EOS - last frame */
|
||||
if (iter->eos && g_queue_is_empty (iter->buffers))
|
||||
return TRUE;
|
||||
|
||||
/* can't load more frames */
|
||||
if (!gst_gdk_animation_iter_may_advance (iter))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GdkPixbuf *
|
||||
gst_gdk_animation_get_static_image (GdkPixbufAnimation * animation)
|
||||
{
|
||||
GstGdkAnimation *ani = GST_GDK_ANIMATION (animation);
|
||||
GTimeVal tv;
|
||||
GstGdkAnimationIter *iter;
|
||||
|
||||
if (!ani->pixbuf) {
|
||||
GST_LOG_OBJECT (ani, "trying to create pixbuf");
|
||||
g_get_current_time (&tv);
|
||||
iter =
|
||||
GST_GDK_ANIMATION_ITER (gdk_pixbuf_animation_get_iter (animation, &tv));
|
||||
if (iter) {
|
||||
guint64 offset;
|
||||
GstBuffer *buf;
|
||||
GstFormat time = GST_FORMAT_TIME;
|
||||
|
||||
if (!gst_element_query (gst_bin_get_by_name (GST_BIN (iter->pipeline),
|
||||
"sink"), GST_QUERY_TOTAL, &time, &offset)) {
|
||||
offset = 0;
|
||||
}
|
||||
if (offset > 120 * GST_SECOND) {
|
||||
offset = 120 * GST_SECOND;
|
||||
} else if (offset < 120 * GST_SECOND && offset >= 10 * GST_SECOND) {
|
||||
offset = offset / 2;
|
||||
}
|
||||
g_assert (time == GST_FORMAT_TIME);
|
||||
GST_LOG_OBJECT (ani,
|
||||
"using time offset %" G_GUINT64_FORMAT " for creating static image",
|
||||
offset);
|
||||
while ((buf = g_queue_pop_head (iter->buffers)) != NULL) {
|
||||
gst_data_unref (GST_DATA (buf));
|
||||
}
|
||||
/* now we do evil stuff, be sure to get rid of the iterator afterwards */
|
||||
if (!gst_element_send_event (gst_bin_get_by_name (GST_BIN
|
||||
(iter->pipeline), "sink"),
|
||||
gst_event_new_seek (GST_FORMAT_TIME | GST_SEEK_METHOD_SET |
|
||||
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, offset))) {
|
||||
GST_INFO_OBJECT (ani, "seeking didn't work. Using next image");
|
||||
}
|
||||
|
||||
do {
|
||||
if (g_queue_is_empty (iter->buffers)) {
|
||||
if (iter->eos)
|
||||
return FALSE;
|
||||
if (gst_gdk_animation_get_more_buffers (iter))
|
||||
continue;
|
||||
}
|
||||
} while (FALSE);
|
||||
if (!g_queue_is_empty (iter->buffers)) {
|
||||
gst_gdk_animation_iter_create_pixbuf (iter);
|
||||
ani->pixbuf =
|
||||
gst_gdk_animation_iter_get_pixbuf (GDK_PIXBUF_ANIMATION_ITER
|
||||
(iter));
|
||||
g_object_ref (ani->pixbuf);
|
||||
} else {
|
||||
g_assert (ani->pixbuf == NULL);
|
||||
}
|
||||
/* DiE iterator, DiE */
|
||||
g_object_unref (iter);
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (ani, "Could not get an iterator. No pixbuf available");
|
||||
}
|
||||
}
|
||||
GST_LOG_OBJECT (ani, "Returning pixbuf %p\n", ani->pixbuf);
|
||||
return ani->pixbuf;
|
||||
}
|
117
ext/gdk_pixbuf/gstgdkanimation.h
Normal file
117
ext/gdk_pixbuf/gstgdkanimation.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_LOADER_H__
|
||||
#define __GST_LOADER_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf-animation.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf-io.h>
|
||||
#include <stdio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* how many bytes we need to have available before we dare to start a new iteration */
|
||||
#define GST_GDK_BUFFER_SIZE (102400)
|
||||
/* how far behind we need to be before we attempt to seek */
|
||||
#define GST_GDK_MAX_DELAY_TO_SEEK (GST_SECOND / 4)
|
||||
|
||||
|
||||
#define GST_TYPE_GDK_ANIMATION (gst_gdk_animation_get_type())
|
||||
#define GST_GDK_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GDK_ANIMATION,GstGdkAnimation))
|
||||
#define GST_GDK_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GDK_ANIMATION,GstGdkAnimationClass))
|
||||
#define GST_IS_GDK_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GDK_ANIMATION))
|
||||
#define GST_IS_GDK_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GDK_ANIMATION))
|
||||
|
||||
typedef struct _GstGdkAnimation GstGdkAnimation;
|
||||
typedef struct _GstGdkAnimationClass GstGdkAnimationClass;
|
||||
|
||||
typedef struct _GstGdkAnimationIter GstGdkAnimationIter;
|
||||
typedef struct _GstGdkAnimationIterClass GstGdkAnimationIterClass;
|
||||
|
||||
struct _GstGdkAnimation
|
||||
{
|
||||
GdkPixbufAnimation parent;
|
||||
|
||||
/* name of temporary buffer file */
|
||||
gchar * temp_location;
|
||||
/* file descriptor to temporary file or 0 if we're done writing */
|
||||
int temp_fd;
|
||||
|
||||
/* size of image */
|
||||
gint width;
|
||||
gint height;
|
||||
gint bpp;
|
||||
/* static image we use */
|
||||
GdkPixbuf * pixbuf;
|
||||
};
|
||||
|
||||
struct _GstGdkAnimationClass
|
||||
{
|
||||
GdkPixbufAnimationClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_gdk_animation_get_type (void);
|
||||
|
||||
GstGdkAnimation * gst_gdk_animation_new (GError **error);
|
||||
|
||||
gboolean gst_gdk_animation_add_data (GstGdkAnimation * ani,
|
||||
const guint8 * data,
|
||||
guint size);
|
||||
void gst_gdk_animation_done_adding (GstGdkAnimation * ani);
|
||||
|
||||
|
||||
#define GST_TYPE_GDK_ANIMATION_ITER (gst_gdk_animation_iter_get_type ())
|
||||
#define GST_GDK_ANIMATION_ITER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GST_TYPE_GDK_ANIMATION_ITER, GstGdkAnimationIter))
|
||||
#define GST_IS_GDK_ANIMATION_ITER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GST_TYPE_GDK_ANIMATION_ITER))
|
||||
|
||||
#define GST_GDK_ANIMATION_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_GDK_ANIMATION_ITER, GstGdkAnimationIterClass))
|
||||
#define GST_IS_GDK_ANIMATION_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_GDK_ANIMATION_ITER))
|
||||
#define GST_GDK_ANIMATION_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GDK_ANIMATION_ITER, GstGdkAnimationIterClass))
|
||||
|
||||
struct _GstGdkAnimationIter {
|
||||
GdkPixbufAnimationIter parent;
|
||||
|
||||
/* our animation */
|
||||
GstGdkAnimation * ani;
|
||||
/* start timeval */
|
||||
GTimeVal start;
|
||||
/* timestamp of last buffer */
|
||||
GstClockTime last_timestamp;
|
||||
|
||||
/* pipeline we're using */
|
||||
GstElement * pipeline;
|
||||
gboolean eos;
|
||||
gboolean just_seeked;
|
||||
|
||||
/* current image and the buffers containing the data */
|
||||
GdkPixbuf * pixbuf;
|
||||
GQueue * buffers;
|
||||
};
|
||||
|
||||
struct _GstGdkAnimationIterClass {
|
||||
GdkPixbufAnimationIterClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_gdk_animation_iter_get_type (void) G_GNUC_CONST;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GDK_ANIMATION_H__ */
|
579
ext/gdk_pixbuf/gstgdkpixbufdec.c
Normal file
579
ext/gdk_pixbuf/gstgdkpixbufdec.c
Normal file
|
@ -0,0 +1,579 @@
|
|||
/* GStreamer GdkPixbuf-based image decoder
|
||||
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2003 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gstgdkpixbufelements.h"
|
||||
#include "gstgdkpixbufdec.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gdkpixbufdec_debug);
|
||||
#define GST_CAT_DEFAULT gdkpixbufdec_debug
|
||||
|
||||
static GstStaticPadTemplate gst_gdk_pixbuf_dec_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("image/png; "
|
||||
/* "image/jpeg; " disabled because we can't handle MJPEG */
|
||||
/*"image/gif; " disabled because we can't handle animated gifs */
|
||||
"image/x-icon; "
|
||||
"application/x-navi-animation; "
|
||||
"image/x-cmu-raster; "
|
||||
"image/x-sun-raster; "
|
||||
"image/x-pixmap; "
|
||||
"image/tiff; "
|
||||
"image/x-portable-anymap; "
|
||||
"image/x-portable-bitmap; "
|
||||
"image/x-portable-graymap; "
|
||||
"image/x-portable-pixmap; "
|
||||
"image/bmp; "
|
||||
"image/x-bmp; "
|
||||
"image/x-MS-bmp; "
|
||||
"image/vnd.wap.wbmp; " "image/x-bitmap; " "image/x-tga; "
|
||||
"image/x-pcx; image/svg; image/svg+xml")
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate gst_gdk_pixbuf_dec_src_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB") "; "
|
||||
GST_VIDEO_CAPS_MAKE ("RGBA"))
|
||||
);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gdk_pixbuf_dec_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
static GstFlowReturn gst_gdk_pixbuf_dec_chain (GstPad * pad, GstObject * parent,
|
||||
GstBuffer * buffer);
|
||||
static gboolean gst_gdk_pixbuf_dec_sink_event (GstPad * pad, GstObject * parent,
|
||||
GstEvent * event);
|
||||
|
||||
#define gst_gdk_pixbuf_dec_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstGdkPixbufDec, gst_gdk_pixbuf_dec, GST_TYPE_ELEMENT);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (gdkpixbufdec, "gdkpixbufdec",
|
||||
GST_RANK_SECONDARY, GST_TYPE_GDK_PIXBUF_DEC,
|
||||
gdk_pixbuf_element_init (plugin));
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_dec_sink_setcaps (GstGdkPixbufDec * filter, GstCaps * caps)
|
||||
{
|
||||
const GValue *framerate;
|
||||
GstStructure *s;
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
|
||||
if ((framerate = gst_structure_get_value (s, "framerate")) != NULL) {
|
||||
filter->in_fps_n = gst_value_get_fraction_numerator (framerate);
|
||||
filter->in_fps_d = gst_value_get_fraction_denominator (framerate);
|
||||
GST_DEBUG_OBJECT (filter, "got framerate of %d/%d fps => packetized mode",
|
||||
filter->in_fps_n, filter->in_fps_d);
|
||||
} else {
|
||||
filter->in_fps_n = 0;
|
||||
filter->in_fps_d = 1;
|
||||
GST_DEBUG_OBJECT (filter, "no framerate, assuming single image");
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_gdk_pixbuf_dec_get_capslist (GstCaps * filter)
|
||||
{
|
||||
GSList *slist;
|
||||
GSList *slist0;
|
||||
GstCaps *capslist = NULL;
|
||||
GstCaps *return_caps = NULL;
|
||||
GstCaps *tmpl_caps;
|
||||
|
||||
capslist = gst_caps_new_empty ();
|
||||
slist0 = gdk_pixbuf_get_formats ();
|
||||
|
||||
for (slist = slist0; slist; slist = g_slist_next (slist)) {
|
||||
GdkPixbufFormat *pixbuf_format;
|
||||
char **mimetypes;
|
||||
char **mimetype;
|
||||
|
||||
pixbuf_format = slist->data;
|
||||
mimetypes = gdk_pixbuf_format_get_mime_types (pixbuf_format);
|
||||
|
||||
for (mimetype = mimetypes; *mimetype; mimetype++) {
|
||||
gst_caps_append_structure (capslist, gst_structure_new_empty (*mimetype));
|
||||
}
|
||||
g_strfreev (mimetypes);
|
||||
}
|
||||
g_slist_free (slist0);
|
||||
|
||||
tmpl_caps =
|
||||
gst_static_caps_get (&gst_gdk_pixbuf_dec_sink_template.static_caps);
|
||||
return_caps = gst_caps_intersect (capslist, tmpl_caps);
|
||||
|
||||
gst_caps_unref (tmpl_caps);
|
||||
gst_caps_unref (capslist);
|
||||
|
||||
if (filter && return_caps) {
|
||||
GstCaps *temp;
|
||||
|
||||
temp = gst_caps_intersect (return_caps, filter);
|
||||
gst_caps_unref (return_caps);
|
||||
return_caps = temp;
|
||||
}
|
||||
|
||||
return return_caps;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_dec_sink_query (GstPad * pad, GstObject * parent,
|
||||
GstQuery * query)
|
||||
{
|
||||
gboolean res;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CAPS:
|
||||
{
|
||||
GstCaps *filter, *caps;
|
||||
|
||||
gst_query_parse_caps (query, &filter);
|
||||
caps = gst_gdk_pixbuf_dec_get_capslist (filter);
|
||||
gst_query_set_caps_result (query, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
res = TRUE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = gst_pad_query_default (pad, parent, query);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/* initialize the plugin's class */
|
||||
static void
|
||||
gst_gdk_pixbuf_dec_class_init (GstGdkPixbufDecClass * klass)
|
||||
{
|
||||
GstElementClass *gstelement_class;
|
||||
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
|
||||
gstelement_class->change_state =
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_change_state);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_gdk_pixbuf_dec_src_template);
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_gdk_pixbuf_dec_sink_template);
|
||||
gst_element_class_set_static_metadata (gstelement_class,
|
||||
"GdkPixbuf image decoder", "Codec/Decoder/Image",
|
||||
"Decodes images in a video stream using GdkPixbuf",
|
||||
"David A. Schleef <ds@schleef.org>, Renato Filho <renato.filho@indt.org.br>");
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gdkpixbufdec_debug, "gdkpixbuf", 0,
|
||||
"GdkPixbuf image decoder");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_dec_init (GstGdkPixbufDec * filter)
|
||||
{
|
||||
filter->sinkpad =
|
||||
gst_pad_new_from_static_template (&gst_gdk_pixbuf_dec_sink_template,
|
||||
"sink");
|
||||
gst_pad_set_query_function (filter->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_sink_query));
|
||||
gst_pad_set_chain_function (filter->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_chain));
|
||||
gst_pad_set_event_function (filter->sinkpad,
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_dec_sink_event));
|
||||
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
|
||||
|
||||
filter->srcpad =
|
||||
gst_pad_new_from_static_template (&gst_gdk_pixbuf_dec_src_template,
|
||||
"src");
|
||||
gst_pad_use_fixed_caps (filter->srcpad);
|
||||
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
|
||||
|
||||
filter->last_timestamp = GST_CLOCK_TIME_NONE;
|
||||
filter->pixbuf_loader = NULL;
|
||||
filter->packetized = FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_dec_setup_pool (GstGdkPixbufDec * filter, GstVideoInfo * info)
|
||||
{
|
||||
GstCaps *target;
|
||||
GstQuery *query;
|
||||
GstBufferPool *pool;
|
||||
GstStructure *config;
|
||||
guint size, min, max;
|
||||
|
||||
target = gst_pad_get_current_caps (filter->srcpad);
|
||||
if (!target)
|
||||
return FALSE;
|
||||
|
||||
/* try to get a bufferpool now */
|
||||
/* find a pool for the negotiated caps now */
|
||||
query = gst_query_new_allocation (target, TRUE);
|
||||
|
||||
if (!gst_pad_peer_query (filter->srcpad, query)) {
|
||||
/* not a problem, we use the query defaults */
|
||||
GST_DEBUG_OBJECT (filter, "ALLOCATION query failed");
|
||||
}
|
||||
|
||||
if (gst_query_get_n_allocation_pools (query) > 0) {
|
||||
/* we got configuration from our peer, parse them */
|
||||
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
|
||||
} else {
|
||||
pool = NULL;
|
||||
size = info->size;
|
||||
min = max = 0;
|
||||
}
|
||||
|
||||
gst_query_unref (query);
|
||||
|
||||
if (pool == NULL) {
|
||||
/* we did not get a pool, make one ourselves then */
|
||||
pool = gst_buffer_pool_new ();
|
||||
}
|
||||
|
||||
/* and configure */
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, target, size, min, max);
|
||||
gst_buffer_pool_set_config (pool, config);
|
||||
|
||||
if (filter->pool) {
|
||||
gst_buffer_pool_set_active (filter->pool, FALSE);
|
||||
gst_object_unref (filter->pool);
|
||||
}
|
||||
filter->pool = pool;
|
||||
|
||||
/* and activate */
|
||||
gst_buffer_pool_set_active (filter->pool, TRUE);
|
||||
|
||||
gst_caps_unref (target);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_dec_flush (GstGdkPixbufDec * filter)
|
||||
{
|
||||
GstBuffer *outbuf;
|
||||
GdkPixbuf *pixbuf;
|
||||
int y;
|
||||
guint8 *out_pix;
|
||||
guint8 *in_pix;
|
||||
int in_rowstride, out_rowstride;
|
||||
GstFlowReturn ret;
|
||||
GstCaps *caps = NULL;
|
||||
gint width, height;
|
||||
gint n_channels;
|
||||
GstVideoFrame frame;
|
||||
|
||||
pixbuf = gdk_pixbuf_loader_get_pixbuf (filter->pixbuf_loader);
|
||||
if (pixbuf == NULL)
|
||||
goto no_pixbuf;
|
||||
|
||||
width = gdk_pixbuf_get_width (pixbuf);
|
||||
height = gdk_pixbuf_get_height (pixbuf);
|
||||
|
||||
if (GST_VIDEO_INFO_FORMAT (&filter->info) == GST_VIDEO_FORMAT_UNKNOWN) {
|
||||
GstVideoInfo info;
|
||||
GstVideoFormat fmt;
|
||||
GList *l;
|
||||
|
||||
GST_DEBUG ("Set size to %dx%d", width, height);
|
||||
|
||||
n_channels = gdk_pixbuf_get_n_channels (pixbuf);
|
||||
switch (n_channels) {
|
||||
case 3:
|
||||
fmt = GST_VIDEO_FORMAT_RGB;
|
||||
break;
|
||||
case 4:
|
||||
fmt = GST_VIDEO_FORMAT_RGBA;
|
||||
break;
|
||||
default:
|
||||
goto channels_not_supported;
|
||||
}
|
||||
|
||||
|
||||
gst_video_info_init (&info);
|
||||
gst_video_info_set_format (&info, fmt, width, height);
|
||||
info.fps_n = filter->in_fps_n;
|
||||
info.fps_d = filter->in_fps_d;
|
||||
caps = gst_video_info_to_caps (&info);
|
||||
|
||||
filter->info = info;
|
||||
|
||||
gst_pad_set_caps (filter->srcpad, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_gdk_pixbuf_dec_setup_pool (filter, &info);
|
||||
|
||||
for (l = filter->pending_events; l; l = l->next)
|
||||
gst_pad_push_event (filter->srcpad, l->data);
|
||||
g_list_free (filter->pending_events);
|
||||
filter->pending_events = NULL;
|
||||
}
|
||||
|
||||
ret = gst_buffer_pool_acquire_buffer (filter->pool, &outbuf, NULL);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto no_buffer;
|
||||
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = filter->last_timestamp;
|
||||
GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
|
||||
|
||||
in_pix = gdk_pixbuf_get_pixels (pixbuf);
|
||||
in_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
||||
|
||||
gst_video_frame_map (&frame, &filter->info, outbuf, GST_MAP_WRITE);
|
||||
out_pix = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
|
||||
out_rowstride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
memcpy (out_pix, in_pix, width * GST_VIDEO_FRAME_COMP_PSTRIDE (&frame, 0));
|
||||
in_pix += in_rowstride;
|
||||
out_pix += out_rowstride;
|
||||
}
|
||||
|
||||
gst_video_frame_unmap (&frame);
|
||||
|
||||
GST_DEBUG ("pushing... %" G_GSIZE_FORMAT " bytes",
|
||||
gst_buffer_get_size (outbuf));
|
||||
ret = gst_pad_push (filter->srcpad, outbuf);
|
||||
|
||||
if (ret != GST_FLOW_OK)
|
||||
GST_DEBUG_OBJECT (filter, "flow: %s", gst_flow_get_name (ret));
|
||||
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
no_pixbuf:
|
||||
{
|
||||
GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL),
|
||||
("error getting pixbuf"));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
channels_not_supported:
|
||||
{
|
||||
GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL),
|
||||
("%d channels not supported", n_channels));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
no_buffer:
|
||||
{
|
||||
GST_DEBUG ("Failed to create outbuffer - %s", gst_flow_get_name (ret));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_dec_sink_event (GstPad * pad, GstObject * parent,
|
||||
GstEvent * event)
|
||||
{
|
||||
GstFlowReturn res = GST_FLOW_OK;
|
||||
gboolean ret = TRUE, forward = TRUE;
|
||||
GstGdkPixbufDec *pixbuf;
|
||||
|
||||
pixbuf = GST_GDK_PIXBUF_DEC (parent);
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_CAPS:
|
||||
{
|
||||
GstCaps *caps;
|
||||
|
||||
gst_event_parse_caps (event, &caps);
|
||||
ret = gst_gdk_pixbuf_dec_sink_setcaps (pixbuf, caps);
|
||||
forward = FALSE;
|
||||
break;
|
||||
}
|
||||
case GST_EVENT_EOS:
|
||||
if (pixbuf->pixbuf_loader != NULL) {
|
||||
gdk_pixbuf_loader_close (pixbuf->pixbuf_loader, NULL);
|
||||
res = gst_gdk_pixbuf_dec_flush (pixbuf);
|
||||
g_object_unref (G_OBJECT (pixbuf->pixbuf_loader));
|
||||
pixbuf->pixbuf_loader = NULL;
|
||||
/* as long as we don't have flow returns for event functions we need
|
||||
* to post an error here, or the application might never know that
|
||||
* things failed */
|
||||
if (res != GST_FLOW_OK && res != GST_FLOW_FLUSHING
|
||||
&& res != GST_FLOW_EOS && res != GST_FLOW_NOT_LINKED) {
|
||||
GST_ELEMENT_FLOW_ERROR (pixbuf, res);
|
||||
forward = FALSE;
|
||||
ret = FALSE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GST_EVENT_FLUSH_STOP:
|
||||
g_list_free_full (pixbuf->pending_events,
|
||||
(GDestroyNotify) gst_event_unref);
|
||||
pixbuf->pending_events = NULL;
|
||||
/* Fall through */
|
||||
case GST_EVENT_SEGMENT:
|
||||
{
|
||||
const GstSegment *segment;
|
||||
GstSegment output_segment;
|
||||
guint32 seqnum;
|
||||
|
||||
gst_event_parse_segment (event, &segment);
|
||||
if (segment->format == GST_FORMAT_BYTES)
|
||||
pixbuf->packetized = FALSE;
|
||||
else
|
||||
pixbuf->packetized = TRUE;
|
||||
|
||||
if (segment->format != GST_FORMAT_TIME) {
|
||||
seqnum = gst_event_get_seqnum (event);
|
||||
gst_event_unref (event);
|
||||
gst_segment_init (&output_segment, GST_FORMAT_TIME);
|
||||
event = gst_event_new_segment (&output_segment);
|
||||
gst_event_set_seqnum (event, seqnum);
|
||||
}
|
||||
|
||||
if (pixbuf->pixbuf_loader != NULL) {
|
||||
gdk_pixbuf_loader_close (pixbuf->pixbuf_loader, NULL);
|
||||
g_object_unref (G_OBJECT (pixbuf->pixbuf_loader));
|
||||
pixbuf->pixbuf_loader = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (forward) {
|
||||
if (!gst_pad_has_current_caps (pixbuf->srcpad) &&
|
||||
GST_EVENT_IS_SERIALIZED (event)
|
||||
&& GST_EVENT_TYPE (event) > GST_EVENT_CAPS
|
||||
&& GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP
|
||||
&& GST_EVENT_TYPE (event) != GST_EVENT_EOS) {
|
||||
ret = TRUE;
|
||||
pixbuf->pending_events = g_list_prepend (pixbuf->pending_events, event);
|
||||
} else {
|
||||
ret = gst_pad_event_default (pad, parent, event);
|
||||
}
|
||||
} else {
|
||||
gst_event_unref (event);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
||||
{
|
||||
GstGdkPixbufDec *filter;
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
GError *error = NULL;
|
||||
GstClockTime timestamp;
|
||||
GstMapInfo map;
|
||||
|
||||
filter = GST_GDK_PIXBUF_DEC (parent);
|
||||
|
||||
timestamp = GST_BUFFER_TIMESTAMP (buf);
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (timestamp))
|
||||
filter->last_timestamp = timestamp;
|
||||
|
||||
GST_LOG_OBJECT (filter, "buffer with ts: %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (timestamp));
|
||||
|
||||
if (filter->pixbuf_loader == NULL)
|
||||
filter->pixbuf_loader = gdk_pixbuf_loader_new ();
|
||||
|
||||
gst_buffer_map (buf, &map, GST_MAP_READ);
|
||||
|
||||
GST_LOG_OBJECT (filter, "Writing buffer size %d", (gint) map.size);
|
||||
if (!gdk_pixbuf_loader_write (filter->pixbuf_loader, map.data, map.size,
|
||||
&error))
|
||||
goto error;
|
||||
|
||||
if (filter->packetized == TRUE) {
|
||||
gdk_pixbuf_loader_close (filter->pixbuf_loader, NULL);
|
||||
ret = gst_gdk_pixbuf_dec_flush (filter);
|
||||
g_object_unref (filter->pixbuf_loader);
|
||||
filter->pixbuf_loader = NULL;
|
||||
}
|
||||
|
||||
gst_buffer_unmap (buf, &map);
|
||||
gst_buffer_unref (buf);
|
||||
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
error:
|
||||
{
|
||||
GST_ELEMENT_ERROR (filter, STREAM, DECODE, (NULL),
|
||||
("gdk_pixbuf_loader_write error: %s", error->message));
|
||||
g_error_free (error);
|
||||
gst_buffer_unmap (buf, &map);
|
||||
gst_buffer_unref (buf);
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gdk_pixbuf_dec_change_state (GstElement * element,
|
||||
GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
||||
GstGdkPixbufDec *dec = GST_GDK_PIXBUF_DEC (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
/* default to single image mode, setcaps function might not be called */
|
||||
dec->in_fps_n = 0;
|
||||
dec->in_fps_d = 1;
|
||||
gst_video_info_init (&dec->info);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
dec->in_fps_n = 0;
|
||||
dec->in_fps_d = 0;
|
||||
if (dec->pool) {
|
||||
gst_buffer_pool_set_active (dec->pool, FALSE);
|
||||
gst_object_replace ((GstObject **) & dec->pool, NULL);
|
||||
}
|
||||
g_list_free_full (dec->pending_events, (GDestroyNotify) gst_event_unref);
|
||||
dec->pending_events = NULL;
|
||||
if (dec->pixbuf_loader != NULL) {
|
||||
gdk_pixbuf_loader_close (dec->pixbuf_loader, NULL);
|
||||
g_object_unref (G_OBJECT (dec->pixbuf_loader));
|
||||
dec->pixbuf_loader = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
53
ext/gdk_pixbuf/gstgdkpixbufdec.h
Normal file
53
ext/gdk_pixbuf/gstgdkpixbufdec.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* GStreamer GdkPixbuf-based image decoder
|
||||
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2003 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GDK_PIXBUF_DEC_H__
|
||||
#define __GST_GDK_PIXBUF_DEC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_GDK_PIXBUF_DEC (gst_gdk_pixbuf_dec_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstGdkPixbufDec, gst_gdk_pixbuf_dec, GST, GDK_PIXBUF_DEC,
|
||||
GstElement)
|
||||
|
||||
struct _GstGdkPixbufDec
|
||||
{
|
||||
GstElement element;
|
||||
|
||||
GstPad *sinkpad, *srcpad;
|
||||
|
||||
GstClockTime last_timestamp;
|
||||
GdkPixbufLoader *pixbuf_loader;
|
||||
|
||||
gint in_fps_n, in_fps_d;
|
||||
|
||||
GstVideoInfo info;
|
||||
GstBufferPool *pool;
|
||||
GList *pending_events;
|
||||
gboolean packetized;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GDK_PIXBUF_DEC_H__ */
|
89
ext/gdk_pixbuf/gstgdkpixbufelement.c
Normal file
89
ext/gdk_pixbuf/gstgdkpixbufelement.c
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* GStreamer GdkPixbuf plugin
|
||||
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2003 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstgdkpixbufelements.h"
|
||||
|
||||
#if 0
|
||||
static void gst_gdk_pixbuf_type_find (GstTypeFind * tf, gpointer ignore);
|
||||
|
||||
#define GST_GDK_PIXBUF_TYPE_FIND_SIZE 1024
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_type_find (GstTypeFind * tf, gpointer ignore)
|
||||
{
|
||||
guint8 *data;
|
||||
GdkPixbufLoader *pixbuf_loader;
|
||||
GdkPixbufFormat *format;
|
||||
|
||||
data = gst_type_find_peek (tf, 0, GST_GDK_PIXBUF_TYPE_FIND_SIZE);
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
GST_DEBUG ("creating new loader");
|
||||
|
||||
pixbuf_loader = gdk_pixbuf_loader_new ();
|
||||
|
||||
gdk_pixbuf_loader_write (pixbuf_loader, data, GST_GDK_PIXBUF_TYPE_FIND_SIZE,
|
||||
NULL);
|
||||
|
||||
format = gdk_pixbuf_loader_get_format (pixbuf_loader);
|
||||
|
||||
if (format != NULL) {
|
||||
GstCaps *caps;
|
||||
gchar **p;
|
||||
gchar **mlist = gdk_pixbuf_format_get_mime_types (format);
|
||||
|
||||
for (p = mlist; *p; ++p) {
|
||||
GST_DEBUG ("suggesting mime type %s", *p);
|
||||
caps = gst_caps_new_simple (*p, NULL);
|
||||
gst_type_find_suggest (tf, GST_TYPE_FIND_MINIMUM, caps);
|
||||
gst_caps_free (caps);
|
||||
}
|
||||
g_strfreev (mlist);
|
||||
}
|
||||
|
||||
GST_DEBUG ("closing pixbuf loader, hope it doesn't hang ...");
|
||||
/* librsvg 2.4.x has a bug where it triggers an endless loop in trying
|
||||
to close a gzip that's not an svg; fixed upstream but no good way
|
||||
to work around it */
|
||||
gdk_pixbuf_loader_close (pixbuf_loader, NULL);
|
||||
GST_DEBUG ("closed pixbuf loader");
|
||||
g_object_unref (G_OBJECT (pixbuf_loader));
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
gdk_pixbuf_element_init (GstPlugin * plugin)
|
||||
{
|
||||
static gsize res = FALSE;
|
||||
if (g_once_init_enter (&res)) {
|
||||
#if 0
|
||||
gst_type_find_register (plugin, "image/*", GST_RANK_MARGINAL,
|
||||
gst_gdk_pixbuf_type_find, NULL, GST_CAPS_ANY, NULL);
|
||||
#endif
|
||||
g_once_init_leave (&res, TRUE);
|
||||
}
|
||||
}
|
37
ext/gdk_pixbuf/gstgdkpixbufelements.h
Normal file
37
ext/gdk_pixbuf/gstgdkpixbufelements.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2003 David A. Schleef <ds@schleef.org>
|
||||
* Copyright (C) 2020 Huawei Technologies Co., Ltd.
|
||||
* @Author: Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GDK_PIXBUF_ELEMENTS_H__
|
||||
#define __GST_GDK_PIXBUF_ELEMENTS_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void gdk_pixbuf_element_init (GstPlugin * plugin);
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (gdkpixbufdec);
|
||||
GST_ELEMENT_REGISTER_DECLARE (gdkpixbufoverlay);
|
||||
GST_ELEMENT_REGISTER_DECLARE (gdkpixbufsink);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GDK_PIXBUF_ELEMENTS_H__ */
|
713
ext/gdk_pixbuf/gstgdkpixbufoverlay.c
Normal file
713
ext/gdk_pixbuf/gstgdkpixbufoverlay.c
Normal file
|
@ -0,0 +1,713 @@
|
|||
/* GStreamer GdkPixbuf overlay
|
||||
* Copyright (C) 2012-2014 Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gdkpixbufoverlay
|
||||
* @title: gdkpixbufoverlay
|
||||
*
|
||||
* The gdkpixbufoverlay element overlays an image loaded from file onto
|
||||
* a video stream.
|
||||
*
|
||||
* Changing the positioning or overlay width and height properties at runtime
|
||||
* is supported, but it might be prudent to to protect the property setting
|
||||
* code with GST_BASE_TRANSFORM_LOCK and GST_BASE_TRANSFORM_UNLOCK, as
|
||||
* g_object_set() is not atomic for multiple properties passed in one go.
|
||||
*
|
||||
* Changing the image at runtime is currently not supported.
|
||||
*
|
||||
* Negative offsets are also not yet supported.
|
||||
*
|
||||
* ## Example launch line
|
||||
* |[
|
||||
* gst-launch-1.0 -v videotestsrc ! gdkpixbufoverlay location=image.png ! autovideosink
|
||||
* ]|
|
||||
* Overlays the image in image.png onto the test video picture produced by
|
||||
* videotestsrc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "gstgdkpixbufoverlay.h"
|
||||
|
||||
#include "gstgdkpixbufelements.h"
|
||||
#include <gst/video/gstvideometa.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gdkpixbufoverlay_debug);
|
||||
#define GST_CAT_DEFAULT gdkpixbufoverlay_debug
|
||||
|
||||
static void gst_gdk_pixbuf_overlay_set_property (GObject * object,
|
||||
guint property_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_gdk_pixbuf_overlay_get_property (GObject * object,
|
||||
guint property_id, GValue * value, GParamSpec * pspec);
|
||||
static void gst_gdk_pixbuf_overlay_finalize (GObject * object);
|
||||
|
||||
static gboolean gst_gdk_pixbuf_overlay_start (GstBaseTransform * trans);
|
||||
static gboolean gst_gdk_pixbuf_overlay_stop (GstBaseTransform * trans);
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_overlay_transform_frame_ip (GstVideoFilter * filter,
|
||||
GstVideoFrame * frame);
|
||||
static void gst_gdk_pixbuf_overlay_before_transform (GstBaseTransform * trans,
|
||||
GstBuffer * outbuf);
|
||||
static gboolean gst_gdk_pixbuf_overlay_set_info (GstVideoFilter * filter,
|
||||
GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
|
||||
GstVideoInfo * out_info);
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_overlay_load_image (GstGdkPixbufOverlay * overlay,
|
||||
GError ** err);
|
||||
static void gst_gdk_pixbuf_overlay_set_pixbuf (GstGdkPixbufOverlay * overlay,
|
||||
GdkPixbuf * pixbuf);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LOCATION,
|
||||
PROP_PIXBUF,
|
||||
PROP_POSITIONING_MODE,
|
||||
PROP_OFFSET_X,
|
||||
PROP_OFFSET_Y,
|
||||
PROP_RELATIVE_X,
|
||||
PROP_RELATIVE_Y,
|
||||
PROP_COEF_X,
|
||||
PROP_COEF_Y,
|
||||
PROP_OVERLAY_WIDTH,
|
||||
PROP_OVERLAY_HEIGHT,
|
||||
PROP_ALPHA
|
||||
};
|
||||
|
||||
/* FIXME 2.0: change to absolute positioning */
|
||||
#define DEFAULT_POSITIONING_MODE \
|
||||
GST_GDK_PIXBUF_POSITIONING_PIXELS_RELATIVE_TO_EDGES
|
||||
|
||||
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
|
||||
(GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS))
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
|
||||
(GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS))
|
||||
);
|
||||
|
||||
G_DEFINE_TYPE (GstGdkPixbufOverlay, gst_gdk_pixbuf_overlay,
|
||||
GST_TYPE_VIDEO_FILTER);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (gdkpixbufoverlay, "gdkpixbufoverlay",
|
||||
GST_RANK_NONE, GST_TYPE_GDK_PIXBUF_OVERLAY,
|
||||
gdk_pixbuf_element_init (plugin));
|
||||
|
||||
#define GST_TYPE_GDK_PIXBUF_POSITIONING_MODE \
|
||||
(gst_gdk_pixbuf_positioning_mode_get_type())
|
||||
|
||||
static GType
|
||||
gst_gdk_pixbuf_positioning_mode_get_type (void)
|
||||
{
|
||||
static const GEnumValue pos_modes[] = {
|
||||
{GST_GDK_PIXBUF_POSITIONING_PIXELS_RELATIVE_TO_EDGES,
|
||||
"pixels-relative-to-edges", "pixels-relative-to-edges"},
|
||||
{GST_GDK_PIXBUF_POSITIONING_PIXELS_ABSOLUTE, "pixels-absolute",
|
||||
"pixels-absolute"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
|
||||
static GType type; /* 0 */
|
||||
|
||||
if (!type) {
|
||||
type = g_enum_register_static ("GstGdkPixbufPositioningMode", pos_modes);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_overlay_class_init (GstGdkPixbufOverlayClass * klass)
|
||||
{
|
||||
GstVideoFilterClass *videofilter_class = GST_VIDEO_FILTER_CLASS (klass);
|
||||
GstBaseTransformClass *basetrans_class = GST_BASE_TRANSFORM_CLASS (klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->set_property = gst_gdk_pixbuf_overlay_set_property;
|
||||
gobject_class->get_property = gst_gdk_pixbuf_overlay_get_property;
|
||||
gobject_class->finalize = gst_gdk_pixbuf_overlay_finalize;
|
||||
|
||||
basetrans_class->start = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_overlay_start);
|
||||
basetrans_class->stop = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_overlay_stop);
|
||||
|
||||
basetrans_class->before_transform =
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_overlay_before_transform);
|
||||
|
||||
videofilter_class->set_info =
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_overlay_set_info);
|
||||
videofilter_class->transform_frame_ip =
|
||||
GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_overlay_transform_frame_ip);
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_LOCATION,
|
||||
g_param_spec_string ("location", "location",
|
||||
"Location of image file to overlay", NULL, GST_PARAM_CONTROLLABLE
|
||||
| GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_OFFSET_X,
|
||||
g_param_spec_int ("offset-x", "X Offset",
|
||||
"For positive value, horizontal offset of overlay image in pixels from"
|
||||
" left of video image. For negative value, horizontal offset of overlay"
|
||||
" image in pixels from right of video image", G_MININT, G_MAXINT, 0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_OFFSET_Y,
|
||||
g_param_spec_int ("offset-y", "Y Offset",
|
||||
"For positive value, vertical offset of overlay image in pixels from"
|
||||
" top of video image. For negative value, vertical offset of overlay"
|
||||
" image in pixels from bottom of video image", G_MININT, G_MAXINT, 0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_RELATIVE_X,
|
||||
g_param_spec_double ("relative-x", "Relative X Offset",
|
||||
"Horizontal offset of overlay image in fractions of video image "
|
||||
"width, from top-left corner of video image"
|
||||
" (in relative positioning)", -1.0, 1.0, 0.0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_RELATIVE_Y,
|
||||
g_param_spec_double ("relative-y", "Relative Y Offset",
|
||||
"Vertical offset of overlay image in fractions of video image "
|
||||
"height, from top-left corner of video image"
|
||||
" (in relative positioning)", -1.0, 1.0, 0.0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_OVERLAY_WIDTH,
|
||||
g_param_spec_int ("overlay-width", "Overlay Width",
|
||||
"Width of overlay image in pixels (0 = same as overlay image)", 0,
|
||||
G_MAXINT, 0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_OVERLAY_HEIGHT,
|
||||
g_param_spec_int ("overlay-height", "Overlay Height",
|
||||
"Height of overlay image in pixels (0 = same as overlay image)", 0,
|
||||
G_MAXINT, 0,
|
||||
GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_READWRITE
|
||||
| G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (gobject_class, PROP_ALPHA,
|
||||
g_param_spec_double ("alpha", "Alpha", "Global alpha of overlay image",
|
||||
0.0, 1.0, 1.0, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
|
||||
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* GstGdkPixbufOverlay:pixbuf:
|
||||
*
|
||||
* GdkPixbuf object to render.
|
||||
*
|
||||
* Since: 1.6
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_PIXBUF,
|
||||
g_param_spec_object ("pixbuf", "Pixbuf", "GdkPixbuf object to render",
|
||||
GDK_TYPE_PIXBUF, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
|
||||
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* GstGdkPixbufOverlay:positioning-mode:
|
||||
*
|
||||
* Positioning mode of offset-x and offset-y properties. Determines how
|
||||
* negative x/y offsets will be interpreted. By default negative values
|
||||
* are for positioning relative to the right/bottom edge of the video
|
||||
* image, but you can use this property to select absolute positioning
|
||||
* relative to a (0, 0) origin in the top-left corner. That way negative
|
||||
* offsets will be to the left/above the video image, which allows you to
|
||||
* smoothly slide logos into and out of the frame if desired.
|
||||
*
|
||||
* Since: 1.6
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_POSITIONING_MODE,
|
||||
g_param_spec_enum ("positioning-mode", "Positioning mode",
|
||||
"Positioning mode of offset-x and offset-y properties",
|
||||
GST_TYPE_GDK_PIXBUF_POSITIONING_MODE, DEFAULT_POSITIONING_MODE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/* FIXME the following actually act as a RELATIVE_X/RELATIVE_Y,
|
||||
* but those were already slightly mutated/abused with ABSOLUTE positioning,
|
||||
* so let's keep that and follow suit
|
||||
* Suffice it to say all that could do with cleanup (2.0 ??) */
|
||||
/**
|
||||
* GstGdkPixbufOverlay:coef-x:
|
||||
*
|
||||
* In absolute positioning mode, the x coordinate of overlay image's
|
||||
* top-left corner is now given by
|
||||
* offset-x + (relative-x * overlay_width) + (coef-x * video_width).
|
||||
* This allows to align the image absolutely and relatively
|
||||
* to any edge or center position.
|
||||
*
|
||||
* Since: 1.12
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_COEF_X,
|
||||
g_param_spec_double ("coef-x", "Relative X Offset",
|
||||
"Horizontal offset of overlay image in fractions of video image "
|
||||
"width, from top-left corner of video image (absolute positioning)",
|
||||
-1.0, 1.0, 0.0, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
|
||||
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* GstGdkPixbufOverlay:coef-y:
|
||||
*
|
||||
* In absolute positioning mode, the y coordinate of overlay image's
|
||||
* top-left corner is now given by
|
||||
* offset-y + (relative-y * overlay_height) + (coef-y * video_height).
|
||||
* This allows to align the image absolutely and relatively
|
||||
* to any edge or center position.
|
||||
*
|
||||
* Since: 1.12
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_COEF_Y,
|
||||
g_param_spec_double ("coef-y", "Relative Y Offset",
|
||||
"Vertical offset of overlay image in fractions of video image "
|
||||
"height, from top-left corner of video image (absolute positioning)",
|
||||
-1.0, 1.0, 0.0, GST_PARAM_CONTROLLABLE | GST_PARAM_MUTABLE_PLAYING
|
||||
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_element_class_add_static_pad_template (element_class, &sink_template);
|
||||
gst_element_class_add_static_pad_template (element_class, &src_template);
|
||||
|
||||
gst_element_class_set_static_metadata (element_class,
|
||||
"GdkPixbuf Overlay", "Filter/Effect/Video",
|
||||
"Overlay an image onto a video stream",
|
||||
"Tim-Philipp Müller <tim centricular net>");
|
||||
GST_DEBUG_CATEGORY_INIT (gdkpixbufoverlay_debug, "gdkpixbufoverlay", 0,
|
||||
"debug category for gdkpixbufoverlay element");
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_GDK_PIXBUF_POSITIONING_MODE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_overlay_init (GstGdkPixbufOverlay * overlay)
|
||||
{
|
||||
overlay->offset_x = 0;
|
||||
overlay->offset_y = 0;
|
||||
|
||||
overlay->relative_x = 0.0;
|
||||
overlay->relative_y = 0.0;
|
||||
|
||||
overlay->coef_x = 0.0;
|
||||
overlay->coef_y = 0.0;
|
||||
|
||||
overlay->positioning_mode = DEFAULT_POSITIONING_MODE;
|
||||
|
||||
overlay->overlay_width = 0;
|
||||
overlay->overlay_height = 0;
|
||||
|
||||
overlay->alpha = 1.0;
|
||||
|
||||
overlay->pixbuf = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
gst_gdk_pixbuf_overlay_set_property (GObject * object, guint property_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (object);
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_LOCATION:{
|
||||
GError *err = NULL;
|
||||
g_free (overlay->location);
|
||||
overlay->location = g_value_dup_string (value);
|
||||
if (!gst_gdk_pixbuf_overlay_load_image (overlay, &err)) {
|
||||
GST_ERROR_OBJECT (overlay, "Could not load overlay image: %s",
|
||||
err->message);
|
||||
g_error_free (err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROP_PIXBUF:{
|
||||
GdkPixbuf *pixbuf = g_value_get_object (value);
|
||||
|
||||
if (overlay->pixbuf != NULL)
|
||||
g_object_unref (overlay->pixbuf);
|
||||
if (pixbuf) {
|
||||
overlay->pixbuf = g_object_ref (pixbuf);
|
||||
gst_gdk_pixbuf_overlay_set_pixbuf (overlay, g_object_ref (pixbuf));
|
||||
} else {
|
||||
overlay->pixbuf = NULL;
|
||||
gst_buffer_replace (&overlay->pixels, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROP_OFFSET_X:
|
||||
overlay->offset_x = g_value_get_int (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_OFFSET_Y:
|
||||
overlay->offset_y = g_value_get_int (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_RELATIVE_X:
|
||||
overlay->relative_x = g_value_get_double (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_RELATIVE_Y:
|
||||
overlay->relative_y = g_value_get_double (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_COEF_X:
|
||||
overlay->coef_x = g_value_get_double (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_COEF_Y:
|
||||
overlay->coef_y = g_value_get_double (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_OVERLAY_WIDTH:
|
||||
overlay->overlay_width = g_value_get_int (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_OVERLAY_HEIGHT:
|
||||
overlay->overlay_height = g_value_get_int (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_ALPHA:
|
||||
overlay->alpha = g_value_get_double (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
case PROP_POSITIONING_MODE:
|
||||
overlay->positioning_mode = g_value_get_enum (value);
|
||||
overlay->update_composition = TRUE;
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
|
||||
void
|
||||
gst_gdk_pixbuf_overlay_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (object);
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_LOCATION:
|
||||
g_value_set_string (value, overlay->location);
|
||||
break;
|
||||
case PROP_PIXBUF:
|
||||
g_value_set_object (value, overlay->pixbuf);
|
||||
break;
|
||||
case PROP_OFFSET_X:
|
||||
g_value_set_int (value, overlay->offset_x);
|
||||
break;
|
||||
case PROP_OFFSET_Y:
|
||||
g_value_set_int (value, overlay->offset_y);
|
||||
break;
|
||||
case PROP_RELATIVE_X:
|
||||
g_value_set_double (value, overlay->relative_x);
|
||||
break;
|
||||
case PROP_RELATIVE_Y:
|
||||
g_value_set_double (value, overlay->relative_y);
|
||||
break;
|
||||
case PROP_COEF_X:
|
||||
g_value_set_double (value, overlay->coef_x);
|
||||
break;
|
||||
case PROP_COEF_Y:
|
||||
g_value_set_double (value, overlay->coef_y);
|
||||
break;
|
||||
case PROP_OVERLAY_WIDTH:
|
||||
g_value_set_int (value, overlay->overlay_width);
|
||||
break;
|
||||
case PROP_OVERLAY_HEIGHT:
|
||||
g_value_set_int (value, overlay->overlay_height);
|
||||
break;
|
||||
case PROP_ALPHA:
|
||||
g_value_set_double (value, overlay->alpha);
|
||||
break;
|
||||
case PROP_POSITIONING_MODE:
|
||||
g_value_set_enum (value, overlay->positioning_mode);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
|
||||
void
|
||||
gst_gdk_pixbuf_overlay_finalize (GObject * object)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (object);
|
||||
|
||||
g_free (overlay->location);
|
||||
overlay->location = NULL;
|
||||
|
||||
G_OBJECT_CLASS (gst_gdk_pixbuf_overlay_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_overlay_load_image (GstGdkPixbufOverlay * overlay, GError ** err)
|
||||
{
|
||||
GdkPixbuf *pixbuf;
|
||||
|
||||
pixbuf = gdk_pixbuf_new_from_file (overlay->location, err);
|
||||
|
||||
if (pixbuf == NULL)
|
||||
return FALSE;
|
||||
|
||||
gst_gdk_pixbuf_overlay_set_pixbuf (overlay, pixbuf);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Takes ownership of pixbuf; call with OBJECT_LOCK */
|
||||
static void
|
||||
gst_gdk_pixbuf_overlay_set_pixbuf (GstGdkPixbufOverlay * overlay,
|
||||
GdkPixbuf * pixbuf)
|
||||
{
|
||||
GstVideoMeta *video_meta;
|
||||
guint8 *pixels, *p;
|
||||
gint width, height, stride, w, h, plane;
|
||||
|
||||
if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
|
||||
GdkPixbuf *alpha_pixbuf;
|
||||
|
||||
/* FIXME: we could do this much more efficiently ourselves below, but
|
||||
* we're lazy for now */
|
||||
/* FIXME: perhaps expose substitute_color via properties */
|
||||
alpha_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
|
||||
g_object_unref (pixbuf);
|
||||
pixbuf = alpha_pixbuf;
|
||||
}
|
||||
|
||||
width = gdk_pixbuf_get_width (pixbuf);
|
||||
height = gdk_pixbuf_get_height (pixbuf);
|
||||
stride = gdk_pixbuf_get_rowstride (pixbuf);
|
||||
pixels = gdk_pixbuf_get_pixels (pixbuf);
|
||||
|
||||
/* the memory layout in GdkPixbuf is R-G-B-A, we want:
|
||||
* - B-G-R-A on little-endian platforms
|
||||
* - A-R-G-B on big-endian platforms
|
||||
*/
|
||||
for (h = 0; h < height; ++h) {
|
||||
p = pixels + (h * stride);
|
||||
for (w = 0; w < width; ++w) {
|
||||
guint8 tmp;
|
||||
|
||||
/* R-G-B-A ==> B-G-R-A */
|
||||
tmp = p[0];
|
||||
p[0] = p[2];
|
||||
p[2] = tmp;
|
||||
|
||||
if (G_BYTE_ORDER == G_BIG_ENDIAN) {
|
||||
/* B-G-R-A ==> A-R-G-B */
|
||||
/* we can probably assume sane alignment */
|
||||
*((guint32 *) p) = GUINT32_SWAP_LE_BE (*((guint32 *) p));
|
||||
}
|
||||
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlay->pixels)
|
||||
gst_buffer_unref (overlay->pixels);
|
||||
|
||||
/* assume we have row padding even for the last row */
|
||||
/* transfer ownership of pixbuf to the buffer */
|
||||
overlay->pixels = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
|
||||
pixels, height * stride, 0, height * stride, pixbuf,
|
||||
(GDestroyNotify) g_object_unref);
|
||||
|
||||
video_meta = gst_buffer_add_video_meta (overlay->pixels,
|
||||
GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
|
||||
width, height);
|
||||
|
||||
for (plane = 0; plane < video_meta->n_planes; ++plane)
|
||||
video_meta->stride[plane] = stride;
|
||||
|
||||
overlay->update_composition = TRUE;
|
||||
|
||||
GST_INFO_OBJECT (overlay, "Updated pixbuf, %d x %d", width, height);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_overlay_start (GstBaseTransform * trans)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (trans);
|
||||
GError *err = NULL;
|
||||
|
||||
if (overlay->location != NULL) {
|
||||
if (!gst_gdk_pixbuf_overlay_load_image (overlay, &err))
|
||||
goto error_loading_image;
|
||||
|
||||
gst_base_transform_set_passthrough (trans, FALSE);
|
||||
} else {
|
||||
GST_WARNING_OBJECT (overlay, "no image location set, doing nothing");
|
||||
gst_base_transform_set_passthrough (trans, TRUE);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
error_loading_image:
|
||||
{
|
||||
GST_ELEMENT_ERROR (overlay, RESOURCE, OPEN_READ,
|
||||
("Could not load overlay image."), ("%s", err->message));
|
||||
g_error_free (err);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_overlay_stop (GstBaseTransform * trans)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (trans);
|
||||
|
||||
if (overlay->comp) {
|
||||
gst_video_overlay_composition_unref (overlay->comp);
|
||||
overlay->comp = NULL;
|
||||
}
|
||||
|
||||
gst_buffer_replace (&overlay->pixels, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_overlay_set_info (GstVideoFilter * filter, GstCaps * incaps,
|
||||
GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
|
||||
{
|
||||
GST_INFO_OBJECT (filter, "caps: %" GST_PTR_FORMAT, incaps);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_overlay_update_composition (GstGdkPixbufOverlay * overlay)
|
||||
{
|
||||
GstGdkPixbufPositioningMode positioning_mode;
|
||||
GstVideoOverlayComposition *comp;
|
||||
GstVideoOverlayRectangle *rect;
|
||||
GstVideoMeta *overlay_meta;
|
||||
gint x, y, width, height;
|
||||
gint video_width =
|
||||
GST_VIDEO_INFO_WIDTH (&GST_VIDEO_FILTER (overlay)->in_info);
|
||||
gint video_height =
|
||||
GST_VIDEO_INFO_HEIGHT (&GST_VIDEO_FILTER (overlay)->in_info);
|
||||
|
||||
if (overlay->comp) {
|
||||
gst_video_overlay_composition_unref (overlay->comp);
|
||||
overlay->comp = NULL;
|
||||
}
|
||||
|
||||
if (overlay->alpha == 0.0 || overlay->pixels == NULL)
|
||||
return;
|
||||
|
||||
overlay_meta = gst_buffer_get_video_meta (overlay->pixels);
|
||||
|
||||
positioning_mode = overlay->positioning_mode;
|
||||
GST_DEBUG_OBJECT (overlay, "overlay positioning mode %d", positioning_mode);
|
||||
|
||||
width = overlay->overlay_width;
|
||||
if (width == 0)
|
||||
width = overlay_meta->width;
|
||||
|
||||
height = overlay->overlay_height;
|
||||
if (height == 0)
|
||||
height = overlay_meta->height;
|
||||
|
||||
if (positioning_mode == GST_GDK_PIXBUF_POSITIONING_PIXELS_ABSOLUTE) {
|
||||
x = overlay->offset_x + (overlay->relative_x * width) +
|
||||
(overlay->coef_x * video_width);
|
||||
y = overlay->offset_y + (overlay->relative_y * height) +
|
||||
(overlay->coef_y * video_height);
|
||||
} else {
|
||||
x = overlay->offset_x < 0 ?
|
||||
video_width + overlay->offset_x - width +
|
||||
(overlay->relative_x * video_width) :
|
||||
overlay->offset_x + (overlay->relative_x * video_width);
|
||||
y = overlay->offset_y < 0 ?
|
||||
video_height + overlay->offset_y - height +
|
||||
(overlay->relative_y * video_height) :
|
||||
overlay->offset_y + (overlay->relative_y * video_height);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (overlay, "overlay image dimensions: %d x %d, alpha=%.2f",
|
||||
overlay_meta->width, overlay_meta->height, overlay->alpha);
|
||||
GST_DEBUG_OBJECT (overlay, "properties: x,y: %d,%d "
|
||||
"(%g%%,%g%%) coef (%g%%,%g%%) - WxH: %dx%d",
|
||||
overlay->offset_x, overlay->offset_y,
|
||||
overlay->relative_x * 100.0, overlay->relative_y * 100.0,
|
||||
overlay->coef_x * 100.0, overlay->coef_y * 100.0,
|
||||
overlay->overlay_height, overlay->overlay_width);
|
||||
GST_DEBUG_OBJECT (overlay, "overlay rendered: %d x %d @ %d,%d (onto %d x %d)",
|
||||
width, height, x, y, video_width, video_height);
|
||||
|
||||
rect = gst_video_overlay_rectangle_new_raw (overlay->pixels,
|
||||
x, y, width, height, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
|
||||
|
||||
if (overlay->alpha != 1.0)
|
||||
gst_video_overlay_rectangle_set_global_alpha (rect, overlay->alpha);
|
||||
|
||||
comp = gst_video_overlay_composition_new (rect);
|
||||
gst_video_overlay_rectangle_unref (rect);
|
||||
|
||||
overlay->comp = comp;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_overlay_before_transform (GstBaseTransform * trans,
|
||||
GstBuffer * outbuf)
|
||||
{
|
||||
GstClockTime stream_time;
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (trans);
|
||||
gboolean set_passthrough = FALSE;
|
||||
|
||||
stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_TIMESTAMP (outbuf));
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (stream_time))
|
||||
gst_object_sync_values (GST_OBJECT (trans), stream_time);
|
||||
|
||||
/* now properties have been sync'ed; maybe need to update composition */
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
if (G_UNLIKELY (overlay->update_composition)) {
|
||||
gst_gdk_pixbuf_overlay_update_composition (overlay);
|
||||
overlay->update_composition = FALSE;
|
||||
set_passthrough = TRUE;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
|
||||
/* determine passthrough mode so the buffer is writable if needed
|
||||
* when passed into _transform_ip */
|
||||
if (G_UNLIKELY (set_passthrough))
|
||||
gst_base_transform_set_passthrough (trans, overlay->comp == NULL);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_overlay_transform_frame_ip (GstVideoFilter * filter,
|
||||
GstVideoFrame * frame)
|
||||
{
|
||||
GstGdkPixbufOverlay *overlay = GST_GDK_PIXBUF_OVERLAY (filter);
|
||||
|
||||
if (overlay->comp != NULL)
|
||||
gst_video_overlay_composition_blend (overlay->comp, frame);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
82
ext/gdk_pixbuf/gstgdkpixbufoverlay.h
Normal file
82
ext/gdk_pixbuf/gstgdkpixbufoverlay.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/* GStreamer GdkPixbuf overlay
|
||||
* Copyright (C) 2012-2014 Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _GST_GDK_PIXBUF_OVERLAY_H_
|
||||
#define _GST_GDK_PIXBUF_OVERLAY_H_
|
||||
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideofilter.h>
|
||||
#include <gst/video/video-overlay-composition.h>
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_GDK_PIXBUF_OVERLAY (gst_gdk_pixbuf_overlay_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstGdkPixbufOverlay, gst_gdk_pixbuf_overlay,
|
||||
GST, GDK_PIXBUF_OVERLAY, GstVideoFilter)
|
||||
|
||||
typedef enum {
|
||||
GST_GDK_PIXBUF_POSITIONING_PIXELS_RELATIVE_TO_EDGES,
|
||||
GST_GDK_PIXBUF_POSITIONING_PIXELS_ABSOLUTE
|
||||
} GstGdkPixbufPositioningMode;
|
||||
|
||||
/**
|
||||
* GstGdkPixbufOverlay:
|
||||
*
|
||||
* The opaque element instance structure.
|
||||
*/
|
||||
struct _GstGdkPixbufOverlay
|
||||
{
|
||||
GstVideoFilter videofilter;
|
||||
|
||||
/* properties */
|
||||
gchar * location;
|
||||
|
||||
/* pixbuf set via pixbuf property */
|
||||
GdkPixbuf * pixbuf;
|
||||
|
||||
gint offset_x;
|
||||
gint offset_y;
|
||||
|
||||
gdouble relative_x;
|
||||
gdouble relative_y;
|
||||
|
||||
gdouble coef_x;
|
||||
gdouble coef_y;
|
||||
|
||||
GstGdkPixbufPositioningMode positioning_mode;
|
||||
|
||||
gint overlay_width;
|
||||
gint overlay_height;
|
||||
|
||||
gdouble alpha;
|
||||
|
||||
/* the loaded image, as BGRA/ARGB pixels, with GstVideoMeta */
|
||||
GstBuffer * pixels; /* OBJECT_LOCK */
|
||||
|
||||
GstVideoOverlayComposition * comp;
|
||||
|
||||
/* render position or dimension has changed */
|
||||
gboolean update_composition;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
46
ext/gdk_pixbuf/gstgdkpixbufplugin.c
Normal file
46
ext/gdk_pixbuf/gstgdkpixbufplugin.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/* GStreamer GdkPixbuf plugin
|
||||
* Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2003 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstgdkpixbufelements.h"
|
||||
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (gdkpixbufdec, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (gdkpixbufoverlay, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (gdkpixbufsink, plugin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
gdkpixbuf,
|
||||
"GdkPixbuf-based image decoder, overlay and sink",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
430
ext/gdk_pixbuf/gstgdkpixbufsink.c
Normal file
430
ext/gdk_pixbuf/gstgdkpixbufsink.c
Normal file
|
@ -0,0 +1,430 @@
|
|||
/* GStreamer GdkPixbuf sink
|
||||
* Copyright (C) 2006-2008 Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gdkpixbufsink
|
||||
* @title: gdkpixbufsink
|
||||
*
|
||||
* This sink element takes RGB or RGBA images as input and wraps them into
|
||||
* #GdkPixbuf objects, for easy saving to file via the
|
||||
* GdkPixbuf library API or displaying in Gtk+ applications (e.g. using
|
||||
* the #GtkImage widget).
|
||||
*
|
||||
* There are two ways to use this element and obtain the #GdkPixbuf objects
|
||||
* created:
|
||||
*
|
||||
* * Watching for element messages named `preroll-pixbuf` or `pixbuf` on the bus, which
|
||||
* will be posted whenever an image would usually be rendered. See below for
|
||||
* more details on these messages and how to extract the pixbuf object
|
||||
* contained in them.
|
||||
*
|
||||
* * Retrieving the current pixbuf via the #GstGdkPixbufSink:last-pixbuf property
|
||||
* when needed. This is the easiest way to get at pixbufs for snapshotting
|
||||
* purposes - just wait until the pipeline is prerolled (ASYNC_DONE message
|
||||
* on the bus), then read the property. If you use this method, you may want
|
||||
* to disable message posting by setting the #GstGdkPixbufSink:post-messages
|
||||
* property to %FALSE. This avoids unnecessary memory overhead.
|
||||
*
|
||||
* The primary purpose of this element is to abstract away the #GstBuffer to
|
||||
* #GdkPixbuf conversion. Other than that it's very similar to the fakesink
|
||||
* element.
|
||||
*
|
||||
* This element is meant for easy no-hassle video snapshotting. It is not
|
||||
* suitable for video playback or video display at high framerates. Use
|
||||
* ximagesink, xvimagesink or some other suitable video sink in connection
|
||||
* with the #GstVideoOverlay interface instead if you want to do video playback.
|
||||
*
|
||||
* ## Message details
|
||||
*
|
||||
* As mentioned above, this element will by default post element messages
|
||||
* containing structures named `preroll-pixbuf`
|
||||
* ` or `pixbuf` on the bus (this
|
||||
* can be disabled by setting the #GstGdkPixbufSink:post-messages property
|
||||
* to %FALSE though). The element message structure has the following fields:
|
||||
*
|
||||
* * `pixbuf`: the #GdkPixbuf object
|
||||
* * `pixel-aspect-ratio`: the pixel aspect ratio (PAR) of the input image
|
||||
* (this field contains a value of type #GST_TYPE_FRACTION); the
|
||||
* PAR is usually 1:1 for images, but is often something non-1:1 in the case
|
||||
* of video input. In this case the image may be distorted and you may need
|
||||
* to rescale it accordingly before saving it to file or displaying it. This
|
||||
* can easily be done using gdk_pixbuf_scale() (the reason this is not done
|
||||
* automatically is that the application will often scale the image anyway
|
||||
* according to the size of the output window, in which case it is much more
|
||||
* efficient to only scale once rather than twice). You can put a videoscale
|
||||
* element and a capsfilter element with
|
||||
* `video/x-raw-rgb,pixel-aspect-ratio=(fraction)1/1` caps
|
||||
* in front of this element to make sure the pixbufs always have a 1:1 PAR.
|
||||
*
|
||||
* ## Example pipeline
|
||||
* |[
|
||||
* gst-launch-1.0 -m -v videotestsrc num-buffers=1 ! gdkpixbufsink
|
||||
* ]| Process one single test image as pixbuf (note that the output you see will
|
||||
* be slightly misleading. The message structure does contain a valid pixbuf
|
||||
* object even if the structure string says '(NULL)').
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstgdkpixbufelements.h"
|
||||
#include "gstgdkpixbufsink.h"
|
||||
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#define DEFAULT_SEND_MESSAGES TRUE
|
||||
#define DEFAULT_POST_MESSAGES TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_POST_MESSAGES,
|
||||
PROP_LAST_PIXBUF,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
|
||||
G_DEFINE_TYPE (GstGdkPixbufSink, gst_gdk_pixbuf_sink, GST_TYPE_VIDEO_SINK);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (gdkpixbufsink, "gdkpixbufsink",
|
||||
GST_RANK_NONE, GST_TYPE_GDK_PIXBUF_SINK, gdk_pixbuf_element_init (plugin));
|
||||
|
||||
static void gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static gboolean gst_gdk_pixbuf_sink_start (GstBaseSink * basesink);
|
||||
static gboolean gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink);
|
||||
static gboolean gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn gst_gdk_pixbuf_sink_render (GstBaseSink * bsink,
|
||||
GstBuffer * buf);
|
||||
static GstFlowReturn gst_gdk_pixbuf_sink_preroll (GstBaseSink * bsink,
|
||||
GstBuffer * buf);
|
||||
static GdkPixbuf *gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink *
|
||||
sink, GstBuffer * buf);
|
||||
|
||||
static GstStaticPadTemplate pixbufsink_sink_factory =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB") ";"
|
||||
GST_VIDEO_CAPS_MAKE ("RGBA"))
|
||||
);
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_sink_class_init (GstGdkPixbufSinkClass * klass)
|
||||
{
|
||||
GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gst_element_class_set_static_metadata (element_class, "GdkPixbuf sink",
|
||||
"Sink/Video", "Output images as GdkPixbuf objects in bus messages",
|
||||
"Tim-Philipp Müller <tim centricular net>");
|
||||
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&pixbufsink_sink_factory);
|
||||
|
||||
gobject_class->set_property = gst_gdk_pixbuf_sink_set_property;
|
||||
gobject_class->get_property = gst_gdk_pixbuf_sink_get_property;
|
||||
|
||||
/**
|
||||
* GstGdkPixbuf:post-messages:
|
||||
*
|
||||
* Post messages on the bus containing pixbufs.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
|
||||
g_param_spec_boolean ("post-messages", "Post Messages",
|
||||
"Whether to post messages containing pixbufs on the bus",
|
||||
DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_LAST_PIXBUF,
|
||||
g_param_spec_object ("last-pixbuf", "Last Pixbuf",
|
||||
"Last GdkPixbuf object rendered", GDK_TYPE_PIXBUF,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
basesink_class->start = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_start);
|
||||
basesink_class->stop = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_stop);
|
||||
basesink_class->render = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_render);
|
||||
basesink_class->preroll = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_preroll);
|
||||
basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_gdk_pixbuf_sink_set_caps);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_sink_init (GstGdkPixbufSink * sink)
|
||||
{
|
||||
sink->par_n = 0;
|
||||
sink->par_d = 0;
|
||||
sink->has_alpha = FALSE;
|
||||
sink->last_pixbuf = NULL;
|
||||
sink->post_messages = DEFAULT_POST_MESSAGES;
|
||||
|
||||
/* we're not a real video sink, we just derive from GstVideoSink in case
|
||||
* anything interesting is added to it in future */
|
||||
gst_base_sink_set_max_lateness (GST_BASE_SINK (sink), -1);
|
||||
gst_base_sink_set_qos_enabled (GST_BASE_SINK (sink), FALSE);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_sink_start (GstBaseSink * basesink)
|
||||
{
|
||||
GST_LOG_OBJECT (basesink, "start");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_sink_stop (GstBaseSink * basesink)
|
||||
{
|
||||
GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
|
||||
|
||||
GST_VIDEO_SINK_WIDTH (sink) = 0;
|
||||
GST_VIDEO_SINK_HEIGHT (sink) = 0;
|
||||
|
||||
sink->par_n = 0;
|
||||
sink->par_d = 0;
|
||||
sink->has_alpha = FALSE;
|
||||
|
||||
if (sink->last_pixbuf) {
|
||||
g_object_unref (sink->last_pixbuf);
|
||||
sink->last_pixbuf = NULL;
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (sink, "stop");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gdk_pixbuf_sink_set_caps (GstBaseSink * basesink, GstCaps * caps)
|
||||
{
|
||||
GstGdkPixbufSink *sink = GST_GDK_PIXBUF_SINK (basesink);
|
||||
GstVideoInfo info;
|
||||
GstVideoFormat fmt;
|
||||
gint w, h, par_n, par_d;
|
||||
|
||||
GST_LOG_OBJECT (sink, "caps: %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps)) {
|
||||
GST_WARNING_OBJECT (sink, "parse_caps failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fmt = GST_VIDEO_INFO_FORMAT (&info);
|
||||
w = GST_VIDEO_INFO_WIDTH (&info);
|
||||
h = GST_VIDEO_INFO_HEIGHT (&info);
|
||||
par_n = GST_VIDEO_INFO_PAR_N (&info);
|
||||
par_d = GST_VIDEO_INFO_PAR_N (&info);
|
||||
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
{
|
||||
gint s;
|
||||
s = GST_VIDEO_INFO_COMP_PSTRIDE (&info, 0);
|
||||
g_assert ((fmt == GST_VIDEO_FORMAT_RGB && s == 3) ||
|
||||
(fmt == GST_VIDEO_FORMAT_RGBA && s == 4));
|
||||
}
|
||||
#endif
|
||||
|
||||
GST_VIDEO_SINK_WIDTH (sink) = w;
|
||||
GST_VIDEO_SINK_HEIGHT (sink) = h;
|
||||
|
||||
sink->par_n = par_n;
|
||||
sink->par_d = par_d;
|
||||
|
||||
sink->has_alpha = GST_VIDEO_INFO_HAS_ALPHA (&info);
|
||||
|
||||
GST_INFO_OBJECT (sink, "format : %d", fmt);
|
||||
GST_INFO_OBJECT (sink, "width x height : %d x %d", w, h);
|
||||
GST_INFO_OBJECT (sink, "pixel-aspect-ratio : %d/%d", par_n, par_d);
|
||||
|
||||
sink->info = info;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_sink_pixbuf_destroy_notify (guchar * pixels,
|
||||
GstVideoFrame * frame)
|
||||
{
|
||||
gst_video_frame_unmap (frame);
|
||||
gst_buffer_unref (frame->buffer);
|
||||
g_slice_free (GstVideoFrame, frame);
|
||||
}
|
||||
|
||||
static GdkPixbuf *
|
||||
gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (GstGdkPixbufSink * sink,
|
||||
GstBuffer * buf)
|
||||
{
|
||||
GdkPixbuf *pix = NULL;
|
||||
GstVideoFrame *frame;
|
||||
gint minsize, bytes_per_pixel;
|
||||
|
||||
g_return_val_if_fail (GST_VIDEO_SINK_WIDTH (sink) > 0, NULL);
|
||||
g_return_val_if_fail (GST_VIDEO_SINK_HEIGHT (sink) > 0, NULL);
|
||||
|
||||
frame = g_slice_new0 (GstVideoFrame);
|
||||
gst_video_frame_map (frame, &sink->info, buf, GST_MAP_READ);
|
||||
|
||||
bytes_per_pixel = (sink->has_alpha) ? 4 : 3;
|
||||
|
||||
/* last row needn't have row padding */
|
||||
minsize = (GST_VIDEO_FRAME_COMP_STRIDE (frame, 0) *
|
||||
(GST_VIDEO_SINK_HEIGHT (sink) - 1)) +
|
||||
(bytes_per_pixel * GST_VIDEO_SINK_WIDTH (sink));
|
||||
|
||||
g_return_val_if_fail (gst_buffer_get_size (buf) >= minsize, NULL);
|
||||
|
||||
gst_buffer_ref (buf);
|
||||
pix = gdk_pixbuf_new_from_data (GST_VIDEO_FRAME_COMP_DATA (frame, 0),
|
||||
GDK_COLORSPACE_RGB, sink->has_alpha, 8, GST_VIDEO_SINK_WIDTH (sink),
|
||||
GST_VIDEO_SINK_HEIGHT (sink), GST_VIDEO_FRAME_COMP_STRIDE (frame, 0),
|
||||
(GdkPixbufDestroyNotify) gst_gdk_pixbuf_sink_pixbuf_destroy_notify,
|
||||
frame);
|
||||
|
||||
return pix;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_sink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf,
|
||||
const gchar * msg_name)
|
||||
{
|
||||
GstGdkPixbufSink *sink;
|
||||
GdkPixbuf *pixbuf;
|
||||
gboolean do_post;
|
||||
|
||||
sink = GST_GDK_PIXBUF_SINK (basesink);
|
||||
|
||||
pixbuf = gst_gdk_pixbuf_sink_get_pixbuf_from_buffer (sink, buf);
|
||||
|
||||
GST_OBJECT_LOCK (sink);
|
||||
|
||||
do_post = sink->post_messages;
|
||||
|
||||
if (sink->last_pixbuf)
|
||||
g_object_unref (sink->last_pixbuf);
|
||||
|
||||
sink->last_pixbuf = pixbuf; /* take ownership */
|
||||
|
||||
GST_OBJECT_UNLOCK (sink);
|
||||
|
||||
if (G_UNLIKELY (pixbuf == NULL))
|
||||
goto error;
|
||||
|
||||
if (do_post) {
|
||||
GstStructure *s;
|
||||
GstMessage *msg;
|
||||
GstFormat format;
|
||||
GstClockTime timestamp;
|
||||
GstClockTime running_time, stream_time;
|
||||
|
||||
GstSegment *segment = &basesink->segment;
|
||||
format = segment->format;
|
||||
|
||||
timestamp = GST_BUFFER_PTS (buf);
|
||||
running_time = gst_segment_to_running_time (segment, format, timestamp);
|
||||
stream_time = gst_segment_to_stream_time (segment, format, timestamp);
|
||||
|
||||
/* it's okay to keep using pixbuf here, we can be sure no one is going to
|
||||
* unref or change sink->last_pixbuf before we return from this function.
|
||||
* The structure will take its own ref to the pixbuf. */
|
||||
s = gst_structure_new (msg_name,
|
||||
"pixbuf", GDK_TYPE_PIXBUF, pixbuf,
|
||||
"pixel-aspect-ratio", GST_TYPE_FRACTION, sink->par_n, sink->par_d,
|
||||
"timestamp", G_TYPE_UINT64, timestamp,
|
||||
"stream-time", G_TYPE_UINT64, stream_time,
|
||||
"running-time", G_TYPE_UINT64, running_time, NULL);
|
||||
|
||||
msg = gst_message_new_element (GST_OBJECT_CAST (sink), s);
|
||||
gst_element_post_message (GST_ELEMENT_CAST (sink), msg);
|
||||
}
|
||||
|
||||
g_object_notify (G_OBJECT (sink), "last-pixbuf");
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
/* ERRORS */
|
||||
error:
|
||||
{
|
||||
/* This shouldn't really happen */
|
||||
GST_ELEMENT_ERROR (sink, LIBRARY, FAILED,
|
||||
("Couldn't create pixbuf from RGB image."),
|
||||
("Probably not enough free memory"));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_sink_preroll (GstBaseSink * basesink, GstBuffer * buf)
|
||||
{
|
||||
return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "preroll-pixbuf");
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gdk_pixbuf_sink_render (GstBaseSink * basesink, GstBuffer * buf)
|
||||
{
|
||||
return gst_gdk_pixbuf_sink_handle_buffer (basesink, buf, "pixbuf");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGdkPixbufSink *sink;
|
||||
|
||||
sink = GST_GDK_PIXBUF_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_POST_MESSAGES:
|
||||
GST_OBJECT_LOCK (sink);
|
||||
sink->post_messages = g_value_get_boolean (value);
|
||||
GST_OBJECT_UNLOCK (sink);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gdk_pixbuf_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGdkPixbufSink *sink;
|
||||
|
||||
sink = GST_GDK_PIXBUF_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_POST_MESSAGES:
|
||||
GST_OBJECT_LOCK (sink);
|
||||
g_value_set_boolean (value, sink->post_messages);
|
||||
GST_OBJECT_UNLOCK (sink);
|
||||
break;
|
||||
case PROP_LAST_PIXBUF:
|
||||
GST_OBJECT_LOCK (sink);
|
||||
g_value_set_object (value, sink->last_pixbuf);
|
||||
GST_OBJECT_UNLOCK (sink);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
58
ext/gdk_pixbuf/gstgdkpixbufsink.h
Normal file
58
ext/gdk_pixbuf/gstgdkpixbufsink.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* GStreamer GdkPixbuf sink
|
||||
* Copyright (C) 2006-2008 Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef GST_GDK_PIXBUF_SINK_H
|
||||
#define GST_GDK_PIXBUF_SINK_H
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
#define GST_TYPE_GDK_PIXBUF_SINK (gst_gdk_pixbuf_sink_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstGdkPixbufSink, gst_gdk_pixbuf_sink,
|
||||
GST, GDK_PIXBUF_SINK, GstVideoSink)
|
||||
|
||||
/**
|
||||
* GstGdkPixbufSink:
|
||||
*
|
||||
* Opaque element structure.
|
||||
*/
|
||||
struct _GstGdkPixbufSink
|
||||
{
|
||||
GstVideoSink basesink;
|
||||
|
||||
/*< private >*/
|
||||
|
||||
/* current caps */
|
||||
GstVideoInfo info;
|
||||
gint width;
|
||||
gint height;
|
||||
gint par_n;
|
||||
gint par_d;
|
||||
gboolean has_alpha;
|
||||
|
||||
/* properties */
|
||||
gboolean post_messages;
|
||||
GdkPixbuf * last_pixbuf;
|
||||
};
|
||||
|
||||
#endif /* GST_GDK_PIXBUF_SINK_H */
|
||||
|
23
ext/gdk_pixbuf/meson.build
Normal file
23
ext/gdk_pixbuf/meson.build
Normal file
|
@ -0,0 +1,23 @@
|
|||
pixbuf_sources = [
|
||||
'gstgdkpixbufdec.c',
|
||||
'gstgdkpixbufoverlay.c',
|
||||
'gstgdkpixbufelement.c',
|
||||
'gstgdkpixbufplugin.c',
|
||||
'gstgdkpixbufsink.c',
|
||||
]
|
||||
|
||||
gdkpixbuf_dep = dependency('gdk-pixbuf-2.0', version : '>=2.8.0', required : get_option('gdk-pixbuf'))
|
||||
|
||||
if gdkpixbuf_dep.found()
|
||||
gstgdkpixbuf = library('gstgdkpixbuf',
|
||||
pixbuf_sources,
|
||||
c_args : gst_plugins_good_args,
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [gstbase_dep, gstvideo_dep, gstcontroller_dep, gdkpixbuf_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstgdkpixbuf, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstgdkpixbuf]
|
||||
endif
|
510
ext/gtk/gstgtkbasesink.c
Normal file
510
ext/gtk/gstgtkbasesink.c
Normal file
|
@ -0,0 +1,510 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:gtkgstsink
|
||||
* @title: GstGtkBaseSink
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstgtkbasesink.h"
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_base_sink
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_ALPHA TRUE
|
||||
|
||||
static void gst_gtk_base_sink_finalize (GObject * object);
|
||||
static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * param_spec);
|
||||
static void gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * param_spec);
|
||||
|
||||
static gboolean gst_gtk_base_sink_start (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_base_sink_stop (GstBaseSink * bsink);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gtk_base_sink_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static void gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end);
|
||||
static gboolean gst_gtk_base_sink_set_caps (GstBaseSink * bsink,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn gst_gtk_base_sink_show_frame (GstVideoSink * bsink,
|
||||
GstBuffer * buf);
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDGET,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_ALPHA,
|
||||
};
|
||||
|
||||
#define gst_gtk_base_sink_parent_class parent_class
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
|
||||
gst_gtk_base_sink_navigation_interface_init);
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink,
|
||||
"gtkbasesink", 0, "Gtk Video Sink base class"));
|
||||
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstVideoSinkClass *gstvideosink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstvideosink_class = (GstVideoSinkClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_gtk_base_sink_set_property;
|
||||
gobject_class->get_property = gst_gtk_base_sink_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_WIDGET,
|
||||
g_param_spec_object ("widget", "Gtk Widget",
|
||||
"The GtkWidget to place in the widget hierarchy "
|
||||
"(must only be get from the GTK main thread)",
|
||||
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
|
||||
g_param_spec_boolean ("force-aspect-ratio",
|
||||
"Force aspect ratio",
|
||||
"When enabled, scaling will respect original aspect ratio",
|
||||
DEFAULT_FORCE_ASPECT_RATIO,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
|
||||
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
|
||||
"The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
|
||||
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA,
|
||||
g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
|
||||
"When enabled, alpha will be ignored and converted to black",
|
||||
DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gobject_class->finalize = gst_gtk_base_sink_finalize;
|
||||
|
||||
gstelement_class->change_state = gst_gtk_base_sink_change_state;
|
||||
gstbasesink_class->set_caps = gst_gtk_base_sink_set_caps;
|
||||
gstbasesink_class->get_times = gst_gtk_base_sink_get_times;
|
||||
gstbasesink_class->start = gst_gtk_base_sink_start;
|
||||
gstbasesink_class->stop = gst_gtk_base_sink_stop;
|
||||
|
||||
gstvideosink_class->show_frame = gst_gtk_base_sink_show_frame;
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_GTK_BASE_SINK, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
gtk_sink->par_n = DEFAULT_PAR_N;
|
||||
gtk_sink->par_d = DEFAULT_PAR_D;
|
||||
gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_finalize (GObject * object)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->window && gtk_sink->window_destroy_id)
|
||||
g_signal_handler_disconnect (gtk_sink->window, gtk_sink->window_destroy_id);
|
||||
if (gtk_sink->widget && gtk_sink->widget_destroy_id)
|
||||
g_signal_handler_disconnect (gtk_sink->widget, gtk_sink->widget_destroy_id);
|
||||
|
||||
g_clear_object (>k_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
widget_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
g_clear_object (>k_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
}
|
||||
|
||||
static void
|
||||
window_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
gtk_sink->window = NULL;
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
}
|
||||
|
||||
static GtkGstBaseWidget *
|
||||
gst_gtk_base_sink_get_widget (GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
if (gtk_sink->widget != NULL)
|
||||
return gtk_sink->widget;
|
||||
|
||||
/* Ensure GTK is initialized, this has no side effect if it was already
|
||||
* initialized. Also, we do that lazily, so the application can be first */
|
||||
if (!gtk_init_check (NULL, NULL)) {
|
||||
GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_assert (GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget);
|
||||
gtk_sink->widget = (GtkGstBaseWidget *)
|
||||
GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget ();
|
||||
|
||||
gtk_sink->bind_aspect_ratio =
|
||||
g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget,
|
||||
"force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
gtk_sink->bind_pixel_aspect_ratio =
|
||||
g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget,
|
||||
"pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
gtk_sink->bind_ignore_alpha =
|
||||
g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget,
|
||||
"ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
|
||||
/* Take the floating ref, other wise the destruction of the container will
|
||||
* make this widget disappear possibly before we are done. */
|
||||
gst_object_ref_sink (gtk_sink->widget);
|
||||
gtk_sink->widget_destroy_id = g_signal_connect (gtk_sink->widget, "destroy",
|
||||
G_CALLBACK (widget_destroy_cb), gtk_sink);
|
||||
|
||||
/* back pointer */
|
||||
gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (gtk_sink->widget),
|
||||
GST_ELEMENT (gtk_sink));
|
||||
|
||||
return gtk_sink->widget;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDGET:
|
||||
{
|
||||
GObject *widget = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->widget != NULL)
|
||||
widget = G_OBJECT (gtk_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (!widget)
|
||||
widget =
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget,
|
||||
gtk_sink);
|
||||
|
||||
g_value_set_object (value, widget);
|
||||
break;
|
||||
}
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
g_value_set_boolean (value, gtk_sink->ignore_alpha);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gtk_sink->par_n = gst_value_get_fraction_numerator (value);
|
||||
gtk_sink->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
gtk_sink->ignore_alpha = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_send_event (GstNavigation * navigation,
|
||||
GstStructure * structure)
|
||||
{
|
||||
GstGtkBaseSink *sink = GST_GTK_BASE_SINK (navigation);
|
||||
GstEvent *event;
|
||||
GstPad *pad;
|
||||
gdouble x, y;
|
||||
|
||||
if (gst_structure_get_double (structure, "pointer_x", &x) &&
|
||||
gst_structure_get_double (structure, "pointer_y", &y)) {
|
||||
GtkGstBaseWidget *widget = gst_gtk_base_sink_get_widget (sink);
|
||||
gdouble stream_x, stream_y;
|
||||
|
||||
gtk_gst_base_widget_display_size_to_stream_size (widget,
|
||||
x, y, &stream_x, &stream_y);
|
||||
gst_structure_set (structure,
|
||||
"pointer_x", G_TYPE_DOUBLE, (gdouble) stream_x,
|
||||
"pointer_y", G_TYPE_DOUBLE, (gdouble) stream_y, NULL);
|
||||
}
|
||||
|
||||
event = gst_event_new_navigation (structure);
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure);
|
||||
|
||||
if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
|
||||
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
|
||||
/* If upstream didn't handle the event we'll post a message with it
|
||||
* for the application in case it wants to do something with it */
|
||||
gst_element_post_message (GST_ELEMENT_CAST (sink),
|
||||
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
|
||||
}
|
||||
gst_event_unref (event);
|
||||
gst_object_unref (pad);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface)
|
||||
{
|
||||
iface->send_event = gst_gtk_base_sink_navigation_send_event;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_start_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink);
|
||||
GtkWidget *toplevel;
|
||||
|
||||
if (gst_gtk_base_sink_get_widget (gst_sink) == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* After this point, gtk_sink->widget will always be set */
|
||||
|
||||
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_sink->widget));
|
||||
if (!gtk_widget_is_toplevel (toplevel)) {
|
||||
/* sanity check */
|
||||
g_assert (klass->window_title);
|
||||
|
||||
/* User did not add widget its own UI, let's popup a new GtkWindow to
|
||||
* make gst-launch-1.0 work. */
|
||||
gst_sink->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480);
|
||||
gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title);
|
||||
gtk_container_add (GTK_CONTAINER (gst_sink->window), toplevel);
|
||||
gst_sink->window_destroy_id = g_signal_connect (gst_sink->window, "destroy",
|
||||
G_CALLBACK (window_destroy_cb), gst_sink);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_start (GstBaseSink * bsink)
|
||||
{
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc)
|
||||
gst_gtk_base_sink_start_on_main, bsink);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (gst_sink->window) {
|
||||
gtk_widget_destroy (gst_sink->window);
|
||||
gst_sink->window = NULL;
|
||||
gst_sink->widget = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_stop (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (gst_sink->window)
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc)
|
||||
gst_gtk_base_sink_stop_on_main, bsink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
|
||||
{
|
||||
gtk_widget_show_all (widget);
|
||||
g_object_unref (widget);
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (element);
|
||||
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
||||
|
||||
GST_DEBUG_OBJECT (element, "changing state: %s => %s",
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
{
|
||||
GtkWindow *window = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->window)
|
||||
window = g_object_ref (GTK_WINDOW (gtk_sink->window));
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (window)
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
|
||||
window);
|
||||
|
||||
break;
|
||||
}
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->widget)
|
||||
gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink;
|
||||
|
||||
gtk_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
|
||||
*start = GST_BUFFER_TIMESTAMP (buf);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
||||
*end = *start + GST_BUFFER_DURATION (buf);
|
||||
else {
|
||||
if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) {
|
||||
*end = *start +
|
||||
gst_util_uint64_scale_int (GST_SECOND,
|
||||
GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
|
||||
GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_gtk_base_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (!gst_video_info_from_caps (>k_sink->v_info, caps))
|
||||
return FALSE;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
|
||||
if (gtk_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_gst_base_widget_set_format (gtk_sink->widget, >k_sink->v_info)) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
return FALSE;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink;
|
||||
|
||||
GST_TRACE ("rendering buffer:%p", buf);
|
||||
|
||||
gtk_sink = GST_GTK_BASE_SINK (vsink);
|
||||
|
||||
GST_OBJECT_LOCK (vsink);
|
||||
|
||||
if (gtk_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf);
|
||||
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
96
ext/gtk/gstgtkbasesink.h
Normal file
96
ext/gtk/gstgtkbasesink.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_BASE_SINK_H__
|
||||
#define __GST_GTK_BASE_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
#define GST_TYPE_GTK_BASE_SINK (gst_gtk_base_sink_get_type())
|
||||
#define GST_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSink))
|
||||
#define GST_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSinkClass))
|
||||
#define GST_GTK_BASE_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GTK_BASE_SINK, GstGtkBaseSinkClass))
|
||||
#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_BASE_SINK))
|
||||
#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_BASE_SINK))
|
||||
#define GST_GTK_BASE_SINK_CAST(obj) ((GstGtkBaseSink*)(obj))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GstGtkBaseSink GstGtkBaseSink;
|
||||
typedef struct _GstGtkBaseSinkClass GstGtkBaseSinkClass;
|
||||
|
||||
GType gst_gtk_base_sink_get_type (void);
|
||||
|
||||
/**
|
||||
* GstGtkBaseSink:
|
||||
*
|
||||
* Opaque #GstGtkBaseSink object
|
||||
*/
|
||||
struct _GstGtkBaseSink
|
||||
{
|
||||
/* <private> */
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo v_info;
|
||||
|
||||
GtkGstBaseWidget *widget;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
GBinding *bind_aspect_ratio;
|
||||
|
||||
gint par_n;
|
||||
gint par_d;
|
||||
GBinding *bind_pixel_aspect_ratio;
|
||||
|
||||
gboolean ignore_alpha;
|
||||
GBinding *bind_ignore_alpha;
|
||||
|
||||
GtkWidget *window;
|
||||
gulong widget_destroy_id;
|
||||
gulong window_destroy_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* GstGtkBaseSinkClass:
|
||||
*
|
||||
* The #GstGtkBaseSinkClass struct only contains private data
|
||||
*/
|
||||
struct _GstGtkBaseSinkClass
|
||||
{
|
||||
GstVideoSinkClass object_class;
|
||||
|
||||
/* metadata */
|
||||
const gchar *window_title;
|
||||
|
||||
/* virtuals */
|
||||
GtkWidget* (*create_widget) (void);
|
||||
};
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstGtkBaseSink, gst_object_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GTK_BASE_SINK_H__ */
|
367
ext/gtk/gstgtkglsink.c
Normal file
367
ext/gtk/gstgtkglsink.c
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gtkglsink
|
||||
* @title: gtkglsink
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#include "gstgtkglsink.h"
|
||||
#include "gtkgstglwidget.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_gl_sink
|
||||
|
||||
static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
|
||||
static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
|
||||
GstQuery * query);
|
||||
static GstCaps *gst_gtk_gl_sink_get_caps (GstBaseSink * bsink,
|
||||
GstCaps * filter);
|
||||
|
||||
static void gst_gtk_gl_sink_finalize (GObject * object);
|
||||
|
||||
static GstStaticPadTemplate gst_gtk_gl_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; "
|
||||
GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", "
|
||||
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA")));
|
||||
|
||||
#define gst_gtk_gl_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink,
|
||||
GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
|
||||
"gtkglsink", 0, "Gtk GL Video Sink"));
|
||||
GST_ELEMENT_REGISTER_DEFINE (gtkglsink, "gtkglsink", GST_RANK_NONE,
|
||||
GST_TYPE_GTK_GL_SINK);
|
||||
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstGtkBaseSinkClass *gstgtkbasesink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass;
|
||||
|
||||
gobject_class->finalize = gst_gtk_gl_sink_finalize;
|
||||
|
||||
gstbasesink_class->query = gst_gtk_gl_sink_query;
|
||||
gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation;
|
||||
gstbasesink_class->start = gst_gtk_gl_sink_start;
|
||||
gstbasesink_class->stop = gst_gtk_gl_sink_stop;
|
||||
gstbasesink_class->get_caps = gst_gtk_gl_sink_get_caps;
|
||||
|
||||
gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new;
|
||||
gstgtkbasesink_class->window_title = "Gtk+ GL renderer";
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class, "Gtk GL Video Sink",
|
||||
"Sink/Video", "A video sink that renders to a GtkWidget using OpenGL",
|
||||
"Matthew Waters <matthew@centricular.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_gtk_gl_sink_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
|
||||
{
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
gboolean res = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CONTEXT:
|
||||
{
|
||||
if (gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
|
||||
gtk_sink->display, gtk_sink->context, gtk_sink->gtk_context))
|
||||
return TRUE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
_size_changed_cb (GtkWidget * widget, GdkRectangle * rectangle,
|
||||
GstGtkGLSink * gtk_sink)
|
||||
{
|
||||
gint scale_factor, width, height;
|
||||
gboolean reconfigure;
|
||||
|
||||
scale_factor = gtk_widget_get_scale_factor (widget);
|
||||
width = scale_factor * gtk_widget_get_allocated_width (widget);
|
||||
height = scale_factor * gtk_widget_get_allocated_height (widget);
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
reconfigure =
|
||||
(width != gtk_sink->display_width || height != gtk_sink->display_height);
|
||||
gtk_sink->display_width = width;
|
||||
gtk_sink->display_height = height;
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (reconfigure) {
|
||||
GST_DEBUG_OBJECT (gtk_sink, "Sending reconfigure event on sinkpad.");
|
||||
gst_pad_push_event (GST_BASE_SINK (gtk_sink)->sinkpad,
|
||||
gst_event_new_reconfigure ());
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink)
|
||||
{
|
||||
if (gtk_sink->size_allocate_sig_handler) {
|
||||
g_signal_handler_disconnect (widget, gtk_sink->size_allocate_sig_handler);
|
||||
gtk_sink->size_allocate_sig_handler = 0;
|
||||
}
|
||||
|
||||
if (gtk_sink->widget_destroy_sig_handler) {
|
||||
g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler);
|
||||
gtk_sink->widget_destroy_sig_handler = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_start (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink);
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
GtkGstGLWidget *gst_widget;
|
||||
|
||||
if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink))
|
||||
return FALSE;
|
||||
|
||||
/* After this point, gtk_sink->widget will always be set */
|
||||
gst_widget = GTK_GST_GL_WIDGET (base_sink->widget);
|
||||
|
||||
/* Track the allocation size */
|
||||
gtk_sink->size_allocate_sig_handler =
|
||||
g_signal_connect (gst_widget, "size-allocate",
|
||||
G_CALLBACK (_size_changed_cb), gtk_sink);
|
||||
|
||||
gtk_sink->widget_destroy_sig_handler =
|
||||
g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb),
|
||||
gtk_sink);
|
||||
|
||||
_size_changed_cb (GTK_WIDGET (gst_widget), NULL, gtk_sink);
|
||||
|
||||
if (!gtk_gst_gl_widget_init_winsys (gst_widget)) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to initialize OpenGL with Gtk"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget);
|
||||
gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget);
|
||||
gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget);
|
||||
|
||||
if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to retrieve OpenGL context from Gtk"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
|
||||
gtk_sink->display);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_stop (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
|
||||
if (gtk_sink->display) {
|
||||
gst_object_unref (gtk_sink->display);
|
||||
gtk_sink->display = NULL;
|
||||
}
|
||||
|
||||
if (gtk_sink->context) {
|
||||
gst_object_unref (gtk_sink->context);
|
||||
gtk_sink->context = NULL;
|
||||
}
|
||||
|
||||
if (gtk_sink->gtk_context) {
|
||||
gst_object_unref (gtk_sink->gtk_context);
|
||||
gtk_sink->gtk_context = NULL;
|
||||
}
|
||||
|
||||
return GST_BASE_SINK_CLASS (parent_class)->stop (bsink);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
GstBufferPool *pool = NULL;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
GstVideoInfo info;
|
||||
guint size;
|
||||
gboolean need_pool;
|
||||
GstStructure *allocation_meta = NULL;
|
||||
gint display_width, display_height;
|
||||
|
||||
if (!gtk_sink->display || !gtk_sink->context)
|
||||
return FALSE;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, &need_pool);
|
||||
|
||||
if (caps == NULL)
|
||||
goto no_caps;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
/* the normal size of a frame */
|
||||
size = info.size;
|
||||
|
||||
if (need_pool) {
|
||||
GST_DEBUG_OBJECT (gtk_sink, "create new pool");
|
||||
pool = gst_gl_buffer_pool_new (gtk_sink->context);
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
||||
gst_buffer_pool_config_add_option (config,
|
||||
GST_BUFFER_POOL_OPTION_GL_SYNC_META);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, config))
|
||||
goto config_failed;
|
||||
}
|
||||
|
||||
/* we need at least 2 buffer because we hold on to the last one */
|
||||
gst_query_add_allocation_pool (query, pool, size, 2, 0);
|
||||
if (pool)
|
||||
gst_object_unref (pool);
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
display_width = gtk_sink->display_width;
|
||||
display_height = gtk_sink->display_height;
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (display_width != 0 && display_height != 0) {
|
||||
GST_DEBUG_OBJECT (gtk_sink, "sending alloc query with size %dx%d",
|
||||
display_width, display_height);
|
||||
allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta",
|
||||
"width", G_TYPE_UINT, display_width,
|
||||
"height", G_TYPE_UINT, display_height, NULL);
|
||||
}
|
||||
|
||||
gst_query_add_allocation_meta (query,
|
||||
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta);
|
||||
|
||||
if (allocation_meta)
|
||||
gst_structure_free (allocation_meta);
|
||||
|
||||
/* we also support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
|
||||
|
||||
if (gtk_sink->context->gl_vtable->FenceSync)
|
||||
gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
invalid_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "invalid caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
config_failed:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "failed setting config");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
||||
{
|
||||
GstCaps *tmp = NULL;
|
||||
GstCaps *result = NULL;
|
||||
|
||||
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
||||
|
||||
if (filter) {
|
||||
GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT,
|
||||
filter);
|
||||
|
||||
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (tmp);
|
||||
} else {
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
result = gst_gl_overlay_compositor_add_caps (result);
|
||||
|
||||
GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_finalize (GObject * object)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
|
||||
GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
if (gtk_sink->size_allocate_sig_handler) {
|
||||
g_signal_handler_disconnect (base_sink->widget,
|
||||
gtk_sink->size_allocate_sig_handler);
|
||||
gtk_sink->size_allocate_sig_handler = 0;
|
||||
}
|
||||
|
||||
if (gtk_sink->widget_destroy_sig_handler) {
|
||||
g_signal_handler_disconnect (base_sink->widget,
|
||||
gtk_sink->widget_destroy_sig_handler);
|
||||
gtk_sink->widget_destroy_sig_handler = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
68
ext/gtk/gstgtkglsink.h
Normal file
68
ext/gtk/gstgtkglsink.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_GL_SINK_H__
|
||||
#define __GST_GTK_GL_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "gstgtkbasesink.h"
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_GTK_GL_SINK (gst_gtk_gl_sink_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstGtkGLSink, gst_gtk_gl_sink, GST, GTK_GL_SINK,
|
||||
GstGtkBaseSink)
|
||||
|
||||
/**
|
||||
* GstGtkGLSink:
|
||||
*
|
||||
* Opaque #GstGtkGLSink object
|
||||
*/
|
||||
struct _GstGtkGLSink
|
||||
{
|
||||
/* <private> */
|
||||
GstGtkBaseSink parent;
|
||||
|
||||
GstGLDisplay *display;
|
||||
GstGLContext *context;
|
||||
GstGLContext *gtk_context;
|
||||
|
||||
GstGLUpload *upload;
|
||||
GstBuffer *uploaded_buffer;
|
||||
|
||||
/* read/write with object lock */
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
gulong size_allocate_sig_handler;
|
||||
gulong widget_destroy_sig_handler;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (gtkglsink);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GTK_GL_SINK_H__ */
|
80
ext/gtk/gstgtksink.c
Normal file
80
ext/gtk/gstgtksink.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gtkgstsink
|
||||
* @title: gtkgstsink
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gtkgstwidget.h"
|
||||
#include "gstgtksink.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_sink
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define FORMATS "{ BGRx, BGRA }"
|
||||
#else
|
||||
#define FORMATS "{ xRGB, ARGB }"
|
||||
#endif
|
||||
|
||||
static GstStaticPadTemplate gst_gtk_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (FORMATS))
|
||||
);
|
||||
|
||||
#define gst_gtk_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstGtkSink, gst_gtk_sink, GST_TYPE_GTK_BASE_SINK,
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_sink, "gtksink", 0,
|
||||
"Gtk Video Sink"));
|
||||
GST_ELEMENT_REGISTER_DEFINE (gtksink, "gtksink", GST_RANK_NONE,
|
||||
GST_TYPE_GTK_SINK);
|
||||
|
||||
static void
|
||||
gst_gtk_sink_class_init (GstGtkSinkClass * klass)
|
||||
{
|
||||
GstElementClass *gstelement_class;
|
||||
GstGtkBaseSinkClass *base_class;
|
||||
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
base_class = (GstGtkBaseSinkClass *) klass;
|
||||
|
||||
base_class->create_widget = gtk_gst_widget_new;
|
||||
base_class->window_title = "Gtk+ Cairo renderer";
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class, "Gtk Video Sink",
|
||||
"Sink/Video", "A video sink that renders to a GtkWidget",
|
||||
"Matthew Waters <matthew@centricular.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_gtk_sink_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_sink_init (GstGtkSink * gtk_sink)
|
||||
{
|
||||
}
|
51
ext/gtk/gstgtksink.h
Normal file
51
ext/gtk/gstgtksink.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_SINK_H__
|
||||
#define __GST_GTK_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "gstgtkbasesink.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_GTK_SINK (gst_gtk_sink_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstGtkSink, gst_gtk_sink, GST, GTK_SINK, GstGtkBaseSink)
|
||||
|
||||
/**
|
||||
* GstGtkSink:
|
||||
*
|
||||
* Opaque #GstGtkSink object
|
||||
*/
|
||||
struct _GstGtkSink
|
||||
{
|
||||
/* <private> */
|
||||
GstGtkBaseSink parent;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (gtksink);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GTK_SINK_H__ */
|
71
ext/gtk/gstgtkutils.c
Normal file
71
ext/gtk/gstgtkutils.c
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
struct invoke_context
|
||||
{
|
||||
GThreadFunc func;
|
||||
gpointer data;
|
||||
GMutex lock;
|
||||
GCond cond;
|
||||
gboolean fired;
|
||||
|
||||
gpointer res;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
gst_gtk_invoke_func (struct invoke_context *info)
|
||||
{
|
||||
g_mutex_lock (&info->lock);
|
||||
info->res = info->func (info->data);
|
||||
info->fired = TRUE;
|
||||
g_cond_signal (&info->cond);
|
||||
g_mutex_unlock (&info->lock);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gpointer
|
||||
gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
|
||||
{
|
||||
GMainContext *main_context = g_main_context_default ();
|
||||
struct invoke_context info;
|
||||
|
||||
g_mutex_init (&info.lock);
|
||||
g_cond_init (&info.cond);
|
||||
info.fired = FALSE;
|
||||
info.func = func;
|
||||
info.data = data;
|
||||
|
||||
g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
|
||||
&info);
|
||||
|
||||
g_mutex_lock (&info.lock);
|
||||
while (!info.fired)
|
||||
g_cond_wait (&info.cond, &info.lock);
|
||||
g_mutex_unlock (&info.lock);
|
||||
|
||||
g_mutex_clear (&info.lock);
|
||||
g_cond_clear (&info.cond);
|
||||
|
||||
return info.res;
|
||||
}
|
29
ext/gtk/gstgtkutils.h
Normal file
29
ext/gtk/gstgtkutils.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_UTILS_H__
|
||||
#define __GST_GTK_UTILS_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
|
||||
|
||||
#endif /* __GST_GTK_UTILS_H__ */
|
49
ext/gtk/gstplugin.c
Normal file
49
ext/gtk/gstplugin.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstgtksink.h"
|
||||
#if defined(HAVE_GTK3_GL)
|
||||
#include "gstgtkglsink.h"
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (gtksink, plugin);
|
||||
|
||||
#if defined(HAVE_GTK3_GL)
|
||||
ret |= GST_ELEMENT_REGISTER (gtkglsink, plugin);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
gtk,
|
||||
"Gtk+ sink",
|
||||
plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME,
|
||||
GST_PACKAGE_ORIGIN)
|
535
ext/gtk/gtkgstbasewidget.c
Normal file
535
ext/gtk/gtkgstbasewidget.c
Normal file
|
@ -0,0 +1,535 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_base_widget
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_ALPHA TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_ALPHA,
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
|
||||
gint * natural)
|
||||
{
|
||||
GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
|
||||
gint video_width = gst_widget->display_width;
|
||||
|
||||
if (!gst_widget->negotiated)
|
||||
video_width = 10;
|
||||
|
||||
if (min)
|
||||
*min = 1;
|
||||
if (natural)
|
||||
*natural = video_width;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
|
||||
gint * natural)
|
||||
{
|
||||
GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
|
||||
gint video_height = gst_widget->display_height;
|
||||
|
||||
if (!gst_widget->negotiated)
|
||||
video_height = 10;
|
||||
|
||||
if (min)
|
||||
*min = 1;
|
||||
if (natural)
|
||||
*natural = video_height;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gtk_widget->par_n = gst_value_get_fraction_numerator (value);
|
||||
gtk_widget->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
gtk_widget->ignore_alpha = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
g_value_set_boolean (value, gtk_widget->ignore_alpha);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
|
||||
{
|
||||
gboolean ok;
|
||||
gint width, height;
|
||||
gint par_n, par_d;
|
||||
gint display_par_n, display_par_d;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (info);
|
||||
|
||||
par_n = GST_VIDEO_INFO_PAR_N (info);
|
||||
par_d = GST_VIDEO_INFO_PAR_D (info);
|
||||
|
||||
if (!par_n)
|
||||
par_n = 1;
|
||||
|
||||
/* get display's PAR */
|
||||
if (widget->par_n != 0 && widget->par_d != 0) {
|
||||
display_par_n = widget->par_n;
|
||||
display_par_d = widget->par_d;
|
||||
} else {
|
||||
display_par_n = 1;
|
||||
display_par_d = 1;
|
||||
}
|
||||
|
||||
|
||||
ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
|
||||
&widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
|
||||
display_par_d);
|
||||
|
||||
if (ok) {
|
||||
GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
|
||||
display_par_d);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_apply_par (GtkGstBaseWidget * widget)
|
||||
{
|
||||
guint display_ratio_num, display_ratio_den;
|
||||
gint width, height;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
|
||||
|
||||
display_ratio_num = widget->display_ratio_num;
|
||||
display_ratio_den = widget->display_ratio_den;
|
||||
|
||||
if (height % display_ratio_den == 0) {
|
||||
GST_DEBUG ("keeping video height");
|
||||
widget->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num,
|
||||
display_ratio_den);
|
||||
widget->display_height = height;
|
||||
} else if (width % display_ratio_num == 0) {
|
||||
GST_DEBUG ("keeping video width");
|
||||
widget->display_width = width;
|
||||
widget->display_height = (guint)
|
||||
gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
|
||||
} else {
|
||||
GST_DEBUG ("approximating while keeping video height");
|
||||
widget->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num,
|
||||
display_ratio_den);
|
||||
widget->display_height = height;
|
||||
}
|
||||
|
||||
GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_queue_draw (GtkGstBaseWidget * widget)
|
||||
{
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
widget->draw_id = 0;
|
||||
|
||||
if (widget->pending_resize) {
|
||||
widget->pending_resize = FALSE;
|
||||
|
||||
widget->v_info = widget->pending_v_info;
|
||||
widget->negotiated = TRUE;
|
||||
|
||||
_apply_par (widget);
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (widget));
|
||||
} else {
|
||||
gtk_widget_queue_draw (GTK_WIDGET (widget));
|
||||
}
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
_gdk_key_to_navigation_string (guint keyval)
|
||||
{
|
||||
/* TODO: expand */
|
||||
switch (keyval) {
|
||||
#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
|
||||
KEY (Up);
|
||||
KEY (Down);
|
||||
KEY (Left);
|
||||
KEY (Right);
|
||||
KEY (Home);
|
||||
KEY (End);
|
||||
#undef KEY
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_key_event (GtkWidget * widget, GdkEventKey * event)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
const gchar *str = _gdk_key_to_navigation_string (event->keyval);
|
||||
const gchar *key_type =
|
||||
event->type == GDK_KEY_PRESS ? "key-press" : "key-release";
|
||||
|
||||
if (!str)
|
||||
str = event->string;
|
||||
|
||||
gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
|
||||
GtkAllocation * allocation, GstVideoRectangle * result)
|
||||
{
|
||||
if (base_widget->force_aspect_ratio) {
|
||||
GstVideoRectangle src, dst;
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = base_widget->display_width;
|
||||
src.h = base_widget->display_height;
|
||||
|
||||
dst.x = 0;
|
||||
dst.y = 0;
|
||||
dst.w = allocation->width;
|
||||
dst.h = allocation->height;
|
||||
|
||||
gst_video_sink_center_rect (src, dst, result, TRUE);
|
||||
} else {
|
||||
result->x = 0;
|
||||
result->y = 0;
|
||||
result->w = allocation->width;
|
||||
result->h = allocation->height;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_display_size_to_stream_size (GtkGstBaseWidget * base_widget,
|
||||
gdouble x, gdouble y, gdouble * stream_x, gdouble * stream_y)
|
||||
{
|
||||
gdouble stream_width, stream_height;
|
||||
GtkAllocation allocation;
|
||||
GstVideoRectangle result;
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
|
||||
_fit_stream_to_allocated_size (base_widget, &allocation, &result);
|
||||
|
||||
stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
|
||||
stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
|
||||
|
||||
/* from display coordinates to stream coordinates */
|
||||
if (result.w > 0)
|
||||
*stream_x = (x - result.x) / result.w * stream_width;
|
||||
else
|
||||
*stream_x = 0.;
|
||||
|
||||
/* clip to stream size */
|
||||
if (*stream_x < 0.)
|
||||
*stream_x = 0.;
|
||||
if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
|
||||
*stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
|
||||
|
||||
/* same for y-axis */
|
||||
if (result.h > 0)
|
||||
*stream_y = (y - result.y) / result.h * stream_height;
|
||||
else
|
||||
*stream_y = 0.;
|
||||
|
||||
if (*stream_y < 0.)
|
||||
*stream_y = 0.;
|
||||
if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
|
||||
*stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
|
||||
|
||||
GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_button_event (GtkWidget * widget, GdkEventButton * event)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
const gchar *key_type =
|
||||
event->type ==
|
||||
GDK_BUTTON_PRESS ? "mouse-button-press" : "mouse-button-release";
|
||||
gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
|
||||
event->button, event->x, event->y);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_motion_event (GtkWidget * widget, GdkEventMotion * event)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
|
||||
0, event->x, event->y);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_scroll_event (GtkWidget * widget, GdkEventScroll * event)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
gdouble x, y, delta_x, delta_y;
|
||||
|
||||
gtk_gst_base_widget_display_size_to_stream_size (base_widget, event->x,
|
||||
event->y, &x, &y);
|
||||
|
||||
if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &delta_x, &delta_y)) {
|
||||
gdouble offset = 20;
|
||||
|
||||
delta_x = event->delta_x;
|
||||
delta_y = event->delta_y;
|
||||
|
||||
switch (event->direction) {
|
||||
case GDK_SCROLL_UP:
|
||||
delta_y = offset;
|
||||
break;
|
||||
case GDK_SCROLL_DOWN:
|
||||
delta_y = -offset;
|
||||
break;
|
||||
case GDK_SCROLL_LEFT:
|
||||
delta_x = -offset;
|
||||
break;
|
||||
case GDK_SCROLL_RIGHT:
|
||||
delta_x = offset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
gst_navigation_send_mouse_scroll_event (GST_NAVIGATION (element),
|
||||
x, y, delta_x, delta_y);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
|
||||
|
||||
gobject_klass->set_property = gtk_gst_base_widget_set_property;
|
||||
gobject_klass->get_property = gtk_gst_base_widget_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
|
||||
g_param_spec_boolean ("force-aspect-ratio",
|
||||
"Force aspect ratio",
|
||||
"When enabled, scaling will respect original aspect ratio",
|
||||
DEFAULT_FORCE_ASPECT_RATIO,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
|
||||
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
|
||||
"The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
|
||||
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
|
||||
g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
|
||||
"When enabled, alpha will be ignored and converted to black",
|
||||
DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
|
||||
widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
|
||||
widget_klass->key_press_event = gtk_gst_base_widget_key_event;
|
||||
widget_klass->key_release_event = gtk_gst_base_widget_key_event;
|
||||
widget_klass->button_press_event = gtk_gst_base_widget_button_event;
|
||||
widget_klass->button_release_event = gtk_gst_base_widget_button_event;
|
||||
widget_klass->motion_notify_event = gtk_gst_base_widget_motion_event;
|
||||
widget_klass->scroll_event = gtk_gst_base_widget_scroll_event;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
|
||||
"Gtk Video Base Widget");
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
|
||||
{
|
||||
int event_mask;
|
||||
|
||||
widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
widget->par_n = DEFAULT_PAR_N;
|
||||
widget->par_d = DEFAULT_PAR_D;
|
||||
widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
|
||||
|
||||
gst_video_info_init (&widget->v_info);
|
||||
gst_video_info_init (&widget->pending_v_info);
|
||||
|
||||
g_weak_ref_init (&widget->element, NULL);
|
||||
g_mutex_init (&widget->lock);
|
||||
|
||||
gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
|
||||
event_mask = gtk_widget_get_events (GTK_WIDGET (widget));
|
||||
event_mask |= GDK_KEY_PRESS_MASK
|
||||
| GDK_KEY_RELEASE_MASK
|
||||
| GDK_BUTTON_PRESS_MASK
|
||||
| GDK_BUTTON_RELEASE_MASK
|
||||
| GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_SCROLL_MASK;
|
||||
gtk_widget_set_events (GTK_WIDGET (widget), event_mask);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_finalize (GObject * object)
|
||||
{
|
||||
GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
gst_buffer_replace (&widget->pending_buffer, NULL);
|
||||
gst_buffer_replace (&widget->buffer, NULL);
|
||||
g_mutex_clear (&widget->lock);
|
||||
g_weak_ref_clear (&widget->element);
|
||||
|
||||
if (widget->draw_id)
|
||||
g_source_remove (widget->draw_id);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
|
||||
GstElement * element)
|
||||
{
|
||||
g_weak_ref_set (&widget->element, element);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
|
||||
GstVideoInfo * v_info)
|
||||
{
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!_calculate_par (widget, v_info)) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
widget->pending_resize = TRUE;
|
||||
widget->pending_v_info = *v_info;
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
|
||||
{
|
||||
/* As we have no type, this is better then no check */
|
||||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
gst_buffer_replace (&widget->pending_buffer, buffer);
|
||||
|
||||
if (!widget->draw_id) {
|
||||
widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
|
||||
(GSourceFunc) _queue_draw, widget, NULL);
|
||||
}
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
}
|
100
ext/gtk/gtkgstbasewidget.h
Normal file
100
ext/gtk/gtkgstbasewidget.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_BASE_WIDGET_H__
|
||||
#define __GTK_GST_BASE_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#define GTK_GST_BASE_WIDGET(w) ((GtkGstBaseWidget *)(w))
|
||||
#define GTK_GST_BASE_WIDGET_CLASS(k) ((GtkGstBaseWidgetClass *)(k))
|
||||
#define GTK_GST_BASE_WIDGET_LOCK(w) g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock)
|
||||
#define GTK_GST_BASE_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock)
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GtkGstBaseWidget GtkGstBaseWidget;
|
||||
typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass;
|
||||
|
||||
struct _GtkGstBaseWidget
|
||||
{
|
||||
union {
|
||||
GtkDrawingArea drawing_area;
|
||||
#if GTK_CHECK_VERSION(3, 15, 0)
|
||||
GtkGLArea gl_area;
|
||||
#endif
|
||||
} parent;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean ignore_alpha;
|
||||
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
gboolean negotiated;
|
||||
GstBuffer *pending_buffer;
|
||||
GstBuffer *buffer;
|
||||
GstVideoInfo v_info;
|
||||
|
||||
/* resize */
|
||||
gboolean pending_resize;
|
||||
GstVideoInfo pending_v_info;
|
||||
guint display_ratio_num;
|
||||
guint display_ratio_den;
|
||||
|
||||
/*< private >*/
|
||||
GMutex lock;
|
||||
GWeakRef element;
|
||||
|
||||
/* Pending draw idles callback */
|
||||
guint draw_id;
|
||||
};
|
||||
|
||||
struct _GtkGstBaseWidgetClass
|
||||
{
|
||||
union {
|
||||
GtkDrawingAreaClass drawing_area_class;
|
||||
#if GTK_CHECK_VERSION(3, 15, 0)
|
||||
GtkGLAreaClass gl_area_class;
|
||||
#endif
|
||||
} parent_class;
|
||||
};
|
||||
|
||||
/* For implementer */
|
||||
void gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass);
|
||||
void gtk_gst_base_widget_init (GtkGstBaseWidget * widget);
|
||||
|
||||
void gtk_gst_base_widget_finalize (GObject * object);
|
||||
|
||||
/* API */
|
||||
gboolean gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, GstVideoInfo * v_info);
|
||||
void gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer);
|
||||
void gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, GstElement * element);
|
||||
void gtk_gst_base_widget_display_size_to_stream_size (GtkGstBaseWidget * base_widget,
|
||||
gdouble x, gdouble y,
|
||||
gdouble * stream_x, gdouble * stream_y);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_BASE_WIDGET_H__ */
|
581
ext/gtk/gtkgstglwidget.c
Normal file
581
ext/gtk/gtkgstglwidget.c
Normal file
|
@ -0,0 +1,581 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "gtkgstglwidget.h"
|
||||
#include "gstgtkutils.h"
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gst/gl/wayland/gstgldisplay_wayland.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* SECTION:gtkgstglwidget
|
||||
* @title: GtkGstGlWidget
|
||||
* @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers
|
||||
* @see_also: #GtkGLArea, #GstBuffer
|
||||
*
|
||||
* #GtkGstGLWidget is an #GtkWidget that renders GStreamer video buffers.
|
||||
*/
|
||||
|
||||
#define GST_CAT_DEFAULT gtk_gst_gl_widget_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _GtkGstGLWidgetPrivate
|
||||
{
|
||||
gboolean initted;
|
||||
GstGLDisplay *display;
|
||||
GdkGLContext *gdk_context;
|
||||
GstGLContext *other_context;
|
||||
GstGLContext *context;
|
||||
GstGLUpload *upload;
|
||||
GstGLShader *shader;
|
||||
GLuint vao;
|
||||
GLuint vertex_buffer;
|
||||
GLint attr_position;
|
||||
GLint attr_texture;
|
||||
GLuint current_tex;
|
||||
GstGLOverlayCompositor *overlay_compositor;
|
||||
};
|
||||
|
||||
static const GLfloat vertices[] = {
|
||||
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA,
|
||||
G_ADD_PRIVATE (GtkGstGLWidget)
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0,
|
||||
"Gtk Gst GL Widget");
|
||||
);
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
|
||||
|
||||
/* Load the vertex position */
|
||||
gl->VertexAttribPointer (priv->attr_position, 3, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) 0);
|
||||
|
||||
/* Load the texture coordinate */
|
||||
gl->VertexAttribPointer (priv->attr_texture, 2, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
|
||||
|
||||
gl->EnableVertexAttribArray (priv->attr_position);
|
||||
gl->EnableVertexAttribArray (priv->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_unbind_buffer (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
|
||||
gl->DisableVertexAttribArray (priv->attr_position);
|
||||
gl->DisableVertexAttribArray (priv->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
GError *error = NULL;
|
||||
|
||||
gst_gl_insert_debug_marker (priv->other_context, "initializing redisplay");
|
||||
if (!(priv->shader = gst_gl_shader_new_default (priv->context, &error))) {
|
||||
GST_ERROR ("Failed to initialize shader: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->attr_position =
|
||||
gst_gl_shader_get_attribute_location (priv->shader, "a_position");
|
||||
priv->attr_texture =
|
||||
gst_gl_shader_get_attribute_location (priv->shader, "a_texcoord");
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gl->GenVertexArrays (1, &priv->vao);
|
||||
gl->BindVertexArray (priv->vao);
|
||||
}
|
||||
|
||||
gl->GenBuffers (1, &priv->vertex_buffer);
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
|
||||
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
|
||||
GL_STATIC_DRAW);
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gtk_gst_gl_widget_bind_buffer (gst_widget);
|
||||
gl->BindVertexArray (0);
|
||||
}
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
|
||||
priv->overlay_compositor =
|
||||
gst_gl_overlay_compositor_new (priv->other_context);
|
||||
|
||||
priv->initted = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_redraw_texture (GtkGstGLWidget * gst_widget, guint tex)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
if (gst_widget->base.force_aspect_ratio) {
|
||||
GstVideoRectangle src, dst, result;
|
||||
gint widget_width, widget_height, widget_scale;
|
||||
|
||||
gl->ClearColor (0.0, 0.0, 0.0, 0.0);
|
||||
gl->Clear (GL_COLOR_BUFFER_BIT);
|
||||
|
||||
widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget);
|
||||
widget_width = gtk_widget_get_allocated_width ((GtkWidget *) gst_widget);
|
||||
widget_height = gtk_widget_get_allocated_height ((GtkWidget *) gst_widget);
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = gst_widget->base.display_width;
|
||||
src.h = gst_widget->base.display_height;
|
||||
|
||||
dst.x = 0;
|
||||
dst.y = 0;
|
||||
dst.w = widget_width * widget_scale;
|
||||
dst.h = widget_height * widget_scale;
|
||||
|
||||
gst_video_sink_center_rect (src, dst, &result, TRUE);
|
||||
|
||||
gl->Viewport (result.x, result.y, result.w, result.h);
|
||||
}
|
||||
|
||||
gst_gl_shader_use (priv->shader);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (priv->vao);
|
||||
gtk_gst_gl_widget_bind_buffer (gst_widget);
|
||||
|
||||
gl->ActiveTexture (GL_TEXTURE0);
|
||||
gl->BindTexture (GL_TEXTURE_2D, tex);
|
||||
gst_gl_shader_set_uniform_1i (priv->shader, "tex", 0);
|
||||
|
||||
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (0);
|
||||
else
|
||||
gtk_gst_gl_widget_unbind_buffer (gst_widget);
|
||||
|
||||
gl->BindTexture (GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_draw_black (GstGLContext * context)
|
||||
{
|
||||
const GstGLFuncs *gl = context->gl_vtable;
|
||||
|
||||
gst_gl_insert_debug_marker (context, "no buffer. rendering black");
|
||||
gl->ClearColor (0.0, 0.0, 0.0, 0.0);
|
||||
gl->Clear (GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (widget)->priv;
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
if (!priv->context || !priv->other_context)
|
||||
goto done;
|
||||
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
|
||||
if (!priv->initted)
|
||||
gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget));
|
||||
|
||||
if (!priv->initted || !base_widget->negotiated) {
|
||||
_draw_black (priv->other_context);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Upload latest buffer */
|
||||
if (base_widget->pending_buffer) {
|
||||
GstBuffer *buffer = base_widget->pending_buffer;
|
||||
GstVideoFrame gl_frame;
|
||||
GstGLSyncMeta *sync_meta;
|
||||
|
||||
if (!gst_video_frame_map (&gl_frame, &base_widget->v_info, buffer,
|
||||
GST_MAP_READ | GST_MAP_GL)) {
|
||||
_draw_black (priv->other_context);
|
||||
goto done;
|
||||
}
|
||||
|
||||
priv->current_tex = *(guint *) gl_frame.data[0];
|
||||
gst_gl_insert_debug_marker (priv->other_context, "redrawing texture %u",
|
||||
priv->current_tex);
|
||||
|
||||
gst_gl_overlay_compositor_upload_overlays (priv->overlay_compositor,
|
||||
buffer);
|
||||
|
||||
sync_meta = gst_buffer_get_gl_sync_meta (buffer);
|
||||
if (sync_meta) {
|
||||
/* XXX: the set_sync() seems to be needed for resizing */
|
||||
gst_gl_sync_meta_set_sync_point (sync_meta, priv->context);
|
||||
gst_gl_sync_meta_wait (sync_meta, priv->other_context);
|
||||
}
|
||||
|
||||
gst_video_frame_unmap (&gl_frame);
|
||||
|
||||
if (base_widget->buffer)
|
||||
gst_buffer_unref (base_widget->buffer);
|
||||
|
||||
/* Keep the buffer to ensure current_tex stay valid */
|
||||
base_widget->buffer = buffer;
|
||||
base_widget->pending_buffer = NULL;
|
||||
}
|
||||
|
||||
GST_DEBUG ("rendering buffer %p with gdk context %p",
|
||||
base_widget->buffer, context);
|
||||
|
||||
_redraw_texture (GTK_GST_GL_WIDGET (widget), priv->current_tex);
|
||||
gst_gl_overlay_compositor_draw_overlays (priv->overlay_compositor);
|
||||
|
||||
gst_gl_insert_debug_marker (priv->other_context, "texture %u redrawn",
|
||||
priv->current_tex);
|
||||
|
||||
done:
|
||||
if (priv->other_context)
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_reset_gl (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->other_context->gl_vtable;
|
||||
|
||||
if (!priv->gdk_context)
|
||||
priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
|
||||
|
||||
if (priv->gdk_context == NULL)
|
||||
return;
|
||||
|
||||
gdk_gl_context_make_current (priv->gdk_context);
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
|
||||
if (priv->vao) {
|
||||
gl->DeleteVertexArrays (1, &priv->vao);
|
||||
priv->vao = 0;
|
||||
}
|
||||
|
||||
if (priv->vertex_buffer) {
|
||||
gl->DeleteBuffers (1, &priv->vertex_buffer);
|
||||
priv->vertex_buffer = 0;
|
||||
}
|
||||
|
||||
if (priv->upload) {
|
||||
gst_object_unref (priv->upload);
|
||||
priv->upload = NULL;
|
||||
}
|
||||
|
||||
if (priv->shader) {
|
||||
gst_object_unref (priv->shader);
|
||||
priv->shader = NULL;
|
||||
}
|
||||
|
||||
if (priv->overlay_compositor)
|
||||
gst_object_unref (priv->overlay_compositor);
|
||||
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
|
||||
gst_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
|
||||
gdk_gl_context_clear_current ();
|
||||
|
||||
g_object_unref (priv->gdk_context);
|
||||
priv->gdk_context = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_finalize (GObject * object)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (object)->priv;
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
if (priv->other_context)
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) _reset_gl, base_widget);
|
||||
|
||||
if (priv->context)
|
||||
gst_object_unref (priv->context);
|
||||
|
||||
if (priv->display)
|
||||
gst_object_unref (priv->display);
|
||||
|
||||
gtk_gst_base_widget_finalize (object);
|
||||
G_OBJECT_CLASS (gtk_gst_gl_widget_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass;
|
||||
|
||||
gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
|
||||
|
||||
gobject_klass->finalize = gtk_gst_gl_widget_finalize;
|
||||
gl_widget_klass->render = gtk_gst_gl_widget_render;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (gst_widget);
|
||||
GdkDisplay *display;
|
||||
GtkGstGLWidgetPrivate *priv;
|
||||
|
||||
gtk_gst_base_widget_init (base_widget);
|
||||
|
||||
gst_widget->priv = priv = gtk_gst_gl_widget_get_instance_private (gst_widget);
|
||||
|
||||
display = gdk_display_get_default ();
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GDK_IS_X11_DISPLAY (display)) {
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay
|
||||
(display));
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GDK_IS_WAYLAND_DISPLAY (display)) {
|
||||
struct wl_display *wayland_display =
|
||||
gdk_wayland_display_get_wl_display (display);
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_wayland_new_with_display (wayland_display);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) display;
|
||||
|
||||
if (!priv->display)
|
||||
priv->display = gst_gl_display_new ();
|
||||
|
||||
GST_INFO ("Created %" GST_PTR_FORMAT, priv->display);
|
||||
|
||||
gtk_gl_area_set_has_alpha (GTK_GL_AREA (gst_widget),
|
||||
!base_widget->ignore_alpha);
|
||||
}
|
||||
|
||||
static void
|
||||
_get_gl_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
|
||||
GstGLAPI gl_api = GST_GL_API_NONE;
|
||||
guintptr gl_handle = 0;
|
||||
|
||||
gtk_widget_realize (GTK_WIDGET (gst_widget));
|
||||
|
||||
if (priv->other_context)
|
||||
gst_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
|
||||
if (priv->gdk_context)
|
||||
g_object_unref (priv->gdk_context);
|
||||
|
||||
priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
|
||||
if (priv->gdk_context == NULL) {
|
||||
GError *error = gtk_gl_area_get_error (GTK_GL_AREA (gst_widget));
|
||||
|
||||
GST_ERROR_OBJECT (gst_widget, "Error creating GdkGLContext : %s",
|
||||
error ? error->message : "No error set by Gdk");
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_ref (priv->gdk_context);
|
||||
|
||||
gdk_gl_context_make_current (priv->gdk_context);
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GST_IS_GL_DISPLAY_X11 (priv->display)) {
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
if (!gl_handle) {
|
||||
platform = GST_GL_PLATFORM_GLX;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
if (!gl_handle) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gl_handle) {
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
priv->other_context =
|
||||
gst_gl_context_new_wrapped (priv->display, gl_handle,
|
||||
platform, gl_api);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GST_IS_GL_DISPLAY_WAYLAND (priv->display)) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
if (gl_handle)
|
||||
priv->other_context =
|
||||
gst_gl_context_new_wrapped (priv->display, gl_handle,
|
||||
platform, gl_api);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) platform;
|
||||
(void) gl_api;
|
||||
(void) gl_handle;
|
||||
|
||||
if (priv->other_context) {
|
||||
GError *error = NULL;
|
||||
|
||||
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT,
|
||||
priv->other_context);
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
if (!gst_gl_context_fill_info (priv->other_context, &error)) {
|
||||
GST_ERROR ("failed to retrieve gdk context info: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
g_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
} else {
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
}
|
||||
} else {
|
||||
GST_WARNING ("Could not retrieve Gdk OpenGL context");
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_gst_gl_widget_new (void)
|
||||
{
|
||||
return (GtkWidget *) g_object_new (GTK_TYPE_GST_GL_WIDGET, NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_GST_GL_WIDGET (gst_widget), FALSE);
|
||||
g_return_val_if_fail (priv->display != NULL, FALSE);
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (gst_widget);
|
||||
|
||||
if (priv->display && priv->gdk_context && priv->other_context) {
|
||||
GST_TRACE ("have already initialized contexts");
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!priv->other_context) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) _get_gl_context, gst_widget);
|
||||
GTK_GST_BASE_WIDGET_LOCK (gst_widget);
|
||||
}
|
||||
|
||||
if (!GST_IS_GL_CONTEXT (priv->other_context)) {
|
||||
GST_FIXME ("Could not retrieve Gdk OpenGL context");
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (priv->display);
|
||||
if (!gst_gl_display_create_context (priv->display, priv->other_context,
|
||||
&priv->context, &error)) {
|
||||
GST_WARNING ("Could not create OpenGL context: %s",
|
||||
error ? error->message : "Unknown");
|
||||
g_clear_error (&error);
|
||||
GST_OBJECT_UNLOCK (priv->display);
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return FALSE;
|
||||
}
|
||||
gst_gl_display_add_context (priv->display, priv->context);
|
||||
GST_OBJECT_UNLOCK (priv->display);
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GstGLContext *
|
||||
gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->other_context)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->other_context);
|
||||
}
|
||||
|
||||
GstGLContext *
|
||||
gtk_gst_gl_widget_get_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->context)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->context);
|
||||
}
|
||||
|
||||
GstGLDisplay *
|
||||
gtk_gst_gl_widget_get_display (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->display)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->display);
|
||||
}
|
77
ext/gtk/gtkgstglwidget.h
Normal file
77
ext/gtk/gtkgstglwidget.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_GL_WIDGET_H__
|
||||
#define __GTK_GST_GL_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GType gtk_gst_gl_widget_get_type (void);
|
||||
#define GTK_TYPE_GST_GL_WIDGET (gtk_gst_gl_widget_get_type())
|
||||
#define GTK_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidget))
|
||||
#define GTK_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidgetClass))
|
||||
#define GTK_IS_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_GL_WIDGET))
|
||||
#define GTK_IS_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_GL_WIDGET))
|
||||
#define GTK_GST_GL_WIDGET_CAST(obj) ((GtkGstGLWidget*)(obj))
|
||||
|
||||
typedef struct _GtkGstGLWidget GtkGstGLWidget;
|
||||
typedef struct _GtkGstGLWidgetClass GtkGstGLWidgetClass;
|
||||
typedef struct _GtkGstGLWidgetPrivate GtkGstGLWidgetPrivate;
|
||||
|
||||
/**
|
||||
* GtkGstGLWidget:
|
||||
*
|
||||
* Opaque #GtkGstGLWidget object
|
||||
*/
|
||||
struct _GtkGstGLWidget
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidget base;
|
||||
|
||||
GtkGstGLWidgetPrivate *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* GtkGstGLWidgetClass:
|
||||
*
|
||||
* The #GtkGstGLWidgetClass struct only contains private data
|
||||
*/
|
||||
struct _GtkGstGLWidgetClass
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidgetClass base_class;
|
||||
};
|
||||
|
||||
GtkWidget * gtk_gst_gl_widget_new (void);
|
||||
|
||||
gboolean gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * widget);
|
||||
GstGLDisplay * gtk_gst_gl_widget_get_display (GtkGstGLWidget * widget);
|
||||
GstGLContext * gtk_gst_gl_widget_get_context (GtkGstGLWidget * widget);
|
||||
GstGLContext * gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * widget);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_GL_WIDGET_H__ */
|
191
ext/gtk/gtkgstwidget.c
Normal file
191
ext/gtk/gtkgstwidget.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "gtkgstwidget.h"
|
||||
#include <gst/video/video.h>
|
||||
|
||||
/**
|
||||
* SECTION:gtkgstwidget
|
||||
* @title: GtkGstWidget
|
||||
* @short_description: a #GtkWidget that renders GStreamer video #GstBuffers
|
||||
* @see_also: #GtkDrawingArea, #GstBuffer
|
||||
*
|
||||
* #GtkGstWidget is an #GtkWidget that renders GStreamer video buffers.
|
||||
*/
|
||||
|
||||
G_DEFINE_TYPE (GtkGstWidget, gtk_gst_widget, GTK_TYPE_DRAWING_AREA);
|
||||
|
||||
static gboolean
|
||||
gtk_gst_widget_draw (GtkWidget * widget, cairo_t * cr)
|
||||
{
|
||||
GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
|
||||
guint widget_width, widget_height;
|
||||
cairo_surface_t *surface;
|
||||
GstVideoFrame frame;
|
||||
|
||||
widget_width = gtk_widget_get_allocated_width (widget);
|
||||
widget_height = gtk_widget_get_allocated_height (widget);
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (gst_widget);
|
||||
|
||||
/* There is not much to optimize in term of redisplay, so simply swap the
|
||||
* pending_buffer with the active buffer */
|
||||
if (gst_widget->pending_buffer) {
|
||||
if (gst_widget->buffer)
|
||||
gst_buffer_unref (gst_widget->buffer);
|
||||
gst_widget->buffer = gst_widget->pending_buffer;
|
||||
gst_widget->pending_buffer = NULL;
|
||||
}
|
||||
|
||||
/* failed to map the video frame */
|
||||
if (gst_widget->negotiated && gst_widget->buffer
|
||||
&& gst_video_frame_map (&frame, &gst_widget->v_info,
|
||||
gst_widget->buffer, GST_MAP_READ)) {
|
||||
gdouble scale_x = (gdouble) widget_width / gst_widget->display_width;
|
||||
gdouble scale_y = (gdouble) widget_height / gst_widget->display_height;
|
||||
GstVideoRectangle result;
|
||||
cairo_format_t format;
|
||||
|
||||
gst_widget->v_info = frame.info;
|
||||
if (frame.info.finfo->format == GST_VIDEO_FORMAT_ARGB ||
|
||||
frame.info.finfo->format == GST_VIDEO_FORMAT_BGRA) {
|
||||
format = CAIRO_FORMAT_ARGB32;
|
||||
} else {
|
||||
format = CAIRO_FORMAT_RGB24;
|
||||
}
|
||||
|
||||
surface = cairo_image_surface_create_for_data (frame.data[0],
|
||||
format, frame.info.width, frame.info.height, frame.info.stride[0]);
|
||||
|
||||
if (gst_widget->force_aspect_ratio) {
|
||||
GstVideoRectangle src, dst;
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = gst_widget->display_width;
|
||||
src.h = gst_widget->display_height;
|
||||
|
||||
dst.x = 0;
|
||||
dst.y = 0;
|
||||
dst.w = widget_width;
|
||||
dst.h = widget_height;
|
||||
|
||||
gst_video_sink_center_rect (src, dst, &result, TRUE);
|
||||
|
||||
scale_x = scale_y = MIN (scale_x, scale_y);
|
||||
} else {
|
||||
result.x = 0;
|
||||
result.y = 0;
|
||||
result.w = widget_width;
|
||||
result.h = widget_height;
|
||||
}
|
||||
|
||||
if (gst_widget->ignore_alpha) {
|
||||
GdkRGBA color = { 0.0, 0.0, 0.0, 1.0 };
|
||||
|
||||
gdk_cairo_set_source_rgba (cr, &color);
|
||||
if (result.x > 0) {
|
||||
cairo_rectangle (cr, 0, 0, result.x, widget_height);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
if (result.y > 0) {
|
||||
cairo_rectangle (cr, 0, 0, widget_width, result.y);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
if (result.w < widget_width) {
|
||||
cairo_rectangle (cr, result.x + result.w, 0, widget_width - result.w,
|
||||
widget_height);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
if (result.h < widget_height) {
|
||||
cairo_rectangle (cr, 0, result.y + result.h, widget_width,
|
||||
widget_height - result.h);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
}
|
||||
|
||||
scale_x *= (gdouble) gst_widget->display_width / (gdouble) frame.info.width;
|
||||
scale_y *=
|
||||
(gdouble) gst_widget->display_height / (gdouble) frame.info.height;
|
||||
|
||||
cairo_translate (cr, result.x, result.y);
|
||||
cairo_scale (cr, scale_x, scale_y);
|
||||
cairo_rectangle (cr, 0, 0, result.w, result.h);
|
||||
cairo_set_source_surface (cr, surface, 0, 0);
|
||||
cairo_paint (cr);
|
||||
|
||||
cairo_surface_destroy (surface);
|
||||
|
||||
gst_video_frame_unmap (&frame);
|
||||
} else {
|
||||
GdkRGBA color;
|
||||
|
||||
if (gst_widget->ignore_alpha) {
|
||||
color.red = color.blue = color.green = 0.0;
|
||||
color.alpha = 1.0;
|
||||
} else {
|
||||
gtk_style_context_get_color (gtk_widget_get_style_context (widget),
|
||||
GTK_STATE_FLAG_NORMAL, &color);
|
||||
}
|
||||
gdk_cairo_set_source_rgba (cr, &color);
|
||||
cairo_rectangle (cr, 0, 0, widget_width, widget_height);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_widget_finalize (GObject * object)
|
||||
{
|
||||
gtk_gst_base_widget_finalize (object);
|
||||
|
||||
G_OBJECT_CLASS (gtk_gst_widget_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_widget_class_init (GtkGstWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
|
||||
|
||||
gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
|
||||
gobject_klass->finalize = gtk_gst_widget_finalize;
|
||||
widget_klass->draw = gtk_gst_widget_draw;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_widget_init (GtkGstWidget * widget)
|
||||
{
|
||||
gtk_gst_base_widget_init (GTK_GST_BASE_WIDGET (widget));
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_gst_widget_new (void)
|
||||
{
|
||||
return (GtkWidget *) g_object_new (GTK_TYPE_GST_WIDGET, NULL);
|
||||
}
|
68
ext/gtk/gtkgstwidget.h
Normal file
68
ext/gtk/gtkgstwidget.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_WIDGET_H__
|
||||
#define __GTK_GST_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GType gtk_gst_widget_get_type (void);
|
||||
#define GTK_TYPE_GST_WIDGET (gtk_gst_widget_get_type())
|
||||
#define GTK_GST_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_WIDGET,GtkGstWidget))
|
||||
#define GTK_GST_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_WIDGET,GtkGstWidgetClass))
|
||||
#define GTK_IS_GST_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_WIDGET))
|
||||
#define GST_IS_GST_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_WIDGET))
|
||||
#define GTK_GST_WIDGET_CAST(obj) ((GtkGstWidget*)(obj))
|
||||
|
||||
typedef struct _GtkGstWidget GtkGstWidget;
|
||||
typedef struct _GtkGstWidgetClass GtkGstWidgetClass;
|
||||
|
||||
/**
|
||||
* GtkGstWidget:
|
||||
*
|
||||
* Opaque #GtkGstWidget object
|
||||
*/
|
||||
struct _GtkGstWidget
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidget base;
|
||||
};
|
||||
|
||||
/**
|
||||
* GtkGstWidgetClass:
|
||||
*
|
||||
* The #GtkGstWidgetClass struct only contains private data
|
||||
*/
|
||||
struct _GtkGstWidgetClass
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidgetClass base_class;
|
||||
};
|
||||
|
||||
GtkWidget * gtk_gst_widget_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_WIDGET_H__ */
|
59
ext/gtk/meson.build
Normal file
59
ext/gtk/meson.build
Normal file
|
@ -0,0 +1,59 @@
|
|||
gtk_sources = [
|
||||
'gstgtkbasesink.c',
|
||||
'gstgtksink.c',
|
||||
'gstgtkutils.c',
|
||||
'gstplugin.c',
|
||||
'gtkgstbasewidget.c',
|
||||
'gtkgstwidget.c',
|
||||
]
|
||||
|
||||
gtk_defines = []
|
||||
optional_deps = []
|
||||
|
||||
gtk_dep = dependency('gtk+-3.0', required : get_option('gtk3'))
|
||||
if gtk_dep.found()
|
||||
# FIXME: automagic
|
||||
if have_gstgl and gtk_dep.version().version_compare('>=3.15.0')
|
||||
have_gtk3_gl_windowing = false
|
||||
|
||||
if gst_gl_have_window_x11 and gst_gl_have_platform_glx
|
||||
# FIXME: automagic
|
||||
gtk_x11_dep = dependency('gtk+-x11-3.0', required : false)
|
||||
if gtk_x11_dep.found()
|
||||
optional_deps += [gtk_x11_dep, gstglx11_dep]
|
||||
have_gtk3_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
|
||||
# FIXME: automagic
|
||||
gtk_wayland_dep = dependency('gtk+-wayland-3.0', required : false)
|
||||
if gtk_wayland_dep.found()
|
||||
optional_deps += [gtk_wayland_dep, gstglegl_dep, gstglwayland_dep]
|
||||
have_gtk3_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if have_gtk3_gl_windowing
|
||||
gtk_sources += [
|
||||
'gstgtkglsink.c',
|
||||
'gtkgstglwidget.c',
|
||||
]
|
||||
optional_deps += [gstgl_dep, gstglproto_dep]
|
||||
gtk_defines += ['-DGST_USE_UNSTABLE_API', '-DHAVE_GTK3_GL']
|
||||
endif
|
||||
endif
|
||||
|
||||
gstgtk = library('gstgtk',
|
||||
gtk_sources,
|
||||
c_args : gst_plugins_good_args + gtk_defines,
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [gtk_dep, gstvideo_dep, gstbase_dep, libm] + optional_deps,
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstgtk, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstgtk]
|
||||
endif
|
||||
|
116
ext/jack/gstjack.c
Normal file
116
ext/jack/gstjack.c
Normal file
|
@ -0,0 +1,116 @@
|
|||
/* GStreamer Jack plugins
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstjack.h"
|
||||
|
||||
GType
|
||||
gst_jack_connect_get_type (void)
|
||||
{
|
||||
static gsize jack_connect_type = 0;
|
||||
|
||||
if (g_once_init_enter (&jack_connect_type)) {
|
||||
static const GEnumValue jack_connect_enums[] = {
|
||||
{GST_JACK_CONNECT_NONE,
|
||||
"Don't automatically connect ports to physical ports", "none"},
|
||||
{GST_JACK_CONNECT_AUTO,
|
||||
"Automatically connect ports to physical ports", "auto"},
|
||||
{GST_JACK_CONNECT_AUTO_FORCED,
|
||||
"Automatically connect ports to as many physical ports as possible",
|
||||
"auto-forced"},
|
||||
{GST_JACK_CONNECT_EXPLICIT,
|
||||
"Connect ports to explicitly requested physical ports",
|
||||
"explicit"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
GType tmp = g_enum_register_static ("GstJackConnect", jack_connect_enums);
|
||||
g_once_init_leave (&jack_connect_type, tmp);
|
||||
}
|
||||
return (GType) jack_connect_type;
|
||||
}
|
||||
|
||||
GType
|
||||
gst_jack_transport_get_type (void)
|
||||
{
|
||||
static gsize type = 0;
|
||||
|
||||
if (g_once_init_enter (&type)) {
|
||||
static const GFlagsValue flag_values[] = {
|
||||
{GST_JACK_TRANSPORT_MASTER,
|
||||
"Start and stop transport with state changes", "master"},
|
||||
{GST_JACK_TRANSPORT_SLAVE,
|
||||
"Follow transport state changes", "slave"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
GType tmp = g_flags_register_static ("GstJackTransport", flag_values);
|
||||
g_once_init_leave (&type, tmp);
|
||||
}
|
||||
return (GType) type;
|
||||
}
|
||||
|
||||
|
||||
static gpointer
|
||||
gst_jack_client_copy (gpointer jclient)
|
||||
{
|
||||
return jclient;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gst_jack_client_free (gpointer jclient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
GType
|
||||
gst_jack_client_get_type (void)
|
||||
{
|
||||
static gsize jack_client_type = 0;
|
||||
|
||||
if (g_once_init_enter (&jack_client_type)) {
|
||||
/* hackish, but makes it show up nicely in gst-inspect */
|
||||
GType tmp = g_boxed_type_register_static ("JackClient",
|
||||
(GBoxedCopyFunc) gst_jack_client_copy,
|
||||
(GBoxedFreeFunc) gst_jack_client_free);
|
||||
g_once_init_leave (&jack_client_type, tmp);
|
||||
}
|
||||
|
||||
return (GType) jack_client_type;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (jackaudiosrc, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (jackaudiosink, plugin);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
jack,
|
||||
"JACK audio elements",
|
||||
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
92
ext/jack/gstjack.h
Normal file
92
ext/jack/gstjack.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* gstjack.h:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _GST_JACK_H_
|
||||
#define _GST_JACK_H_
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <gst/audio/audio.h>
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (jackaudiosrc);
|
||||
GST_ELEMENT_REGISTER_DECLARE (jackaudiosink);
|
||||
|
||||
/**
|
||||
* GstJackConnect:
|
||||
* @GST_JACK_CONNECT_NONE: Don't automatically connect to physical ports.
|
||||
* In this mode, the element will accept any number of input channels and will
|
||||
* create (but not connect) an output port for each channel.
|
||||
* @GST_JACK_CONNECT_AUTO: In this mode, the element will try to connect each
|
||||
* output port to a random physical jack input pin. The sink will
|
||||
* expose the number of physical channels on its pad caps.
|
||||
* @GST_JACK_CONNECT_AUTO_FORCED: In this mode, the element will try to connect each
|
||||
* output port to a random physical jack input pin. The element will accept any number
|
||||
* of input channels.
|
||||
*
|
||||
* Specify how the output ports will be connected.
|
||||
*/
|
||||
typedef enum {
|
||||
GST_JACK_CONNECT_NONE,
|
||||
GST_JACK_CONNECT_AUTO,
|
||||
GST_JACK_CONNECT_AUTO_FORCED,
|
||||
|
||||
/**
|
||||
* GstJackConnect::explicit
|
||||
*
|
||||
* In this mode, the element will try to connect to explicitly requested
|
||||
* port specified by "port-names".
|
||||
*
|
||||
* Since: 1.20
|
||||
*/
|
||||
GST_JACK_CONNECT_EXPLICIT,
|
||||
} GstJackConnect;
|
||||
|
||||
/**
|
||||
* GstJackTransport:
|
||||
* @GST_JACK_TRANSPORT_AUTONOMOUS: no transport support
|
||||
* @GST_JACK_TRANSPORT_MASTER: start and stop transport with state-changes
|
||||
* @GST_JACK_TRANSPORT_SLAVE: follow transport state changes
|
||||
*
|
||||
* The jack transport state allow to sync multiple clients. This enum defines a
|
||||
* client behaviour regarding to the transport mechanism.
|
||||
*/
|
||||
typedef enum {
|
||||
GST_JACK_TRANSPORT_AUTONOMOUS = 0,
|
||||
GST_JACK_TRANSPORT_MASTER = (1 << 0),
|
||||
GST_JACK_TRANSPORT_SLAVE = (1 << 1),
|
||||
} GstJackTransport;
|
||||
|
||||
typedef jack_default_audio_sample_t sample_t;
|
||||
|
||||
#define GST_TYPE_JACK_CONNECT (gst_jack_connect_get_type ())
|
||||
#define GST_TYPE_JACK_TRANSPORT (gst_jack_transport_get_type ())
|
||||
#define GST_TYPE_JACK_CLIENT (gst_jack_client_get_type ())
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
#define GST_JACK_FORMAT_STR "F32LE"
|
||||
#else
|
||||
#define GST_JACK_FORMAT_STR "F32BE"
|
||||
#endif
|
||||
|
||||
GType gst_jack_client_get_type(void);
|
||||
GType gst_jack_connect_get_type(void);
|
||||
GType gst_jack_transport_get_type(void);
|
||||
|
||||
#endif // _GST_JACK_H_
|
688
ext/jack/gstjackaudioclient.c
Normal file
688
ext/jack/gstjackaudioclient.c
Normal file
|
@ -0,0 +1,688 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* gstjackaudioclient.c: jack audio client implementation
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gstjackaudioclient.h"
|
||||
#include "gstjack.h"
|
||||
|
||||
#include <gst/glib-compat-private.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_jack_audio_client_debug);
|
||||
#define GST_CAT_DEFAULT gst_jack_audio_client_debug
|
||||
|
||||
static void
|
||||
jack_log_error (const gchar * msg)
|
||||
{
|
||||
GST_ERROR ("%s", msg);
|
||||
}
|
||||
|
||||
static void
|
||||
jack_info_error (const gchar * msg)
|
||||
{
|
||||
GST_INFO ("%s", msg);
|
||||
}
|
||||
|
||||
void
|
||||
gst_jack_audio_client_init (void)
|
||||
{
|
||||
GST_DEBUG_CATEGORY_INIT (gst_jack_audio_client_debug, "jackclient", 0,
|
||||
"jackclient helpers");
|
||||
|
||||
jack_set_error_function (jack_log_error);
|
||||
jack_set_info_function (jack_info_error);
|
||||
}
|
||||
|
||||
/* a list of global connections indexed by id and server. */
|
||||
G_LOCK_DEFINE_STATIC (connections_lock);
|
||||
static GList *connections;
|
||||
|
||||
/* the connection to a server */
|
||||
typedef struct
|
||||
{
|
||||
gint refcount;
|
||||
GMutex lock;
|
||||
GCond flush_cond;
|
||||
|
||||
/* id/server pair and the connection */
|
||||
gchar *id;
|
||||
gchar *server;
|
||||
jack_client_t *client;
|
||||
|
||||
/* lists of GstJackAudioClients */
|
||||
gint n_clients;
|
||||
GList *src_clients;
|
||||
GList *sink_clients;
|
||||
|
||||
/* transport state handling */
|
||||
gint cur_ts;
|
||||
GstState transport_state;
|
||||
} GstJackAudioConnection;
|
||||
|
||||
/* an object sharing a jack_client_t connection. */
|
||||
struct _GstJackAudioClient
|
||||
{
|
||||
GstJackAudioConnection *conn;
|
||||
|
||||
GstJackClientType type;
|
||||
gboolean active;
|
||||
gboolean deactivate;
|
||||
gboolean server_down;
|
||||
|
||||
JackShutdownCallback shutdown;
|
||||
JackProcessCallback process;
|
||||
JackBufferSizeCallback buffer_size;
|
||||
JackSampleRateCallback sample_rate;
|
||||
gpointer user_data;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
jack_nframes_t nframes;
|
||||
gpointer user_data;
|
||||
} JackCB;
|
||||
|
||||
static gboolean
|
||||
jack_handle_transport_change (GstJackAudioClient * client, GstState state)
|
||||
{
|
||||
GstObject *obj = GST_OBJECT_PARENT (client->user_data);
|
||||
guint mode;
|
||||
|
||||
g_object_get (obj, "transport", &mode, NULL);
|
||||
if ((mode & GST_JACK_TRANSPORT_SLAVE) && (GST_STATE (obj) != state)) {
|
||||
GST_INFO_OBJECT (obj, "requesting state change: %s",
|
||||
gst_element_state_get_name (state));
|
||||
gst_element_post_message (GST_ELEMENT (obj),
|
||||
gst_message_new_request_state (obj, state));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int
|
||||
jack_process_cb (jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
GstJackAudioConnection *conn = (GstJackAudioConnection *) arg;
|
||||
GList *walk;
|
||||
int res = 0;
|
||||
jack_transport_state_t ts = jack_transport_query (conn->client, NULL);
|
||||
|
||||
if (ts != conn->cur_ts) {
|
||||
conn->cur_ts = ts;
|
||||
switch (ts) {
|
||||
case JackTransportStopped:
|
||||
GST_DEBUG ("transport state is 'stopped'");
|
||||
conn->transport_state = GST_STATE_PAUSED;
|
||||
break;
|
||||
case JackTransportStarting:
|
||||
GST_DEBUG ("transport state is 'starting'");
|
||||
conn->transport_state = GST_STATE_READY;
|
||||
break;
|
||||
case JackTransportRolling:
|
||||
GST_DEBUG ("transport state is 'rolling'");
|
||||
conn->transport_state = GST_STATE_PLAYING;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
GST_DEBUG ("num of clients: src=%d, sink=%d",
|
||||
g_list_length (conn->src_clients), g_list_length (conn->sink_clients));
|
||||
}
|
||||
|
||||
g_mutex_lock (&conn->lock);
|
||||
/* call sources first, then sinks. Sources will either push data into the
|
||||
* ringbuffer of the sinks, which will then pull the data out of it, or
|
||||
* sinks will pull the data from the sources. */
|
||||
for (walk = conn->src_clients; walk; walk = g_list_next (walk)) {
|
||||
GstJackAudioClient *client = (GstJackAudioClient *) walk->data;
|
||||
|
||||
/* only call active clients */
|
||||
if ((client->active || client->deactivate) && client->process) {
|
||||
res = client->process (nframes, client->user_data);
|
||||
if (client->deactivate) {
|
||||
client->deactivate = FALSE;
|
||||
g_cond_signal (&conn->flush_cond);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) {
|
||||
GstJackAudioClient *client = (GstJackAudioClient *) walk->data;
|
||||
|
||||
/* only call active clients */
|
||||
if ((client->active || client->deactivate) && client->process) {
|
||||
res = client->process (nframes, client->user_data);
|
||||
if (client->deactivate) {
|
||||
client->deactivate = FALSE;
|
||||
g_cond_signal (&conn->flush_cond);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* handle transport state requisition, do sinks first, stop after the first
|
||||
* element that handled it */
|
||||
if (conn->transport_state != GST_STATE_VOID_PENDING) {
|
||||
for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) {
|
||||
if (jack_handle_transport_change ((GstJackAudioClient *) walk->data,
|
||||
conn->transport_state)) {
|
||||
conn->transport_state = GST_STATE_VOID_PENDING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conn->transport_state != GST_STATE_VOID_PENDING) {
|
||||
for (walk = conn->src_clients; walk; walk = g_list_next (walk)) {
|
||||
if (jack_handle_transport_change ((GstJackAudioClient *) walk->data,
|
||||
conn->transport_state)) {
|
||||
conn->transport_state = GST_STATE_VOID_PENDING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_mutex_unlock (&conn->lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
jack_shutdown_cb (void *arg)
|
||||
{
|
||||
GstJackAudioConnection *conn = (GstJackAudioConnection *) arg;
|
||||
GList *walk;
|
||||
|
||||
GST_DEBUG ("disconnect client %s from server %s", conn->id,
|
||||
GST_STR_NULL (conn->server));
|
||||
|
||||
g_mutex_lock (&conn->lock);
|
||||
for (walk = conn->src_clients; walk; walk = g_list_next (walk)) {
|
||||
GstJackAudioClient *client = (GstJackAudioClient *) walk->data;
|
||||
|
||||
client->server_down = TRUE;
|
||||
g_cond_signal (&conn->flush_cond);
|
||||
if (client->shutdown)
|
||||
client->shutdown (client->user_data);
|
||||
}
|
||||
for (walk = conn->sink_clients; walk; walk = g_list_next (walk)) {
|
||||
GstJackAudioClient *client = (GstJackAudioClient *) walk->data;
|
||||
|
||||
client->server_down = TRUE;
|
||||
g_cond_signal (&conn->flush_cond);
|
||||
if (client->shutdown)
|
||||
client->shutdown (client->user_data);
|
||||
}
|
||||
g_mutex_unlock (&conn->lock);
|
||||
}
|
||||
|
||||
/* we error out */
|
||||
static int
|
||||
jack_sample_rate_cb (jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
jack_shutdown_cb (arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we error out */
|
||||
static int
|
||||
jack_buffer_size_cb (jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
jack_shutdown_cb (arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const gchar *id;
|
||||
const gchar *server;
|
||||
} FindData;
|
||||
|
||||
static gint
|
||||
connection_find (GstJackAudioConnection * conn, FindData * data)
|
||||
{
|
||||
/* id's must match */
|
||||
if (strcmp (conn->id, data->id))
|
||||
return 1;
|
||||
|
||||
/* both the same or NULL */
|
||||
if (conn->server == data->server)
|
||||
return 0;
|
||||
|
||||
/* we cannot compare NULL */
|
||||
if (conn->server == NULL || data->server == NULL)
|
||||
return 1;
|
||||
|
||||
if (strcmp (conn->server, data->server))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* make a connection with @id and @server. Returns NULL on failure with the
|
||||
* status set. */
|
||||
static GstJackAudioConnection *
|
||||
gst_jack_audio_make_connection (const gchar * id, const gchar * server,
|
||||
jack_client_t * jclient, jack_status_t * status)
|
||||
{
|
||||
GstJackAudioConnection *conn;
|
||||
jack_options_t options;
|
||||
gint res;
|
||||
|
||||
*status = 0;
|
||||
|
||||
GST_DEBUG ("new client %s, connecting to server %s", id,
|
||||
GST_STR_NULL (server));
|
||||
|
||||
/* never start a server */
|
||||
options = JackNoStartServer;
|
||||
/* if we have a servername, use it */
|
||||
if (server != NULL)
|
||||
options |= JackServerName;
|
||||
/* open the client */
|
||||
if (jclient == NULL)
|
||||
jclient = jack_client_open (id, options, status, server);
|
||||
if (jclient == NULL)
|
||||
goto could_not_open;
|
||||
|
||||
/* now create object */
|
||||
conn = g_new (GstJackAudioConnection, 1);
|
||||
conn->refcount = 1;
|
||||
g_mutex_init (&conn->lock);
|
||||
g_cond_init (&conn->flush_cond);
|
||||
conn->id = g_strdup (id);
|
||||
conn->server = g_strdup (server);
|
||||
conn->client = jclient;
|
||||
conn->n_clients = 0;
|
||||
conn->src_clients = NULL;
|
||||
conn->sink_clients = NULL;
|
||||
conn->cur_ts = -1;
|
||||
conn->transport_state = GST_STATE_VOID_PENDING;
|
||||
|
||||
/* set our callbacks */
|
||||
jack_set_process_callback (jclient, jack_process_cb, conn);
|
||||
/* these callbacks cause us to error */
|
||||
jack_set_buffer_size_callback (jclient, jack_buffer_size_cb, conn);
|
||||
jack_set_sample_rate_callback (jclient, jack_sample_rate_cb, conn);
|
||||
jack_on_shutdown (jclient, jack_shutdown_cb, conn);
|
||||
|
||||
/* all callbacks are set, activate the client */
|
||||
GST_INFO ("activate jack_client %p", jclient);
|
||||
if ((res = jack_activate (jclient)))
|
||||
goto could_not_activate;
|
||||
|
||||
GST_DEBUG ("opened connection %p", conn);
|
||||
|
||||
return conn;
|
||||
|
||||
/* ERRORS */
|
||||
could_not_open:
|
||||
{
|
||||
GST_DEBUG ("failed to open jack client, %d", *status);
|
||||
return NULL;
|
||||
}
|
||||
could_not_activate:
|
||||
{
|
||||
GST_ERROR ("Could not activate client (%d)", res);
|
||||
*status = JackFailure;
|
||||
g_mutex_clear (&conn->lock);
|
||||
g_free (conn->id);
|
||||
g_free (conn->server);
|
||||
g_free (conn);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static GstJackAudioConnection *
|
||||
gst_jack_audio_get_connection (const gchar * id, const gchar * server,
|
||||
jack_client_t * jclient, jack_status_t * status)
|
||||
{
|
||||
GstJackAudioConnection *conn;
|
||||
GList *found;
|
||||
FindData data;
|
||||
|
||||
GST_DEBUG ("getting connection for id %s, server %s", id,
|
||||
GST_STR_NULL (server));
|
||||
|
||||
data.id = id;
|
||||
data.server = server;
|
||||
|
||||
G_LOCK (connections_lock);
|
||||
found =
|
||||
g_list_find_custom (connections, &data, (GCompareFunc) connection_find);
|
||||
if (found != NULL && jclient != NULL) {
|
||||
/* we found it, increase refcount and return it */
|
||||
conn = (GstJackAudioConnection *) found->data;
|
||||
conn->refcount++;
|
||||
|
||||
GST_DEBUG ("found connection %p", conn);
|
||||
} else {
|
||||
/* make new connection */
|
||||
conn = gst_jack_audio_make_connection (id, server, jclient, status);
|
||||
if (conn != NULL) {
|
||||
GST_DEBUG ("created connection %p", conn);
|
||||
/* add to list on success */
|
||||
connections = g_list_prepend (connections, conn);
|
||||
} else {
|
||||
GST_WARNING ("could not create connection");
|
||||
}
|
||||
}
|
||||
G_UNLOCK (connections_lock);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jack_audio_unref_connection (GstJackAudioConnection * conn)
|
||||
{
|
||||
gint res;
|
||||
gboolean zero;
|
||||
|
||||
GST_DEBUG ("unref connection %p refcnt %d", conn, conn->refcount);
|
||||
|
||||
G_LOCK (connections_lock);
|
||||
conn->refcount--;
|
||||
if ((zero = (conn->refcount == 0))) {
|
||||
GST_DEBUG ("closing connection %p", conn);
|
||||
/* remove from list, we can release the mutex after removing the connection
|
||||
* from the list because after that, nobody can access the connection anymore. */
|
||||
connections = g_list_remove (connections, conn);
|
||||
}
|
||||
G_UNLOCK (connections_lock);
|
||||
|
||||
/* if we are zero, close and cleanup the connection */
|
||||
if (zero) {
|
||||
/* don't use conn->lock here. two reasons:
|
||||
*
|
||||
* 1) its not necessary: jack_deactivate() will not return until the JACK thread
|
||||
* associated with this connection is cleaned up by a thread join, hence
|
||||
* no more callbacks can occur or be in progress.
|
||||
*
|
||||
* 2) it would deadlock anyway, because jack_deactivate() will sleep
|
||||
* waiting for the JACK thread, and can thus cause deadlock in
|
||||
* jack_process_cb()
|
||||
*/
|
||||
GST_INFO ("deactivate jack_client %p", conn->client);
|
||||
if ((res = jack_deactivate (conn->client))) {
|
||||
/* we only warn, this means the server is probably shut down and the client
|
||||
* is gone anyway. */
|
||||
GST_WARNING ("Could not deactivate Jack client (%d)", res);
|
||||
}
|
||||
/* close connection */
|
||||
if ((res = jack_client_close (conn->client))) {
|
||||
/* we assume the client is gone. */
|
||||
GST_WARNING ("close failed (%d)", res);
|
||||
}
|
||||
|
||||
/* free resources */
|
||||
g_mutex_clear (&conn->lock);
|
||||
g_cond_clear (&conn->flush_cond);
|
||||
g_free (conn->id);
|
||||
g_free (conn->server);
|
||||
g_free (conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jack_audio_connection_add_client (GstJackAudioConnection * conn,
|
||||
GstJackAudioClient * client)
|
||||
{
|
||||
g_mutex_lock (&conn->lock);
|
||||
switch (client->type) {
|
||||
case GST_JACK_CLIENT_SOURCE:
|
||||
conn->src_clients = g_list_append (conn->src_clients, client);
|
||||
conn->n_clients++;
|
||||
break;
|
||||
case GST_JACK_CLIENT_SINK:
|
||||
conn->sink_clients = g_list_append (conn->sink_clients, client);
|
||||
conn->n_clients++;
|
||||
break;
|
||||
default:
|
||||
g_warning ("trying to add unknown client type");
|
||||
break;
|
||||
}
|
||||
g_mutex_unlock (&conn->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jack_audio_connection_remove_client (GstJackAudioConnection * conn,
|
||||
GstJackAudioClient * client)
|
||||
{
|
||||
g_mutex_lock (&conn->lock);
|
||||
switch (client->type) {
|
||||
case GST_JACK_CLIENT_SOURCE:
|
||||
conn->src_clients = g_list_remove (conn->src_clients, client);
|
||||
conn->n_clients--;
|
||||
break;
|
||||
case GST_JACK_CLIENT_SINK:
|
||||
conn->sink_clients = g_list_remove (conn->sink_clients, client);
|
||||
conn->n_clients--;
|
||||
break;
|
||||
default:
|
||||
g_warning ("trying to remove unknown client type");
|
||||
break;
|
||||
}
|
||||
g_mutex_unlock (&conn->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_get:
|
||||
* @id: the client id
|
||||
* @server: the server to connect to or NULL for the default server
|
||||
* @type: the client type
|
||||
* @shutdown: a callback when the jack server shuts down
|
||||
* @process: a callback when samples are available
|
||||
* @buffer_size: a callback when the buffer_size changes
|
||||
* @sample_rate: a callback when the sample_rate changes
|
||||
* @user_data: user data passed to the callbacks
|
||||
* @status: pointer to hold the jack status code in case of errors
|
||||
*
|
||||
* Get the jack client connection for @id and @server. Connections to the same
|
||||
* @id and @server will receive the same physical Jack client connection and
|
||||
* will therefore be scheduled in the same process callback.
|
||||
*
|
||||
* Returns: a #GstJackAudioClient.
|
||||
*/
|
||||
GstJackAudioClient *
|
||||
gst_jack_audio_client_new (const gchar * id, const gchar * server,
|
||||
jack_client_t * jclient, GstJackClientType type,
|
||||
void (*shutdown) (void *arg), JackProcessCallback process,
|
||||
JackBufferSizeCallback buffer_size, JackSampleRateCallback sample_rate,
|
||||
gpointer user_data, jack_status_t * status)
|
||||
{
|
||||
GstJackAudioClient *client;
|
||||
GstJackAudioConnection *conn;
|
||||
|
||||
g_return_val_if_fail (id != NULL, NULL);
|
||||
g_return_val_if_fail (status != NULL, NULL);
|
||||
|
||||
/* first get a connection for the id/server pair */
|
||||
conn = gst_jack_audio_get_connection (id, server, jclient, status);
|
||||
if (conn == NULL)
|
||||
goto no_connection;
|
||||
|
||||
GST_INFO ("new client %s", id);
|
||||
|
||||
/* make new client using the connection */
|
||||
client = g_new (GstJackAudioClient, 1);
|
||||
client->active = client->deactivate = FALSE;
|
||||
client->conn = conn;
|
||||
client->type = type;
|
||||
client->shutdown = shutdown;
|
||||
client->process = process;
|
||||
client->buffer_size = buffer_size;
|
||||
client->sample_rate = sample_rate;
|
||||
client->user_data = user_data;
|
||||
client->server_down = FALSE;
|
||||
|
||||
/* add the client to the connection */
|
||||
gst_jack_audio_connection_add_client (conn, client);
|
||||
|
||||
return client;
|
||||
|
||||
/* ERRORS */
|
||||
no_connection:
|
||||
{
|
||||
GST_DEBUG ("Could not get server connection (%d)", *status);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_free:
|
||||
* @client: a #GstJackAudioClient
|
||||
*
|
||||
* Free the resources used by @client.
|
||||
*/
|
||||
void
|
||||
gst_jack_audio_client_free (GstJackAudioClient * client)
|
||||
{
|
||||
GstJackAudioConnection *conn;
|
||||
|
||||
g_return_if_fail (client != NULL);
|
||||
|
||||
GST_INFO ("free client");
|
||||
|
||||
conn = client->conn;
|
||||
|
||||
/* remove from connection first so that it's not scheduled anymore after this
|
||||
* call */
|
||||
gst_jack_audio_connection_remove_client (conn, client);
|
||||
gst_jack_audio_unref_connection (conn);
|
||||
|
||||
g_free (client);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_get_client:
|
||||
* @client: a #GstJackAudioClient
|
||||
*
|
||||
* Get the jack audio client for @client. This function is used to perform
|
||||
* operations on the jack server from this client.
|
||||
*
|
||||
* Returns: The jack audio client.
|
||||
*/
|
||||
jack_client_t *
|
||||
gst_jack_audio_client_get_client (GstJackAudioClient * client)
|
||||
{
|
||||
g_return_val_if_fail (client != NULL, NULL);
|
||||
|
||||
/* no lock needed, the connection and the client does not change
|
||||
* once the client is created. */
|
||||
return client->conn->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_set_active:
|
||||
* @client: a #GstJackAudioClient
|
||||
* @active: new mode for the client
|
||||
*
|
||||
* Activate or deactivate @client. When a client is activated it will receive
|
||||
* callbacks when data should be processed.
|
||||
*
|
||||
* Returns: 0 if all ok.
|
||||
*/
|
||||
gint
|
||||
gst_jack_audio_client_set_active (GstJackAudioClient * client, gboolean active)
|
||||
{
|
||||
g_return_val_if_fail (client != NULL, -1);
|
||||
|
||||
/* make sure that we are not dispatching the client */
|
||||
g_mutex_lock (&client->conn->lock);
|
||||
if (client->active && !active) {
|
||||
/* we need to process once more to flush the port */
|
||||
client->deactivate = TRUE;
|
||||
|
||||
/* need to wait for process_cb run once more */
|
||||
while (client->deactivate && !client->server_down)
|
||||
g_cond_wait (&client->conn->flush_cond, &client->conn->lock);
|
||||
}
|
||||
client->active = active;
|
||||
g_mutex_unlock (&client->conn->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_get_transport_state:
|
||||
* @client: a #GstJackAudioClient
|
||||
*
|
||||
* Check the current transport state. The client can use this to request a state
|
||||
* change from the application.
|
||||
*
|
||||
* Returns: the state, %GST_STATE_VOID_PENDING for no change in the transport
|
||||
* state
|
||||
*/
|
||||
GstState
|
||||
gst_jack_audio_client_get_transport_state (GstJackAudioClient * client)
|
||||
{
|
||||
GstState state = client->conn->transport_state;
|
||||
|
||||
client->conn->transport_state = GST_STATE_VOID_PENDING;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_jack_audio_client_get_port_names_from_string:
|
||||
* @jclient: a jack_client_t handle
|
||||
* @port_names: comma-separated jack port name(s)
|
||||
* @port_flags: JackPortFlags
|
||||
*
|
||||
* Returns: a newly-allocated %NULL-terminated array of strings or %NULL
|
||||
* if @port_names contains invalid port name. Use g_strfreev() to free it.
|
||||
*/
|
||||
gchar **
|
||||
gst_jack_audio_client_get_port_names_from_string (jack_client_t * jclient,
|
||||
const gchar * port_names, gint port_flags)
|
||||
{
|
||||
gchar **p = NULL;
|
||||
guint i, len;
|
||||
|
||||
g_return_val_if_fail (jclient != NULL, NULL);
|
||||
|
||||
if (!port_names)
|
||||
return NULL;
|
||||
|
||||
p = g_strsplit (port_names, ",", 0);
|
||||
len = g_strv_length (p);
|
||||
|
||||
if (len < 1)
|
||||
goto invalid;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
jack_port_t *port = jack_port_by_name (jclient, p[i]);
|
||||
int flags;
|
||||
|
||||
if (!port) {
|
||||
GST_WARNING ("Couldn't get jack port by name %s", p[i]);
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
flags = jack_port_flags (port);
|
||||
if ((flags & port_flags) != port_flags) {
|
||||
GST_WARNING ("Port flags 0x%x doesn't match expected flags 0x%x",
|
||||
flags, port_flags);
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
|
||||
invalid:
|
||||
g_strfreev (p);
|
||||
return NULL;
|
||||
}
|
65
ext/jack/gstjackaudioclient.h
Normal file
65
ext/jack/gstjackaudioclient.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* gstjackaudioclient.h:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_JACK_AUDIO_CLIENT_H__
|
||||
#define __GST_JACK_AUDIO_CLIENT_H__
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_JACK_CLIENT_SOURCE,
|
||||
GST_JACK_CLIENT_SINK
|
||||
} GstJackClientType;
|
||||
|
||||
typedef struct _GstJackAudioClient GstJackAudioClient;
|
||||
|
||||
void gst_jack_audio_client_init (void);
|
||||
|
||||
|
||||
GstJackAudioClient * gst_jack_audio_client_new (const gchar *id, const gchar *server,
|
||||
jack_client_t *jclient,
|
||||
GstJackClientType type,
|
||||
void (*shutdown) (void *arg),
|
||||
JackProcessCallback process,
|
||||
JackBufferSizeCallback buffer_size,
|
||||
JackSampleRateCallback sample_rate,
|
||||
gpointer user_data,
|
||||
jack_status_t *status);
|
||||
void gst_jack_audio_client_free (GstJackAudioClient *client);
|
||||
|
||||
jack_client_t * gst_jack_audio_client_get_client (GstJackAudioClient *client);
|
||||
|
||||
gboolean gst_jack_audio_client_set_active (GstJackAudioClient *client, gboolean active);
|
||||
|
||||
GstState gst_jack_audio_client_get_transport_state (GstJackAudioClient *client);
|
||||
|
||||
gchar ** gst_jack_audio_client_get_port_names_from_string (jack_client_t *jclient,
|
||||
const gchar *port_names,
|
||||
gint port_flags);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_JACK_AUDIO_CLIENT_H__ */
|
1106
ext/jack/gstjackaudiosink.c
Normal file
1106
ext/jack/gstjackaudiosink.c
Normal file
File diff suppressed because it is too large
Load diff
72
ext/jack/gstjackaudiosink.h
Normal file
72
ext/jack/gstjackaudiosink.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
*
|
||||
* gstjacksink.h:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_JACK_AUDIO_SINK_H__
|
||||
#define __GST_JACK_AUDIO_SINK_H__
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiobasesink.h>
|
||||
|
||||
#include "gstjack.h"
|
||||
#include "gstjackaudioclient.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_JACK_AUDIO_SINK (gst_jack_audio_sink_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstJackAudioSink, gst_jack_audio_sink,
|
||||
GST, JACK_AUDIO_SINK, GstAudioBaseSink)
|
||||
|
||||
/**
|
||||
* GstJackAudioSink:
|
||||
*
|
||||
* Opaque #GstJackAudioSink.
|
||||
*/
|
||||
struct _GstJackAudioSink {
|
||||
GstAudioBaseSink element;
|
||||
|
||||
/*< private >*/
|
||||
/* cached caps */
|
||||
GstCaps *caps;
|
||||
|
||||
/* properties */
|
||||
GstJackConnect connect;
|
||||
gchar *server;
|
||||
jack_client_t *jclient;
|
||||
gchar *client_name;
|
||||
gchar *port_pattern;
|
||||
guint transport;
|
||||
gboolean low_latency;
|
||||
gchar *port_names;
|
||||
|
||||
/* our client */
|
||||
GstJackAudioClient *client;
|
||||
|
||||
/* our ports */
|
||||
jack_port_t **ports;
|
||||
int port_count;
|
||||
sample_t **buffers;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_JACK_AUDIO_SINK_H__ */
|
1118
ext/jack/gstjackaudiosrc.c
Normal file
1118
ext/jack/gstjackaudiosrc.c
Normal file
File diff suppressed because it is too large
Load diff
89
ext/jack/gstjackaudiosrc.h
Normal file
89
ext/jack/gstjackaudiosrc.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2008 Tristan Matthews <tristan@sat.qc.ca>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the
|
||||
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
||||
* which case the following provisions apply instead of the ones
|
||||
* mentioned above:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_JACK_AUDIO_SRC_H__
|
||||
#define __GST_JACK_AUDIO_SRC_H__
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiosrc.h>
|
||||
|
||||
#include "gstjackaudioclient.h"
|
||||
#include "gstjack.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_JACK_AUDIO_SRC (gst_jack_audio_src_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstJackAudioSrc, gst_jack_audio_src,
|
||||
GST, JACK_AUDIO_SRC, GstAudioBaseSrc)
|
||||
|
||||
struct _GstJackAudioSrc
|
||||
{
|
||||
GstAudioBaseSrc src;
|
||||
|
||||
/*< private >*/
|
||||
/* cached caps */
|
||||
GstCaps *caps;
|
||||
|
||||
/* properties */
|
||||
GstJackConnect connect;
|
||||
gchar *server;
|
||||
jack_client_t *jclient;
|
||||
gchar *client_name;
|
||||
gchar *port_pattern;
|
||||
guint transport;
|
||||
gboolean low_latency;
|
||||
gchar *port_names;
|
||||
|
||||
/* our client */
|
||||
GstJackAudioClient *client;
|
||||
|
||||
/* our ports */
|
||||
jack_port_t **ports;
|
||||
int port_count;
|
||||
sample_t **buffers;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_JACK_AUDIO_SRC_H__ */
|
88
ext/jack/gstjackringbuffer.h
Normal file
88
ext/jack/gstjackringbuffer.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2006 Wim Taymans <wim@fluendo.com>
|
||||
* Copyright (C) 2008 Tristan Matthews <tristan@sat.qc.ca>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the
|
||||
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
||||
* which case the following provisions apply instead of the ones
|
||||
* mentioned above:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_JACK_RING_BUFFER_H__
|
||||
#define __GST_JACK_RING_BUFFER_H__
|
||||
|
||||
#define GST_TYPE_JACK_RING_BUFFER (gst_jack_ring_buffer_get_type())
|
||||
#define GST_JACK_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JACK_RING_BUFFER,GstJackRingBuffer))
|
||||
#define GST_JACK_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JACK_RING_BUFFER,GstJackRingBufferClass))
|
||||
#define GST_JACK_RING_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_JACK_RING_BUFFER,GstJackRingBufferClass))
|
||||
#define GST_JACK_RING_BUFFER_CAST(obj) ((GstJackRingBuffer *)obj)
|
||||
#define GST_IS_JACK_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JACK_RING_BUFFER))
|
||||
#define GST_IS_JACK_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JACK_RING_BUFFER))
|
||||
|
||||
typedef struct _GstJackRingBuffer GstJackRingBuffer;
|
||||
typedef struct _GstJackRingBufferClass GstJackRingBufferClass;
|
||||
|
||||
struct _GstJackRingBuffer
|
||||
{
|
||||
GstAudioRingBuffer object;
|
||||
|
||||
gint sample_rate;
|
||||
gint buffer_size;
|
||||
gint channels;
|
||||
};
|
||||
|
||||
struct _GstJackRingBufferClass
|
||||
{
|
||||
GstAudioRingBufferClass parent_class;
|
||||
};
|
||||
|
||||
static void gst_jack_ring_buffer_class_init(GstJackRingBufferClass * klass);
|
||||
static void gst_jack_ring_buffer_init(GstJackRingBuffer * ringbuffer,
|
||||
GstJackRingBufferClass * klass);
|
||||
|
||||
static GstAudioRingBufferClass *ring_parent_class = NULL;
|
||||
|
||||
static gboolean gst_jack_ring_buffer_open_device(GstAudioRingBuffer * buf);
|
||||
static gboolean gst_jack_ring_buffer_close_device(GstAudioRingBuffer * buf);
|
||||
static gboolean gst_jack_ring_buffer_acquire(GstAudioRingBuffer * buf,GstAudioRingBufferSpec * spec);
|
||||
static gboolean gst_jack_ring_buffer_release(GstAudioRingBuffer * buf);
|
||||
static gboolean gst_jack_ring_buffer_start(GstAudioRingBuffer * buf);
|
||||
static gboolean gst_jack_ring_buffer_pause(GstAudioRingBuffer * buf);
|
||||
static gboolean gst_jack_ring_buffer_stop(GstAudioRingBuffer * buf);
|
||||
static guint gst_jack_ring_buffer_delay(GstAudioRingBuffer * buf);
|
||||
|
||||
#endif
|
110
ext/jack/gstjackutil.c
Normal file
110
ext/jack/gstjackutil.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/* GStreamer Jack utility functions
|
||||
* Copyright (C) 2010 Tristan Matthews <tristan@sat.qc.ca>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "gstjackutil.h"
|
||||
#include <gst/audio/audio.h>
|
||||
|
||||
static const GstAudioChannelPosition default_positions[8][8] = {
|
||||
/* 1 channel */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_MONO,
|
||||
},
|
||||
/* 2 channels */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
},
|
||||
/* 3 channels (2.1) */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1, /* or FRONT_CENTER for 3.0? */
|
||||
},
|
||||
/* 4 channels (4.0 or 3.1?) */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
},
|
||||
/* 5 channels */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
},
|
||||
/* 6 channels */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
},
|
||||
/* 7 channels */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
|
||||
},
|
||||
/* 8 channels */
|
||||
{
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
||||
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
||||
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
||||
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* if channels are less than or equal to 8, we set a default layout,
|
||||
* otherwise set layout to an array of GST_AUDIO_CHANNEL_POSITION_NONE */
|
||||
void
|
||||
gst_jack_set_layout (GstAudioRingBuffer * buffer, GstAudioRingBufferSpec * spec)
|
||||
{
|
||||
gint i;
|
||||
|
||||
if (spec->info.channels <= 8) {
|
||||
for (i = 0; i < spec->info.channels; i++)
|
||||
spec->info.position[i] = default_positions[spec->info.channels - 1][i];
|
||||
gst_audio_channel_positions_to_valid_order (spec->info.position,
|
||||
spec->info.channels);
|
||||
gst_audio_ring_buffer_set_channel_positions (buffer,
|
||||
default_positions[spec->info.channels - 1]);
|
||||
} else {
|
||||
spec->info.flags |= GST_AUDIO_FLAG_UNPOSITIONED;
|
||||
for (i = 0; i < G_N_ELEMENTS (spec->info.position); i++)
|
||||
spec->info.position[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
||||
gst_audio_ring_buffer_set_channel_positions (buffer, spec->info.position);
|
||||
}
|
||||
|
||||
gst_caps_unref (spec->caps);
|
||||
spec->caps = gst_audio_info_to_caps (&spec->info);
|
||||
}
|
31
ext/jack/gstjackutil.h
Normal file
31
ext/jack/gstjackutil.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2010 Tristan Matthews <tristan@sat.qc.ca>
|
||||
*
|
||||
* gstjackutil.h:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _GST_JACK_UTIL_H_
|
||||
#define _GST_JACK_UTIL_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/audio.h>
|
||||
|
||||
void
|
||||
gst_jack_set_layout (GstAudioRingBuffer * buffer, GstAudioRingBufferSpec *spec);
|
||||
|
||||
#endif // _GST_JACK_UTIL_H_
|
22
ext/jack/meson.build
Normal file
22
ext/jack/meson.build
Normal file
|
@ -0,0 +1,22 @@
|
|||
jack_sources = [
|
||||
'gstjackaudioclient.c',
|
||||
'gstjackaudiosink.c',
|
||||
'gstjackaudiosrc.c',
|
||||
'gstjack.c',
|
||||
'gstjackutil.c',
|
||||
]
|
||||
|
||||
libjack_dep = dependency('jack', version : '>= 1.9.7', required : get_option('jack'))
|
||||
|
||||
if libjack_dep.found()
|
||||
gstjack = library('gstjack',
|
||||
jack_sources,
|
||||
c_args : gst_plugins_good_args + ['-DHAVE_JACK_1_9_7'],
|
||||
include_directories : [configinc, libsinc],
|
||||
dependencies : [gst_dep, gstbase_dep, gstaudio_dep, libjack_dep],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstjack, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstjack]
|
||||
endif
|
20
ext/jpeg/README
Normal file
20
ext/jpeg/README
Normal file
|
@ -0,0 +1,20 @@
|
|||
The Smoke Codec
|
||||
---------------
|
||||
|
||||
This is a very simple compression algorithm I was toying with when doing a
|
||||
Java based player. Decoding a JPEG in Java has acceptable speed so this codec
|
||||
tries to exploit that feature. The algorithm first compares the last and the
|
||||
new image and finds all 16x16 blocks that have a squared difference bigger than
|
||||
a configurable threshold. Then all these blocks are compressed into an NxM JPEG.
|
||||
The quality of the JPEG is inversely proportional to the number of blocks, this
|
||||
way, the picture quality degrades with heavy motion scenes but the bitrate stays
|
||||
more or less constant.
|
||||
Decoding decompresses the JPEG and then updates the old picture with the new
|
||||
blocks.
|
||||
|
||||
|
||||
TODO:
|
||||
----
|
||||
- make format extensible
|
||||
- motion vectors
|
||||
- do some real bitrate control
|
46
ext/jpeg/gstjpeg.c
Normal file
46
ext/jpeg/gstjpeg.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstjpeg.h"
|
||||
|
||||
GType
|
||||
gst_idct_method_get_type (void)
|
||||
{
|
||||
static GType idct_method_type = 0;
|
||||
static const GEnumValue idct_method[] = {
|
||||
{JDCT_ISLOW, "Slow but accurate integer algorithm", "islow"},
|
||||
{JDCT_IFAST, "Faster, less accurate integer method", "ifast"},
|
||||
{JDCT_FLOAT, "Floating-point: accurate, fast on fast HW", "float"},
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
|
||||
if (!idct_method_type) {
|
||||
idct_method_type = g_enum_register_static ("GstIDCTMethod", idct_method);
|
||||
}
|
||||
return idct_method_type;
|
||||
}
|
35
ext/jpeg/gstjpeg.h
Normal file
35
ext/jpeg/gstjpeg.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_JPEG_H__
|
||||
#define __GST_JPEG_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
#define GST_TYPE_IDCT_METHOD (gst_idct_method_get_type())
|
||||
GType gst_idct_method_get_type (void);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_JPEG_H__ */
|
1593
ext/jpeg/gstjpegdec.c
Normal file
1593
ext/jpeg/gstjpegdec.c
Normal file
File diff suppressed because it is too large
Load diff
110
ext/jpeg/gstjpegdec.h
Normal file
110
ext/jpeg/gstjpegdec.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2012 Collabora Ltd.
|
||||
* Author : Edward Hervey <edward@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_JPEG_DEC_H__
|
||||
#define __GST_JPEG_DEC_H__
|
||||
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideodecoder.h>
|
||||
#include <gst/base/gstadapter.h>
|
||||
|
||||
/* this is a hack hack hack to get around jpeglib header bugs... */
|
||||
#ifdef HAVE_STDLIB_H
|
||||
# undef HAVE_STDLIB_H
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_JPEG_DEC \
|
||||
(gst_jpeg_dec_get_type())
|
||||
#define GST_JPEG_DEC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JPEG_DEC,GstJpegDec))
|
||||
#define GST_JPEG_DEC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JPEG_DEC,GstJpegDecClass))
|
||||
#define GST_IS_JPEG_DEC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JPEG_DEC))
|
||||
#define GST_IS_JPEG_DEC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JPEG_DEC))
|
||||
|
||||
typedef struct _GstJpegDec GstJpegDec;
|
||||
typedef struct _GstJpegDecClass GstJpegDecClass;
|
||||
|
||||
struct GstJpegDecErrorMgr {
|
||||
struct jpeg_error_mgr pub; /* public fields */
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
struct GstJpegDecSourceMgr {
|
||||
struct jpeg_source_mgr pub; /* public fields */
|
||||
GstJpegDec *dec;
|
||||
};
|
||||
|
||||
/* Can't use GstBaseTransform, because GstBaseTransform
|
||||
* doesn't handle the N buffers in, 1 buffer out case,
|
||||
* but only the 1-in 1-out case */
|
||||
struct _GstJpegDec {
|
||||
GstVideoDecoder decoder;
|
||||
|
||||
/* negotiated state */
|
||||
GstVideoCodecState *input_state;
|
||||
GstVideoCodecFrame *current_frame;
|
||||
GstMapInfo current_frame_map;
|
||||
|
||||
/* parse state */
|
||||
gboolean saw_header;
|
||||
gint parse_entropy_len;
|
||||
gint parse_resync;
|
||||
|
||||
/* properties */
|
||||
gint idct_method;
|
||||
gint max_errors; /* ATOMIC */
|
||||
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct GstJpegDecErrorMgr jerr;
|
||||
struct GstJpegDecSourceMgr jsrc;
|
||||
|
||||
/* arrays for indirect decoding */
|
||||
gboolean idr_width_allocated;
|
||||
guchar *idr_y[16],*idr_u[16],*idr_v[16];
|
||||
/* scratch buffer for direct decoding overflow */
|
||||
guchar *scratch;
|
||||
guint scratch_size;
|
||||
/* current (parsed) image size */
|
||||
guint rem_img_len;
|
||||
};
|
||||
|
||||
struct _GstJpegDecClass {
|
||||
GstVideoDecoderClass decoder_class;
|
||||
};
|
||||
|
||||
GType gst_jpeg_dec_get_type(void);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
#endif /* __GST_JPEG_DEC_H__ */
|
37
ext/jpeg/gstjpegelements.h
Normal file
37
ext/jpeg/gstjpegelements.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2020 Huawei Technologies Co., Ltd.
|
||||
* @Author: Stéphane Cerveau <scerveau@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_JPEG_ELEMENTS_H__
|
||||
#define __GST_JPEG_ELEMENTS_H__
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (jpegenc);
|
||||
GST_ELEMENT_REGISTER_DECLARE (jpegdec);
|
||||
#if 0
|
||||
GST_ELEMENT_REGISTER_DECLARE (smokeenc);
|
||||
GST_ELEMENT_REGISTER_DECLARE (smokedec);
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_JPEG_ELEMENTS_H__ */
|
660
ext/jpeg/gstjpegenc.c
Normal file
660
ext/jpeg/gstjpegenc.c
Normal file
|
@ -0,0 +1,660 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2012 Collabora Ltd.
|
||||
* Author : Edward Hervey <edward@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
/**
|
||||
* SECTION:element-jpegenc
|
||||
* @title: jpegenc
|
||||
*
|
||||
* Encodes jpeg images.
|
||||
*
|
||||
* ## Example launch line
|
||||
* |[
|
||||
* gst-launch-1.0 videotestsrc num-buffers=50 ! video/x-raw, framerate='(fraction)'5/1 ! jpegenc ! avimux ! filesink location=mjpeg.avi
|
||||
* ]| a pipeline to mux 5 JPEG frames per second into a 10 sec. long motion jpeg
|
||||
* avi.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
#include "gstjpeg.h"
|
||||
#include "gstjpegenc.h"
|
||||
#include "gstjpegelements.h"
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideometa.h>
|
||||
#include <gst/base/base.h>
|
||||
|
||||
/* experimental */
|
||||
/* setting smoothig seems to have no effect in libjepeg
|
||||
#define ENABLE_SMOOTHING 1
|
||||
*/
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (jpegenc_debug);
|
||||
#define GST_CAT_DEFAULT jpegenc_debug
|
||||
|
||||
#define JPEG_DEFAULT_QUALITY 85
|
||||
#define JPEG_DEFAULT_SMOOTHING 0
|
||||
#define JPEG_DEFAULT_IDCT_METHOD JDCT_FASTEST
|
||||
#define JPEG_DEFAULT_SNAPSHOT FALSE
|
||||
|
||||
/* JpegEnc signals and args */
|
||||
enum
|
||||
{
|
||||
/* FILL ME */
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_QUALITY,
|
||||
PROP_SMOOTHING,
|
||||
PROP_IDCT_METHOD,
|
||||
PROP_SNAPSHOT
|
||||
};
|
||||
|
||||
static void gst_jpegenc_finalize (GObject * object);
|
||||
|
||||
static void gst_jpegenc_resync (GstJpegEnc * jpegenc);
|
||||
static void gst_jpegenc_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_jpegenc_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static gboolean gst_jpegenc_start (GstVideoEncoder * benc);
|
||||
static gboolean gst_jpegenc_stop (GstVideoEncoder * benc);
|
||||
static gboolean gst_jpegenc_set_format (GstVideoEncoder * encoder,
|
||||
GstVideoCodecState * state);
|
||||
static GstFlowReturn gst_jpegenc_handle_frame (GstVideoEncoder * encoder,
|
||||
GstVideoCodecFrame * frame);
|
||||
static gboolean gst_jpegenc_propose_allocation (GstVideoEncoder * encoder,
|
||||
GstQuery * query);
|
||||
|
||||
/* static guint gst_jpegenc_signals[LAST_SIGNAL] = { 0 }; */
|
||||
|
||||
#define gst_jpegenc_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstJpegEnc, gst_jpegenc, GST_TYPE_VIDEO_ENCODER);
|
||||
GST_ELEMENT_REGISTER_DEFINE (jpegenc, "jpegenc", GST_RANK_PRIMARY,
|
||||
GST_TYPE_JPEGENC);
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
static GstStaticPadTemplate gst_jpegenc_sink_pad_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
|
||||
("{ I420, YV12, YUY2, UYVY, Y41B, Y42B, YVYU, Y444, NV21, "
|
||||
"NV12, RGB, BGR, RGBx, xRGB, BGRx, xBGR, GRAY8 }"))
|
||||
);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static GstStaticPadTemplate gst_jpegenc_src_pad_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("image/jpeg, "
|
||||
"width = (int) [ 1, 65535 ], "
|
||||
"height = (int) [ 1, 65535 ], "
|
||||
"framerate = (fraction) [ 0/1, MAX ], "
|
||||
"sof-marker = (int) { 0, 1, 2, 4, 9 }")
|
||||
);
|
||||
|
||||
static void
|
||||
gst_jpegenc_class_init (GstJpegEncClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *element_class;
|
||||
GstVideoEncoderClass *venc_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
element_class = (GstElementClass *) klass;
|
||||
venc_class = (GstVideoEncoderClass *) klass;
|
||||
|
||||
parent_class = g_type_class_peek_parent (klass);
|
||||
|
||||
gobject_class->finalize = gst_jpegenc_finalize;
|
||||
gobject_class->set_property = gst_jpegenc_set_property;
|
||||
gobject_class->get_property = gst_jpegenc_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_QUALITY,
|
||||
g_param_spec_int ("quality", "Quality", "Quality of encoding",
|
||||
0, 100, JPEG_DEFAULT_QUALITY,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
||||
GST_PARAM_MUTABLE_PLAYING));
|
||||
|
||||
#ifdef ENABLE_SMOOTHING
|
||||
/* disabled, since it doesn't seem to work */
|
||||
g_object_class_install_property (gobject_class, PROP_SMOOTHING,
|
||||
g_param_spec_int ("smoothing", "Smoothing", "Smoothing factor",
|
||||
0, 100, JPEG_DEFAULT_SMOOTHING,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
#endif
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_IDCT_METHOD,
|
||||
g_param_spec_enum ("idct-method", "IDCT Method",
|
||||
"The IDCT algorithm to use", GST_TYPE_IDCT_METHOD,
|
||||
JPEG_DEFAULT_IDCT_METHOD,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* GstJpegEnc:snapshot:
|
||||
*
|
||||
* Send EOS after encoding a frame, useful for snapshots.
|
||||
*
|
||||
* Since: 1.14
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_SNAPSHOT,
|
||||
g_param_spec_boolean ("snapshot", "Snapshot",
|
||||
"Send EOS after encoding a frame, useful for snapshots",
|
||||
JPEG_DEFAULT_SNAPSHOT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_jpegenc_sink_pad_template);
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_jpegenc_src_pad_template);
|
||||
|
||||
gst_element_class_set_static_metadata (element_class, "JPEG image encoder",
|
||||
"Codec/Encoder/Image", "Encode images in JPEG format",
|
||||
"Wim Taymans <wim.taymans@tvd.be>");
|
||||
|
||||
venc_class->start = gst_jpegenc_start;
|
||||
venc_class->stop = gst_jpegenc_stop;
|
||||
venc_class->set_format = gst_jpegenc_set_format;
|
||||
venc_class->handle_frame = gst_jpegenc_handle_frame;
|
||||
venc_class->propose_allocation = gst_jpegenc_propose_allocation;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (jpegenc_debug, "jpegenc", 0,
|
||||
"JPEG encoding element");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_init_destination (j_compress_ptr cinfo)
|
||||
{
|
||||
GST_DEBUG ("gst_jpegenc_chain: init_destination");
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_memory (GstJpegEnc * jpegenc)
|
||||
{
|
||||
GstMemory *new_memory;
|
||||
GstMapInfo map;
|
||||
gsize old_size, desired_size, new_size;
|
||||
guint8 *new_data;
|
||||
static GstAllocationParams params = { 0, 3, 0, 0, };
|
||||
|
||||
old_size = jpegenc->output_map.size;
|
||||
if (old_size == 0)
|
||||
desired_size = jpegenc->bufsize;
|
||||
else
|
||||
desired_size = old_size * 2;
|
||||
|
||||
/* Our output memory wasn't big enough.
|
||||
* Make a new memory that's twice the size, */
|
||||
new_memory = gst_allocator_alloc (NULL, desired_size, ¶ms);
|
||||
gst_memory_map (new_memory, &map, GST_MAP_READWRITE);
|
||||
new_data = map.data;
|
||||
new_size = map.size;
|
||||
|
||||
/* copy previous data if any */
|
||||
if (jpegenc->output_mem) {
|
||||
memcpy (new_data, jpegenc->output_map.data, old_size);
|
||||
gst_memory_unmap (jpegenc->output_mem, &jpegenc->output_map);
|
||||
gst_memory_unref (jpegenc->output_mem);
|
||||
}
|
||||
|
||||
/* drop it into place, */
|
||||
jpegenc->output_mem = new_memory;
|
||||
jpegenc->output_map = map;
|
||||
|
||||
/* and last, update libjpeg on where to work. */
|
||||
jpegenc->jdest.next_output_byte = new_data + old_size;
|
||||
jpegenc->jdest.free_in_buffer = new_size - old_size;
|
||||
}
|
||||
|
||||
static boolean
|
||||
gst_jpegenc_flush_destination (j_compress_ptr cinfo)
|
||||
{
|
||||
GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data);
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc,
|
||||
"gst_jpegenc_chain: flush_destination: buffer too small");
|
||||
|
||||
ensure_memory (jpegenc);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_term_destination (j_compress_ptr cinfo)
|
||||
{
|
||||
GstBuffer *outbuf;
|
||||
GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data);
|
||||
gsize memory_size = jpegenc->output_map.size - jpegenc->jdest.free_in_buffer;
|
||||
GstByteReader reader =
|
||||
GST_BYTE_READER_INIT (jpegenc->output_map.data, memory_size);
|
||||
guint16 marker;
|
||||
gint sof_marker = -1;
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc, "gst_jpegenc_chain: term_source");
|
||||
|
||||
/* Find the SOF marker */
|
||||
while (gst_byte_reader_get_uint16_be (&reader, &marker)) {
|
||||
/* SOF marker */
|
||||
if (marker >> 4 == 0x0ffc) {
|
||||
sof_marker = marker & 0x4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gst_memory_unmap (jpegenc->output_mem, &jpegenc->output_map);
|
||||
/* Trim the buffer size. we will push it in the chain function */
|
||||
gst_memory_resize (jpegenc->output_mem, 0, memory_size);
|
||||
jpegenc->output_map.data = NULL;
|
||||
jpegenc->output_map.size = 0;
|
||||
|
||||
if (jpegenc->sof_marker != sof_marker || jpegenc->input_caps_changed) {
|
||||
GstVideoCodecState *output;
|
||||
output =
|
||||
gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (jpegenc),
|
||||
gst_caps_new_simple ("image/jpeg", "sof-marker", G_TYPE_INT, sof_marker,
|
||||
NULL), jpegenc->input_state);
|
||||
gst_video_codec_state_unref (output);
|
||||
jpegenc->sof_marker = sof_marker;
|
||||
jpegenc->input_caps_changed = FALSE;
|
||||
}
|
||||
|
||||
outbuf = gst_buffer_new ();
|
||||
gst_buffer_append_memory (outbuf, jpegenc->output_mem);
|
||||
jpegenc->output_mem = NULL;
|
||||
|
||||
jpegenc->current_frame->output_buffer = outbuf;
|
||||
|
||||
gst_video_frame_unmap (&jpegenc->current_vframe);
|
||||
|
||||
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (jpegenc->current_frame);
|
||||
|
||||
jpegenc->res = gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (jpegenc),
|
||||
jpegenc->current_frame);
|
||||
jpegenc->current_frame = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_init (GstJpegEnc * jpegenc)
|
||||
{
|
||||
GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (jpegenc));
|
||||
|
||||
/* setup jpeglib */
|
||||
memset (&jpegenc->cinfo, 0, sizeof (jpegenc->cinfo));
|
||||
memset (&jpegenc->jerr, 0, sizeof (jpegenc->jerr));
|
||||
jpegenc->cinfo.err = jpeg_std_error (&jpegenc->jerr);
|
||||
jpeg_create_compress (&jpegenc->cinfo);
|
||||
|
||||
jpegenc->jdest.init_destination = gst_jpegenc_init_destination;
|
||||
jpegenc->jdest.empty_output_buffer = gst_jpegenc_flush_destination;
|
||||
jpegenc->jdest.term_destination = gst_jpegenc_term_destination;
|
||||
jpegenc->cinfo.dest = &jpegenc->jdest;
|
||||
jpegenc->cinfo.client_data = jpegenc;
|
||||
|
||||
/* init properties */
|
||||
jpegenc->quality = JPEG_DEFAULT_QUALITY;
|
||||
jpegenc->smoothing = JPEG_DEFAULT_SMOOTHING;
|
||||
jpegenc->idct_method = JPEG_DEFAULT_IDCT_METHOD;
|
||||
jpegenc->snapshot = JPEG_DEFAULT_SNAPSHOT;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_finalize (GObject * object)
|
||||
{
|
||||
GstJpegEnc *filter = GST_JPEGENC (object);
|
||||
|
||||
jpeg_destroy_compress (&filter->cinfo);
|
||||
|
||||
if (filter->input_state)
|
||||
gst_video_codec_state_unref (filter->input_state);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_jpegenc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
|
||||
{
|
||||
GstJpegEnc *enc = GST_JPEGENC (encoder);
|
||||
gint i;
|
||||
GstVideoInfo *info = &state->info;
|
||||
|
||||
if (enc->input_state)
|
||||
gst_video_codec_state_unref (enc->input_state);
|
||||
enc->input_state = gst_video_codec_state_ref (state);
|
||||
|
||||
/* prepare a cached image description */
|
||||
enc->channels = GST_VIDEO_INFO_N_COMPONENTS (info);
|
||||
|
||||
/* ... but any alpha is disregarded in encoding */
|
||||
if (GST_VIDEO_INFO_IS_GRAY (info))
|
||||
enc->channels = 1;
|
||||
|
||||
enc->h_max_samp = 0;
|
||||
enc->v_max_samp = 0;
|
||||
for (i = 0; i < enc->channels; ++i) {
|
||||
enc->cwidth[i] = GST_VIDEO_INFO_COMP_WIDTH (info, i);
|
||||
enc->cheight[i] = GST_VIDEO_INFO_COMP_HEIGHT (info, i);
|
||||
enc->inc[i] = GST_VIDEO_INFO_COMP_PSTRIDE (info, i);
|
||||
enc->h_samp[i] =
|
||||
GST_ROUND_UP_4 (GST_VIDEO_INFO_WIDTH (info)) / enc->cwidth[i];
|
||||
enc->h_max_samp = MAX (enc->h_max_samp, enc->h_samp[i]);
|
||||
enc->v_samp[i] =
|
||||
GST_ROUND_UP_4 (GST_VIDEO_INFO_HEIGHT (info)) / enc->cheight[i];
|
||||
enc->v_max_samp = MAX (enc->v_max_samp, enc->v_samp[i]);
|
||||
}
|
||||
/* samp should only be 1, 2 or 4 */
|
||||
g_assert (enc->h_max_samp <= 4);
|
||||
g_assert (enc->v_max_samp <= 4);
|
||||
|
||||
/* now invert */
|
||||
/* maximum is invariant, as one of the components should have samp 1 */
|
||||
for (i = 0; i < enc->channels; ++i) {
|
||||
GST_DEBUG ("%d %d", enc->h_samp[i], enc->h_max_samp);
|
||||
enc->h_samp[i] = enc->h_max_samp / enc->h_samp[i];
|
||||
enc->v_samp[i] = enc->v_max_samp / enc->v_samp[i];
|
||||
}
|
||||
enc->planar = (enc->inc[0] == 1 && enc->inc[1] == 1 && enc->inc[2] == 1);
|
||||
|
||||
enc->input_caps_changed = TRUE;
|
||||
gst_jpegenc_resync (enc);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_resync (GstJpegEnc * jpegenc)
|
||||
{
|
||||
GstVideoInfo *info;
|
||||
gint width, height;
|
||||
gint i, j;
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc, "resync");
|
||||
|
||||
if (!jpegenc->input_state)
|
||||
return;
|
||||
|
||||
info = &jpegenc->input_state->info;
|
||||
|
||||
jpegenc->cinfo.image_width = width = GST_VIDEO_INFO_WIDTH (info);
|
||||
jpegenc->cinfo.image_height = height = GST_VIDEO_INFO_HEIGHT (info);
|
||||
jpegenc->cinfo.input_components = jpegenc->channels;
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc, "width %d, height %d", width, height);
|
||||
GST_DEBUG_OBJECT (jpegenc, "format %d", GST_VIDEO_INFO_FORMAT (info));
|
||||
|
||||
if (GST_VIDEO_INFO_IS_RGB (info)) {
|
||||
GST_DEBUG_OBJECT (jpegenc, "RGB");
|
||||
jpegenc->cinfo.in_color_space = JCS_RGB;
|
||||
} else if (GST_VIDEO_INFO_IS_GRAY (info)) {
|
||||
GST_DEBUG_OBJECT (jpegenc, "gray");
|
||||
jpegenc->cinfo.in_color_space = JCS_GRAYSCALE;
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (jpegenc, "YUV");
|
||||
jpegenc->cinfo.in_color_space = JCS_YCbCr;
|
||||
}
|
||||
|
||||
/* input buffer size as max output */
|
||||
jpegenc->bufsize = GST_VIDEO_INFO_SIZE (info);
|
||||
jpeg_set_defaults (&jpegenc->cinfo);
|
||||
jpegenc->cinfo.raw_data_in = TRUE;
|
||||
/* duh, libjpeg maps RGB to YUV ... and don't expect some conversion */
|
||||
if (jpegenc->cinfo.in_color_space == JCS_RGB)
|
||||
jpeg_set_colorspace (&jpegenc->cinfo, JCS_RGB);
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc, "h_max_samp=%d, v_max_samp=%d",
|
||||
jpegenc->h_max_samp, jpegenc->v_max_samp);
|
||||
/* image dimension info */
|
||||
for (i = 0; i < jpegenc->channels; i++) {
|
||||
GST_DEBUG_OBJECT (jpegenc, "comp %i: h_samp=%d, v_samp=%d", i,
|
||||
jpegenc->h_samp[i], jpegenc->v_samp[i]);
|
||||
jpegenc->cinfo.comp_info[i].h_samp_factor = jpegenc->h_samp[i];
|
||||
jpegenc->cinfo.comp_info[i].v_samp_factor = jpegenc->v_samp[i];
|
||||
g_free (jpegenc->line[i]);
|
||||
jpegenc->line[i] = g_new (guchar *, jpegenc->v_max_samp * DCTSIZE);
|
||||
if (!jpegenc->planar) {
|
||||
for (j = 0; j < jpegenc->v_max_samp * DCTSIZE; j++) {
|
||||
g_free (jpegenc->row[i][j]);
|
||||
jpegenc->row[i][j] = g_malloc (width);
|
||||
jpegenc->line[i][j] = jpegenc->row[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* guard against a potential error in gst_jpegenc_term_destination
|
||||
which occurs iff bufsize % 4 < free_space_remaining */
|
||||
jpegenc->bufsize = GST_ROUND_UP_4 (jpegenc->bufsize);
|
||||
|
||||
jpeg_suppress_tables (&jpegenc->cinfo, TRUE);
|
||||
|
||||
GST_DEBUG_OBJECT (jpegenc, "resync done");
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_jpegenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
|
||||
{
|
||||
GstJpegEnc *jpegenc;
|
||||
guint height;
|
||||
guchar *base[3], *end[3];
|
||||
guint stride[3];
|
||||
gint i, j, k;
|
||||
static GstAllocationParams params = { 0, 0, 0, 3, };
|
||||
|
||||
jpegenc = GST_JPEGENC (encoder);
|
||||
|
||||
GST_LOG_OBJECT (jpegenc, "got new frame");
|
||||
|
||||
if (!gst_video_frame_map (&jpegenc->current_vframe,
|
||||
&jpegenc->input_state->info, frame->input_buffer, GST_MAP_READ))
|
||||
goto invalid_frame;
|
||||
|
||||
jpegenc->current_frame = frame;
|
||||
|
||||
height = GST_VIDEO_INFO_HEIGHT (&jpegenc->input_state->info);
|
||||
|
||||
for (i = 0; i < jpegenc->channels; i++) {
|
||||
base[i] = GST_VIDEO_FRAME_COMP_DATA (&jpegenc->current_vframe, i);
|
||||
stride[i] = GST_VIDEO_FRAME_COMP_STRIDE (&jpegenc->current_vframe, i);
|
||||
end[i] =
|
||||
base[i] + GST_VIDEO_FRAME_COMP_HEIGHT (&jpegenc->current_vframe,
|
||||
i) * stride[i];
|
||||
}
|
||||
|
||||
jpegenc->res = GST_FLOW_OK;
|
||||
jpegenc->output_mem = gst_allocator_alloc (NULL, jpegenc->bufsize, ¶ms);
|
||||
gst_memory_map (jpegenc->output_mem, &jpegenc->output_map, GST_MAP_READWRITE);
|
||||
|
||||
jpegenc->jdest.next_output_byte = jpegenc->output_map.data;
|
||||
jpegenc->jdest.free_in_buffer = jpegenc->output_map.size;
|
||||
|
||||
/* prepare for raw input */
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
jpegenc->cinfo.do_fancy_downsampling = FALSE;
|
||||
#endif
|
||||
|
||||
GST_OBJECT_LOCK (jpegenc);
|
||||
jpegenc->cinfo.smoothing_factor = jpegenc->smoothing;
|
||||
jpegenc->cinfo.dct_method = jpegenc->idct_method;
|
||||
jpeg_set_quality (&jpegenc->cinfo, jpegenc->quality, TRUE);
|
||||
GST_OBJECT_UNLOCK (jpegenc);
|
||||
|
||||
jpeg_start_compress (&jpegenc->cinfo, TRUE);
|
||||
|
||||
GST_LOG_OBJECT (jpegenc, "compressing");
|
||||
|
||||
if (jpegenc->planar) {
|
||||
for (i = 0; i < height; i += jpegenc->v_max_samp * DCTSIZE) {
|
||||
for (k = 0; k < jpegenc->channels; k++) {
|
||||
for (j = 0; j < jpegenc->v_samp[k] * DCTSIZE; j++) {
|
||||
jpegenc->line[k][j] = base[k];
|
||||
if (base[k] + stride[k] < end[k])
|
||||
base[k] += stride[k];
|
||||
}
|
||||
}
|
||||
jpeg_write_raw_data (&jpegenc->cinfo, jpegenc->line,
|
||||
jpegenc->v_max_samp * DCTSIZE);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < height; i += jpegenc->v_max_samp * DCTSIZE) {
|
||||
for (k = 0; k < jpegenc->channels; k++) {
|
||||
for (j = 0; j < jpegenc->v_samp[k] * DCTSIZE; j++) {
|
||||
guchar *src, *dst;
|
||||
gint l;
|
||||
|
||||
/* ouch, copy line */
|
||||
src = base[k];
|
||||
dst = jpegenc->line[k][j];
|
||||
for (l = jpegenc->cwidth[k]; l > 0; l--) {
|
||||
*dst = *src;
|
||||
src += jpegenc->inc[k];
|
||||
dst++;
|
||||
}
|
||||
if (base[k] + stride[k] < end[k])
|
||||
base[k] += stride[k];
|
||||
}
|
||||
}
|
||||
jpeg_write_raw_data (&jpegenc->cinfo, jpegenc->line,
|
||||
jpegenc->v_max_samp * DCTSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/* This will ensure that gst_jpegenc_term_destination is called */
|
||||
jpeg_finish_compress (&jpegenc->cinfo);
|
||||
GST_LOG_OBJECT (jpegenc, "compressing done");
|
||||
|
||||
return (jpegenc->snapshot) ? GST_FLOW_EOS : jpegenc->res;
|
||||
|
||||
invalid_frame:
|
||||
{
|
||||
GST_WARNING_OBJECT (jpegenc, "invalid frame received");
|
||||
return gst_video_encoder_finish_frame (encoder, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_jpegenc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
|
||||
{
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
|
||||
|
||||
return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
|
||||
query);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstJpegEnc *jpegenc = GST_JPEGENC (object);
|
||||
|
||||
GST_OBJECT_LOCK (jpegenc);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_QUALITY:
|
||||
jpegenc->quality = g_value_get_int (value);
|
||||
break;
|
||||
#ifdef ENABLE_SMOOTHING
|
||||
case PROP_SMOOTHING:
|
||||
jpegenc->smoothing = g_value_get_int (value);
|
||||
break;
|
||||
#endif
|
||||
case PROP_IDCT_METHOD:
|
||||
jpegenc->idct_method = g_value_get_enum (value);
|
||||
break;
|
||||
case PROP_SNAPSHOT:
|
||||
jpegenc->snapshot = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (jpegenc);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_jpegenc_get_property (GObject * object, guint prop_id, GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstJpegEnc *jpegenc = GST_JPEGENC (object);
|
||||
|
||||
GST_OBJECT_LOCK (jpegenc);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_QUALITY:
|
||||
g_value_set_int (value, jpegenc->quality);
|
||||
break;
|
||||
#ifdef ENABLE_SMOOTHING
|
||||
case PROP_SMOOTHING:
|
||||
g_value_set_int (value, jpegenc->smoothing);
|
||||
break;
|
||||
#endif
|
||||
case PROP_IDCT_METHOD:
|
||||
g_value_set_enum (value, jpegenc->idct_method);
|
||||
break;
|
||||
case PROP_SNAPSHOT:
|
||||
g_value_set_boolean (value, jpegenc->snapshot);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (jpegenc);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_jpegenc_start (GstVideoEncoder * benc)
|
||||
{
|
||||
GstJpegEnc *enc = (GstJpegEnc *) benc;
|
||||
|
||||
enc->line[0] = NULL;
|
||||
enc->line[1] = NULL;
|
||||
enc->line[2] = NULL;
|
||||
enc->sof_marker = -1;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_jpegenc_stop (GstVideoEncoder * benc)
|
||||
{
|
||||
GstJpegEnc *enc = (GstJpegEnc *) benc;
|
||||
gint i, j;
|
||||
|
||||
g_free (enc->line[0]);
|
||||
g_free (enc->line[1]);
|
||||
g_free (enc->line[2]);
|
||||
enc->line[0] = NULL;
|
||||
enc->line[1] = NULL;
|
||||
enc->line[2] = NULL;
|
||||
for (i = 0; i < 3; i++) {
|
||||
for (j = 0; j < 4 * DCTSIZE; j++) {
|
||||
g_free (enc->row[i][j]);
|
||||
enc->row[i][j] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
103
ext/jpeg/gstjpegenc.h
Normal file
103
ext/jpeg/gstjpegenc.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) 2012 Collabora Ltd.
|
||||
* Author : Edward Hervey <edward@collabora.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_JPEGENC_H__
|
||||
#define __GST_JPEGENC_H__
|
||||
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/video/gstvideoencoder.h>
|
||||
/* this is a hack hack hack to get around jpeglib header bugs... */
|
||||
#ifdef HAVE_STDLIB_H
|
||||
# undef HAVE_STDLIB_H
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
#define GST_TYPE_JPEGENC \
|
||||
(gst_jpegenc_get_type())
|
||||
#define GST_JPEGENC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JPEGENC,GstJpegEnc))
|
||||
#define GST_JPEGENC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JPEGENC,GstJpegEncClass))
|
||||
#define GST_IS_JPEGENC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JPEGENC))
|
||||
#define GST_IS_JPEGENC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JPEGENC))
|
||||
|
||||
typedef struct _GstJpegEnc GstJpegEnc;
|
||||
typedef struct _GstJpegEncClass GstJpegEncClass;
|
||||
|
||||
struct _GstJpegEnc
|
||||
{
|
||||
GstVideoEncoder encoder;
|
||||
|
||||
GstVideoCodecState *input_state;
|
||||
GstVideoFrame current_vframe;
|
||||
GstVideoCodecFrame *current_frame;
|
||||
GstFlowReturn res;
|
||||
|
||||
gboolean input_caps_changed;
|
||||
|
||||
guint channels;
|
||||
|
||||
gint inc[GST_VIDEO_MAX_COMPONENTS];
|
||||
gint cwidth[GST_VIDEO_MAX_COMPONENTS];
|
||||
gint cheight[GST_VIDEO_MAX_COMPONENTS];
|
||||
gint h_samp[GST_VIDEO_MAX_COMPONENTS];
|
||||
gint v_samp[GST_VIDEO_MAX_COMPONENTS];
|
||||
gint h_max_samp;
|
||||
gint v_max_samp;
|
||||
gboolean planar;
|
||||
gint sof_marker;
|
||||
/* the video buffer */
|
||||
gint bufsize;
|
||||
/* the jpeg line buffer */
|
||||
guchar **line[3];
|
||||
/* indirect encoding line buffers */
|
||||
guchar *row[3][4 * DCTSIZE];
|
||||
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
struct jpeg_destination_mgr jdest;
|
||||
|
||||
/* properties */
|
||||
gint quality;
|
||||
gint smoothing;
|
||||
gint idct_method;
|
||||
gboolean snapshot;
|
||||
|
||||
GstMemory *output_mem;
|
||||
GstMapInfo output_map;
|
||||
};
|
||||
|
||||
struct _GstJpegEncClass
|
||||
{
|
||||
GstVideoEncoderClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_jpegenc_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
#endif /* __GST_JPEGENC_H__ */
|
52
ext/jpeg/gstjpegplugin.c
Normal file
52
ext/jpeg/gstjpegplugin.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstjpegelements.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
ret |= GST_ELEMENT_REGISTER (jpegenc, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (jpegdec, plugin);
|
||||
|
||||
#if 0
|
||||
ret |= GST_ELEMENT_REGISTER (smokeenc, plugin);
|
||||
ret |= GST_ELEMENT_REGISTER (smokedec, plugin);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
jpeg,
|
||||
"JPeg plugin library",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
336
ext/jpeg/gstsmokedec.c
Normal file
336
ext/jpeg/gstsmokedec.c
Normal file
|
@ -0,0 +1,336 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-smokedec
|
||||
* @title: smokedec
|
||||
*
|
||||
* Decodes images in smoke format.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
/*#define DEBUG_ENABLED*/
|
||||
#include "gstsmokedec.h"
|
||||
#include <gst/video/video.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (smokedec_debug);
|
||||
#define GST_CAT_DEFAULT smokedec_debug
|
||||
|
||||
/* SmokeDec signals and args */
|
||||
enum
|
||||
{
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0
|
||||
};
|
||||
|
||||
static void gst_smokedec_base_init (gpointer g_class);
|
||||
static void gst_smokedec_class_init (GstSmokeDec * klass);
|
||||
static void gst_smokedec_init (GstSmokeDec * smokedec);
|
||||
static void gst_smokedec_finalize (GObject * object);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_smokedec_change_state (GstElement * element, GstStateChange transition);
|
||||
|
||||
static GstFlowReturn gst_smokedec_chain (GstPad * pad, GstBuffer * buf);
|
||||
|
||||
static GstElementClass *parent_class = NULL;
|
||||
|
||||
/*static guint gst_smokedec_signals[LAST_SIGNAL] = { 0 }; */
|
||||
|
||||
GType
|
||||
gst_smokedec_get_type (void)
|
||||
{
|
||||
static GType smokedec_type = 0;
|
||||
|
||||
if (!smokedec_type) {
|
||||
static const GTypeInfo smokedec_info = {
|
||||
sizeof (GstSmokeDecClass),
|
||||
gst_smokedec_base_init,
|
||||
NULL,
|
||||
(GClassInitFunc) gst_smokedec_class_init,
|
||||
NULL,
|
||||
NULL,
|
||||
sizeof (GstSmokeDec),
|
||||
0,
|
||||
(GInstanceInitFunc) gst_smokedec_init,
|
||||
};
|
||||
|
||||
smokedec_type =
|
||||
g_type_register_static (GST_TYPE_ELEMENT, "GstSmokeDec", &smokedec_info,
|
||||
0);
|
||||
}
|
||||
return smokedec_type;
|
||||
}
|
||||
|
||||
GST_ELEMENT_REGISTER_DEFINE (smokedec, "smokedec", GST_RANK_PRIMARY,
|
||||
GST_TYPE_SMOKEDEC);
|
||||
|
||||
static GstStaticPadTemplate gst_smokedec_src_pad_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate gst_smokedec_sink_pad_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-smoke, "
|
||||
"width = (int) [ 16, 4096 ], "
|
||||
"height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0/1, MAX ]")
|
||||
);
|
||||
|
||||
static void
|
||||
gst_smokedec_base_init (gpointer g_class)
|
||||
{
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
||||
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_smokedec_src_pad_template);
|
||||
gst_element_class_add_static_pad_template (element_class,
|
||||
&gst_smokedec_sink_pad_template);
|
||||
gst_element_class_set_static_metadata (element_class, "Smoke video decoder",
|
||||
"Codec/Decoder/Video", "Decode video from Smoke format",
|
||||
"Wim Taymans <wim@fluendo.com>");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_smokedec_class_init (GstSmokeDec * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
|
||||
parent_class = g_type_class_peek_parent (klass);
|
||||
|
||||
gobject_class->finalize = gst_smokedec_finalize;
|
||||
|
||||
gstelement_class->change_state =
|
||||
GST_DEBUG_FUNCPTR (gst_smokedec_change_state);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (smokedec_debug, "smokedec", 0, "Smoke decoder");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_smokedec_init (GstSmokeDec * smokedec)
|
||||
{
|
||||
GST_DEBUG_OBJECT (smokedec, "gst_smokedec_init: initializing");
|
||||
/* create the sink and src pads */
|
||||
|
||||
smokedec->sinkpad =
|
||||
gst_pad_new_from_static_template (&gst_smokedec_sink_pad_template,
|
||||
"sink");
|
||||
gst_pad_set_chain_function (smokedec->sinkpad, gst_smokedec_chain);
|
||||
gst_element_add_pad (GST_ELEMENT (smokedec), smokedec->sinkpad);
|
||||
|
||||
smokedec->srcpad =
|
||||
gst_pad_new_from_static_template (&gst_smokedec_src_pad_template, "src");
|
||||
gst_pad_use_fixed_caps (smokedec->srcpad);
|
||||
gst_element_add_pad (GST_ELEMENT (smokedec), smokedec->srcpad);
|
||||
|
||||
smokecodec_decode_new (&smokedec->info);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_smokedec_finalize (GObject * object)
|
||||
{
|
||||
GstSmokeDec *dec = GST_SMOKEDEC (object);
|
||||
|
||||
smokecodec_info_free (dec->info);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_smokedec_chain (GstPad * pad, GstBuffer * buf)
|
||||
{
|
||||
GstSmokeDec *smokedec;
|
||||
guint8 *data, *outdata;
|
||||
gulong size, outsize;
|
||||
GstBuffer *outbuf;
|
||||
SmokeCodecFlags flags;
|
||||
GstClockTime time;
|
||||
guint width, height;
|
||||
guint fps_num, fps_denom;
|
||||
gint smokeret;
|
||||
GstFlowReturn ret;
|
||||
|
||||
smokedec = GST_SMOKEDEC (gst_pad_get_parent (pad));
|
||||
|
||||
data = GST_BUFFER_DATA (buf);
|
||||
size = GST_BUFFER_SIZE (buf);
|
||||
time = GST_BUFFER_TIMESTAMP (buf);
|
||||
|
||||
if (size < 1)
|
||||
goto too_small;
|
||||
|
||||
GST_LOG_OBJECT (smokedec, "got buffer of %lu bytes", size);
|
||||
|
||||
/* have the ID packet. */
|
||||
if (data[0] == SMOKECODEC_TYPE_ID) {
|
||||
smokeret = smokecodec_parse_id (smokedec->info, data, size);
|
||||
if (smokeret != SMOKECODEC_OK)
|
||||
goto header_error;
|
||||
|
||||
ret = GST_FLOW_OK;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* now handle data packets */
|
||||
GST_DEBUG_OBJECT (smokedec, "reading header %08lx", *(gulong *) data);
|
||||
smokecodec_parse_header (smokedec->info, data, size, &flags, &width, &height,
|
||||
&fps_num, &fps_denom);
|
||||
|
||||
if (smokedec->height != height || smokedec->width != width ||
|
||||
smokedec->fps_num != fps_num || smokedec->fps_denom != fps_denom) {
|
||||
GstCaps *caps;
|
||||
|
||||
GST_DEBUG_OBJECT (smokedec, "parameter change: %dx%d @ %d/%dfps",
|
||||
width, height, fps_num, fps_denom);
|
||||
|
||||
smokedec->height = height;
|
||||
smokedec->width = width;
|
||||
|
||||
caps = gst_caps_new_simple ("video/x-raw-yuv",
|
||||
"format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),
|
||||
"width", G_TYPE_INT, width,
|
||||
"height", G_TYPE_INT, height,
|
||||
"framerate", GST_TYPE_FRACTION, fps_num, fps_denom, NULL);
|
||||
|
||||
gst_pad_set_caps (smokedec->srcpad, caps);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
if (smokedec->need_keyframe) {
|
||||
if (!(flags & SMOKECODEC_KEYFRAME))
|
||||
goto keyframe_skip;
|
||||
|
||||
smokedec->need_keyframe = FALSE;
|
||||
}
|
||||
|
||||
outsize = width * height + width * height / 2;
|
||||
outbuf = gst_buffer_new_and_alloc (outsize);
|
||||
outdata = GST_BUFFER_DATA (outbuf);
|
||||
|
||||
GST_BUFFER_DURATION (outbuf) =
|
||||
gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
|
||||
GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buf);
|
||||
gst_buffer_set_caps (outbuf, GST_PAD_CAPS (smokedec->srcpad));
|
||||
|
||||
if (time == GST_CLOCK_TIME_NONE) {
|
||||
if (GST_BUFFER_OFFSET (buf) == -1) {
|
||||
time = smokedec->next_time;
|
||||
} else {
|
||||
time = GST_BUFFER_OFFSET (buf) * GST_BUFFER_DURATION (outbuf);
|
||||
}
|
||||
}
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = time;
|
||||
if (time != -1)
|
||||
smokedec->next_time = time + GST_BUFFER_DURATION (outbuf);
|
||||
else
|
||||
smokedec->next_time = -1;
|
||||
|
||||
smokeret = smokecodec_decode (smokedec->info, data, size, outdata);
|
||||
if (smokeret != SMOKECODEC_OK)
|
||||
goto decode_error;
|
||||
|
||||
GST_DEBUG_OBJECT (smokedec, "gst_smokedec_chain: sending buffer");
|
||||
ret = gst_pad_push (smokedec->srcpad, outbuf);
|
||||
|
||||
done:
|
||||
gst_buffer_unref (buf);
|
||||
gst_object_unref (smokedec);
|
||||
|
||||
return ret;
|
||||
|
||||
/* ERRORS */
|
||||
too_small:
|
||||
{
|
||||
GST_ELEMENT_ERROR (smokedec, STREAM, DECODE,
|
||||
(NULL), ("Input buffer too small"));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
header_error:
|
||||
{
|
||||
GST_ELEMENT_ERROR (smokedec, STREAM, DECODE,
|
||||
(NULL), ("Could not parse smoke header, reason: %d", smokeret));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
keyframe_skip:
|
||||
{
|
||||
GST_DEBUG_OBJECT (smokedec, "dropping buffer while waiting for keyframe");
|
||||
ret = GST_FLOW_OK;
|
||||
goto done;
|
||||
}
|
||||
decode_error:
|
||||
{
|
||||
GST_ELEMENT_ERROR (smokedec, STREAM, DECODE,
|
||||
(NULL), ("Could not decode smoke frame, reason: %d", smokeret));
|
||||
ret = GST_FLOW_ERROR;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_smokedec_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstSmokeDec *dec;
|
||||
|
||||
dec = GST_SMOKEDEC (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
/* reset the initial video state */
|
||||
dec->format = -1;
|
||||
dec->width = -1;
|
||||
dec->height = -1;
|
||||
dec->fps_num = -1;
|
||||
dec->fps_denom = -1;
|
||||
dec->next_time = 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (ret != GST_STATE_CHANGE_SUCCESS)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
75
ext/jpeg/gstsmokedec.h
Normal file
75
ext/jpeg/gstsmokedec.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GST_SMOKEDEC_H__
|
||||
#define __GST_SMOKEDEC_H__
|
||||
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "smokecodec.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_SMOKEDEC \
|
||||
(gst_smokedec_get_type())
|
||||
#define GST_SMOKEDEC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SMOKEDEC,GstSmokeDec))
|
||||
#define GST_SMOKEDEC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SMOKEDEC,GstSmokeDecClass))
|
||||
#define GST_IS_SMOKEDEC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SMOKEDEC))
|
||||
#define GST_IS_SMOKEDEC_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SMOKEDEC))
|
||||
|
||||
typedef struct _GstSmokeDec GstSmokeDec;
|
||||
typedef struct _GstSmokeDecClass GstSmokeDecClass;
|
||||
|
||||
struct _GstSmokeDec {
|
||||
GstElement element;
|
||||
|
||||
/* pads */
|
||||
GstPad *sinkpad,*srcpad;
|
||||
|
||||
/* video state */
|
||||
gint format;
|
||||
gint width;
|
||||
gint height;
|
||||
gint fps_num;
|
||||
gint fps_denom;
|
||||
GstClockTime next_time;
|
||||
|
||||
SmokeCodecInfo *info;
|
||||
|
||||
gint threshold;
|
||||
gint quality;
|
||||
gint smoothing;
|
||||
|
||||
gboolean need_keyframe;
|
||||
};
|
||||
|
||||
struct _GstSmokeDecClass {
|
||||
GstElementClass parent_class;
|
||||
};
|
||||
|
||||
GType gst_smokedec_get_type(void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_SMOKEDEC_H__ */
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue