Merging gst-plugins-good

This commit is contained in:
Thibault Saunier 2021-09-24 16:13:37 -03:00
commit 2217dc11ee
1488 changed files with 731113 additions and 0 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
*~
*.bak
Build
*.user
*.suo
*.ipch
*.sdf
*.opensdf
*.DS_Store
# Meson
/build
/_build
/subprojects

1
.gitlab-ci.yml Normal file
View file

@ -0,0 +1 @@
include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"

21
AUTHORS Normal file
View 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
View 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!

157811
ChangeLog Normal file

File diff suppressed because it is too large Load diff

12
MAINTAINERS Normal file
View 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
View 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 thats 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 dont 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
View 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
View 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 didnt 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
@GST_API_VERSION@

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
View file

111
docs/meson.build Normal file
View 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
View file

@ -0,0 +1 @@
gst-index

41
ext/aalib/gstaaplugin.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 &lt;gst/gst.h&gt;
* #include &lt;gst/video/video.h&gt;
*
* ...
*
* 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, &amp;state-&gt;width, &amp;state-&gt;height);
* state-&gt;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-&gt;valid)
* return;
*
* scale = 2*(((timestamp/(int)1e7) % 70)+30)/100.0;
* cairo_translate(cr, s-&gt;width/2, (s-&gt;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 (&quot;cairooverlay&quot;, &quot;overlay&quot;);
*
* g_signal_connect (cairo_overlay, &quot;draw&quot;, G_CALLBACK (draw_overlay),
* overlay_state);
* g_signal_connect (cairo_overlay, &quot;caps-changed&quot;,
* 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 */
}

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

83
ext/dv/gstdvdemux.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View 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

File diff suppressed because it is too large Load diff

68
ext/flac/gstflacenc.h Normal file
View 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
View 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
View 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
View 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

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

View 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__ */

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

View 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__ */

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

View 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__ */

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

View 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

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

View 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 &apos;(NULL)&apos;).
*/
#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;
}
}

View 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 */

View 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
View 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 (&gtk_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 (&gtk_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 (&gtk_sink->v_info) > 0) {
*end = *start +
gst_util_uint64_scale_int (GST_SECOND,
GST_VIDEO_INFO_FPS_D (&gtk_sink->v_info),
GST_VIDEO_INFO_FPS_N (&gtk_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 (&gtk_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, &gtk_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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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_

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

View 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

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

View 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__ */

View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

110
ext/jpeg/gstjpegdec.h Normal file
View 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__ */

View 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
View 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, &params);
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, &params);
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
View 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
View 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
View 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
View 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