-first ccommit for stable version of JustVideo
This commit is contained in:
zii 2024-08-06 08:28:43 -04:00
commit ef90c77e8a
133 changed files with 15034 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
/build
/app_dir
/release
/debug
/installers
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
# VSCode
/.vscode

56
JustVideo.pro Normal file
View File

@ -0,0 +1,56 @@
QT += core gui
QT += multimedia
QT += multimediawidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
TARGET = build/jvideo
OBJECTS_DIR = build
MOC_DIR = build
RCC_DIR = build
DEFINES += "QT_AVPLAYER_MULTIMEDIA" \
#"QT_AVPLAYER_VA_X11" \ #uncomment to enable libva-x11
#"QT_AVPLAYER_VA_DRM" \ #uncomment to enable libva-drm
"QT_AVPLAYER_VDPAU"
INCLUDEPATH += QtAVPlayer/src
include(QtAVPlayer/src/QtAVPlayer/QtAVPlayer.pri)
SOURCES += \
src/actions.cpp \
src/folder_dialog.cpp \
src/img_loader.cpp \
src/main.cpp \
src/common.cpp \
src/main_widget.cpp \
src/play_control.cpp \
src/player.cpp \
src/playlist_backend.cpp \
src/playlist_widget.cpp \
src/pref_dialog.cpp \
src/recents.cpp \
src/vid_grid.cpp \
src/vid_widget.cpp
HEADERS += \
src/actions.h \
src/common.h \
src/folder_dialog.h \
src/img_loader.h \
src/main_widget.h \
src/play_control.h \
src/player.h \
src/playlist_backend.h \
src/playlist_widget.h \
src/pref_dialog.h \
src/recents.h \
src/vid_grid.h \
src/vid_widget.h

621
LICENSE.md Normal file
View File

@ -0,0 +1,621 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, 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
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS

21
QtAVPlayer/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Val Doroshchuk
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.

218
QtAVPlayer/README.md Normal file
View File

@ -0,0 +1,218 @@
# Qt AVPlayer
![example workflow](https://github.com/valbok/QtAVPlayer/actions/workflows/main.yaml/badge.svg)
Free and open-source Qt Media Player library based on FFmpeg.
- Designed to decode _video_/_audio_/_subtitle_ frames.
- Supports [FFmpeg Bitstream Filters](https://ffmpeg.org/ffmpeg-bitstream-filters.html) and [FFmpeg Filters](https://ffmpeg.org/ffmpeg-filters.html) including `filter_complex`.
- Supports multiple parallel filters for one input (one input frame and multiple output ones).
- Supports decoding all available streams at the same time.
- Based on Qt platform the video frames are sent using specific hardware context:
* `VA-API` for Linux: DRM with EGL or X11 with GLX.
* `VDPAU` for Linux.
* `Video Toolbox` for macOS and iOS.
* `D3D11` for Windows.
* `MediaCodec` for Android.
Note: Not all ffmpeg decoders or filters support HW acceleration. In this case software decoders are used.
- It is up to an application to decide how to process the frames.
* But there is _experimental_ support of converting the video frames to QtMultimedia's [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) for copy-free rendering if possible.
Note: Not all Qt's renders support copy-free rendering. Also QtMultimedia does not always provide public API to render the video frames. And, of course, for best performance both decoding and rendering should be accelerated.
* Audio frames could be played by `QAVAudioOutput` which is a wrapper of QtMultimedia's [QAudioSink](https://doc-snapshots.qt.io/qt6-dev/qaudiosink.html)
- Supports accurate seek, it starts playing the closest frame. No weird jumps on pts anymore.
- It is bundled directly into an app using qmake pri.
- Designed to be as simple and understandable as possible, to share knowledge about creating efficient FFmpeg applications.
- Might be used for media analytics software like [qctools](https://github.com/bavc/qctools) or [dvrescue](https://github.com/mipops/dvrescue).
- Strange to say this in 21st century, but each feature is covered by integration tests.
- Implements and replaces a combination of FFmpeg and FFplay:
ffmpeg -i we-miss-gst-pipeline-in-qt6mm.mkv -filter_complex "qt,nev:er,wanted;[ffmpeg];what:happened" - | ffplay -
but using QML or Qt Widgets:
./qml_video :/valbok "if:you:like[cats];remove[this]"
# Features
1. QAVPlayer supports playing from an url or QIODevice or from avdevice:
player.setSource("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
player.setSource("/home/lana/The Matrix Resurrections.mov");
// Playing from qrc
QSharedPointer<QIODevice> file(new QFile(":/alarm.wav"));
file->open(QIODevice::ReadOnly);
QSharedPointer<QAVIODevice> dev(new QAVIODevice(file));
player.setSource("alarm", dev);
// Getting frames from the camera in Linux
player.setSource("/dev/video0");
// Or Windows
player.setInputFormat("dshow");
player.setSource("video=Integrated Camera");
// Or MacOS
player.setInputFormat("avfoundation");
player.setSource("default");
// Or Android
player.setInputFormat("android_camera");
player.setSource("0:0");
player.setInputOptions({{"user_agent", "QAVPlayer"}});
// Using various protocols
player.setSource("subfile,,start,0,end,0,,:/root/Downloads/why-qtmm-must-die.mkv");
3. Easy getting video and audio frames:
QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) {
// QAVVideoFrame is comppatible with QVideoFrame
QVideoFrame videoFrame = frame;
// QAVVideoFrame can be converted to various pixel formats
auto convertedFrame = frame.convert(AV_PIX_FMT_YUV420P);
// Easy getting data from video frame
auto mapped = videoFrame.map(); // downloads data if it is in GPU
qDebug() << mapped.format << mapped.size;
// The frame might contain OpenGL or MTL textures, for copy-free rendering
qDebug() << frame.handleType() << frame.handle();
}, Qt::DirectConnection);
// Audio frames could be played using QAVAudioOutput
QAVAudioOutput audioOutput;
QObject::connect(&player, &QAVPlayer::audioFrame, [&](const QAVAudioFrame &frame) {
// Access to the data
qDebug() << autioFrame.format() << autioFrame.data().size();
audioOutput.play(frame);
}, Qt::DirectConnection);
QObject::connect(&p, &QAVPlayer::subtitleFrame, &p, [](const QAVSubtitleFrame &frame) {
for (unsigned i = 0; i < frame.subtitle()->num_rects; ++i) {
if (frame.subtitle()->rects[i]->type == SUBTITLE_TEXT)
qDebug() << "text:" << frame.subtitle()->rects[i]->text;
else
qDebug() << "ass:" << frame.subtitle()->rects[i]->ass;
}
}, Qt::DirectConnection);
4. Each action is confirmed by a signal:
// All signals are added to a queue and guaranteed to be emitted in proper order.
QObject::connect(&player, &QAVPlayer::played, [&](qint64 pos) { qDebug() << "Playing started from pos" << pos; });
QObject::connect(&player, &QAVPlayer::paused, [&](qint64 pos) { qDebug() << "Paused at pos" << pos; });
QObject::connect(&player, &QAVPlayer::stopped, [&](qint64 pos) { qDebug() << "Stopped at pos" << pos; });
QObject::connect(&player, &QAVPlayer::seeked, [&](qint64 pos) { qDebug() << "Seeked to pos" << pos; });
QObject::connect(&player, &QAVPlayer::stepped, [&](qint64 pos) { qDebug() << "Made a step to pos" << pos; });
QObject::connect(&player, &QAVPlayer::mediaStatusChanged, [&](QAVPlayer::MediaStatus status) {
switch (status) {
case QAVplayer::EndOfMedia:
qDebug() << "Finished to play, no frames in queue";
break;
case QAVplayer::NoMedia:
qDebug() << "Demuxer threads are finished";
break;
default:
break;
}
});
5. Accurate seek:
QObject::connect(&p, &QAVPlayer::seeked, &p, [&](qint64 pos) { seekPosition = pos; });
QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { seekFrame = frame; });
player.seek(5000)
QTRY_COMPARE(seekPosition, 5000);
QTRY_COMPARE(seekFrame.pts(), 5.0);
If there is a frame with needed pts, it will be returned as first frame.
6. FFmpeg filters:
player.setFilter("crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack");
// Render bundled subtitles
player.setFilter("subtitles=file.mkv");
// Render subtitles from srt file
player.setFilter("subtitles=file.srt");
// Multiple filters
player.setFilters({
"drawtext=text=%{pts\\\\:hms}:x=(w-text_w)/2:y=(h-text_h)*(4/5):box=1:boxcolor=gray@0.5:fontsize=36[drawtext]",
"negate[negate]",
"[0:v]split=3[in1][in2][in3];[in1]boxblur[out1];[in2]negate[out2];[in3]drawtext=text=%{pts\\\\:hms}:x=(w-text_w)/2:y=(h-text_h)*(4/5):box=1:boxcolor=gray@0.5:fontsize=36[out3]"
}); // Return frames from 3 filters with 5 outputs
7. Step by step:
// Pausing will always emit one frame
QObject::connect(&player, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { receivedFrame = frame; });
if (player.state() != QAVPlayer::PausedState) { // No frames if it is already paused
player.pause();
QTRY_VERIFY(receivedFrame);
}
// Always makes a step forward and emits only one frame
player.stepForward();
// the same here but backward
player.stepBackward();
8. Multiple streams:
qDebug() << "Audio streams" << player.availableAudioStreams().size();
qDebug() << "Current audio stream" << player.currentAudioStreams().first().index() << player.currentAudioStreams().first().metadata();
player.setAudioStreams(player.availableAudioStreams()); // Return all frames for all available audio streams
// Reports progress of playing per stream, like current pts, fps, frame rate, num of frames etc
for (const auto &s : p.availableVideoStreams())
qDebug() << s << p.progress(s);
9. HW accelerations:
QT_AVPLAYER_NO_HWDEVICE can be used to force using software decoding. The video codec is negotiated automatically.
* `VA-API` and `VDPAU` for Linux: the frames are returned with OpenGL textures.
* `Video Toolbox` for macOS and iOS: the frames are returned with Metal Textures.
* `D3D11` for Windows: the frames are returned with D3D11Texture2D textures.
* `MediaCodec` for Android: the frames are returned with OpenGL textures.
10. QtMultimedia could be used to render video frames to QML or Widgets. See [examples](examples)
11. Qt 5.12 - **6**.x is supported
# How to build
QtAVPlayer should be directly bundled into an app using QMake and [QtAVPlayer.pri](https://github.com/valbok/QtAVPlayer/blob/master/src/QtAVPlayer/QtAVPlayer.pri).
Some defines should be provided to opt some features.
* `QT_AVPLAYER_MULTIMEDIA` - enables support of `QtMultimedia` which requires `QtGUI`, `QtQuick` etc.
* `QT_AVPLAYER_VA_X11` - enables support of `libva-x11` for HW acceleration. For linux only.
* `QT_AVPLAYER_VA_DRM` - enables support of `libva-drm` for HW acceleration. For linux only.
* `QT_AVPLAYER_VDPAU` - enables support of `libvdpau` for HW acceleration. For linux only.
CMake is not supported.
Include QtAVPlayer.pri in your pro file:
INCLUDEPATH += ../../src/
include(../../src/QtAVPlayer/QtAVPlayer.pri)
And then for your app:
$ qmake DEFINES+="QT_AVPLAYER_MULTIMEDIA"
FFmpeg on custom path:
$ qmake DEFINES+="QT_AVPLAYER_MULTIMEDIA" INCLUDEPATH+="/usr/local/Cellar/ffmpeg/6.0/include" LIBS="-L/usr/local/Cellar/ffmpeg/6.0/lib"
## Android:
Some exports could be also used: vars that point to libraries in armeabi-v7a, arm64-v8a, x86 and x86_64 target archs.
$ export AVPLAYER_ANDROID_LIB_ARMEABI_V7A=/opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib
$ export AVPLAYER_ANDROID_LIB_ARMEABI_V8A=/opt/mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/lib
$ export AVPLAYER_ANDROID_LIB_X86=/opt/mobile-ffmpeg/prebuilt/android-x86/ffmpeg/lib
$ export AVPLAYER_ANDROID_LIB_X86_64=/opt/mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/lib
$ export CPLUS_INCLUDE_PATH=/opt/mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/include:$CPLUS_INCLUDE_PATH
$ qmake DEFINES+="QT_AVPLAYER_MULTIMEDIA"
Don't forget to set extra libs in _pro_ file for your app:
ANDROID_EXTRA_LIBS += /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavdevice.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavformat.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavutil.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavcodec.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavfilter.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswscale.so /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswresample.so

View File

@ -0,0 +1,13 @@
TEMPLATE = app
TARGET = extract_frames
INCLUDEPATH += .
INCLUDEPATH += . ../../src
include(../../src/QtAVPlayer/QtAVPlayer.pri)
QT -= gui
CONFIG += c++1z
SOURCES += main.cpp
target.path = $$[QT_INSTALL_EXAMPLES]/$$TARGET
INSTALLS += target

View File

@ -0,0 +1,30 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include <QtAVPlayer/qavplayer.h>
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudioframe.h>
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QAVPlayer p;
QObject::connect(&p, &QAVPlayer::audioFrame, [&](const QAVAudioFrame &frame) { qDebug() << "audio:" << frame.pts(); });
QObject::connect(&p, &QAVPlayer::videoFrame, [&](const QAVVideoFrame &frame) { qDebug() << "video:" << frame.pts(); });
p.setSource(QLatin1String("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"));
p.play();
QObject::connect(&p, &QAVPlayer::stateChanged, [&](auto s) { qDebug() << "stateChanged" << s << p.mediaStatus(); });
QObject::connect(&p, &QAVPlayer::mediaStatusChanged, [&](auto s){ qDebug() << "mediaStatusChanged"<< s << p.state(); });
QObject::connect(&p, &QAVPlayer::durationChanged, [&](auto d) { qDebug() << "durationChanged" << d; });
return app.exec();
}

View File

@ -0,0 +1,204 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include <QtAVPlayer/qavplayer.h>
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudiooutput.h>
#include <QtAVPlayer/qaviodevice.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAbstractVideoSurface>
#include <private/qdeclarativevideooutput_p.h>
#else
#include <QVideoSink>
#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
#endif
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickItem>
#include <QtQml/QQmlEngine>
#include <QGuiApplication>
#include <QDebug>
#include <QElapsedTimer>
#include <QFile>
extern "C" {
#include <libavcodec/avcodec.h>
}
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
class Source : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
public:
explicit Source(QObject *parent = 0) : QObject(parent) { }
virtual ~Source() { }
QAbstractVideoSurface* videoSurface() const { return m_surface; }
void setVideoSurface(QAbstractVideoSurface *surface)
{
m_surface = surface;
}
QAbstractVideoSurface *m_surface = nullptr;
};
#endif
static bool isStreamCurrent(int index, const QList<QAVStream> &streams)
{
for (const auto &stream: streams) {
if (stream.index() == index)
return true;
}
return false;
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView viewer;
viewer.setSource(QUrl(QString::fromLatin1("qrc:///main.qml")));
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
QObject::connect(viewer.engine(), SIGNAL(quit()), &viewer, SLOT(close()));
QQuickItem *rootObject = viewer.rootObject();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
using VideoOutput = QDeclarativeVideoOutput;
#else
using VideoOutput = QQuickVideoOutput;
#endif
auto vo = rootObject->findChild<VideoOutput *>(QString::fromLatin1("videoOutput"));
QAVAudioOutput audioOutput;
QAVPlayer p;
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
Source src;
vo->setSource(&src);
auto videoSurface = src.m_surface;
#elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto videoSurface = vo->videoSurface();
// Make sure that render geometry has been updated after a frame
QObject::connect(vo, &QDeclarativeVideoOutput::sourceRectChanged, &p, [&] {
vo->update();
});
#else
auto videoSurface = vo->videoSink();
#endif
QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
rootObject->setProperty("frame_fps", p.progress(frame.stream()).fps());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// Might download and convert data
QVideoFrame videoFrame = frame;
if (!videoSurface->isActive())
videoSurface->start({videoFrame.size(), videoFrame.pixelFormat(), videoFrame.handleType()});
if (videoSurface->isActive())
videoSurface->present(videoFrame);
#endif
});
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
// Might download and convert data
QVideoFrame videoFrame = frame;
videoSurface->setVideoFrame(videoFrame);
}, Qt::DirectConnection);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
//audioOutput.setChannelConfig(QAudioFormat::channelConfig(QAudioFormat::FrontLeft));
#endif
QObject::connect(&p, &QAVPlayer::audioFrame, &p, [&audioOutput](const QAVAudioFrame &frame) { audioOutput.play(frame); }, Qt::DirectConnection);
QString file = argc > 1 ? QString::fromUtf8(argv[1]) : QString::fromLatin1("http://archive.org/download/big-bunny-sample-video/SampleVideo.ia.mp4");
QString filter = argc > 2 ? QString::fromUtf8(argv[2]) : QString();
QObject::connect(&p, &QAVPlayer::stateChanged, [&](auto s) { qDebug() << "stateChanged" << s << p.mediaStatus(); });
QObject::connect(&p, &QAVPlayer::mediaStatusChanged, [&](auto status) {
qDebug() << "mediaStatusChanged"<< status << p.state();
if (status == QAVPlayer::LoadedMedia) {
auto availableVideoStreams = p.availableVideoStreams();
//p.setVideoStreams({});
auto videoStreams = p.currentVideoStreams();
qDebug() << "Video streams:" << availableVideoStreams.size();
for (auto &s : p.availableVideoStreams())
qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), videoStreams) ? "---current" : "");
auto availableAudioStreams = p.availableAudioStreams();
auto audioStreams = p.currentAudioStreams();
qDebug() << "Audio streams:" << availableAudioStreams.size();
for (auto &s : availableAudioStreams)
qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), audioStreams) ? "---current" : "");
auto availableSubtitleStreams = p.availableSubtitleStreams();
qDebug() << "Subtitle streams:" << availableSubtitleStreams.size();
for (auto &s : availableSubtitleStreams) {
if (s.metadata()[QString::fromLatin1("language")] == QString::fromLatin1("eng")) {
p.setSubtitleStream(s);
break;
}
}
auto subtitleStreams = p.currentSubtitleStreams();
for (auto &s : availableSubtitleStreams) {
qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), subtitleStreams) ? "---current" : "");
}
p.play();
} else if (status == QAVPlayer::EndOfMedia) {
for (const auto &s : p.availableVideoStreams())
qDebug() << s << p.progress(s);
for (const auto &s : p.availableAudioStreams())
qDebug() << s << p.progress(s);
for (const auto &s : p.availableSubtitleStreams())
qDebug() << s << p.progress(s);
}
});
QObject::connect(&p, &QAVPlayer::durationChanged, [&](auto d) { qDebug() << "durationChanged" << d; });
QObject::connect(&p, &QAVPlayer::subtitleFrame, &p, [](const QAVSubtitleFrame &frame) {
for (unsigned i = 0; i < frame.subtitle()->num_rects; ++i) {
if (frame.subtitle()->rects[i]->type == SUBTITLE_TEXT)
qDebug() << "text:" << frame.subtitle()->rects[i]->text;
else
qDebug() << "ass:" << frame.subtitle()->rects[i]->ass;
}
});
QSharedPointer<QAVIODevice> qrc;
if (file.startsWith(QString::fromLatin1(":/"))) {
QSharedPointer<QIODevice> io(new QFile(file));
if (io->open(QIODevice::ReadOnly))
qrc.reset(new QAVIODevice(io));
}
p.setSource(file, qrc);
p.setFilter(filter);
//p.setSynced(false);
viewer.setMinimumSize(QSize(300, 360));
viewer.resize(1960, 1086);
viewer.show();
QElapsedTimer qmlElapsed;
qmlElapsed.start();
int qmlCount = 0;
QObject::connect(&viewer, &QQuickView::afterRendering, &viewer, [&] {
const int fps = qmlCount++ * 1000 / qmlElapsed.elapsed();
rootObject->setProperty("qml_fps", fps);
});
return app.exec();
}
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
#include "main.moc"
#endif

View File

@ -0,0 +1,80 @@
import QtQuick 2.5
import QtMultimedia 5.12
Item {
id: root
property alias frame_fps: fpsTextVideo.text
property alias qml_fps: fpsTextQML.text
VideoOutput {
anchors.fill: parent
objectName: "videoOutput"
}
Rectangle {
id: fps
property color textColor: "white"
property int textSize: 30
border.width: 1
border.color: "black"
width: 5.5 * fps.textSize
height: 2.5 * fps.textSize
color: "black"
opacity: 0.5
radius: 0
// This should ensure that the monitor is on top of all other content
z: 999
Text {
id: labelText
anchors {
top: parent.top
left: parent.left
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
text: "Video FPS"
width: fps.width - 2*anchors.margins
elide: Text.ElideRight
}
Text {
id: labelTextQML
anchors {
bottom: parent.bottom
left: parent.left
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
text: "QML FPS"
width: fps.width - 2*anchors.margins
elide: Text.ElideRight
}
Text {
id: fpsTextVideo
anchors {
top: parent.top
right: parent.right
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
}
Text {
id: fpsTextQML
anchors {
bottom: parent.bottom
right: parent.right
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
}
}
}

View File

@ -0,0 +1,80 @@
import QtQuick 2.5
import QtMultimedia 6.2
Item {
id: root
property alias frame_fps: fpsTextVideo.text
property alias qml_fps: fpsTextQML.text
VideoOutput {
anchors.fill: parent
objectName: "videoOutput"
}
Rectangle {
id: fps
property color textColor: "white"
property int textSize: 30
border.width: 1
border.color: "black"
width: 5.5 * fps.textSize
height: 2.5 * fps.textSize
color: "black"
opacity: 0.5
radius: 0
// This should ensure that the monitor is on top of all other content
z: 999
Text {
id: labelText
anchors {
top: parent.top
left: parent.left
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
text: "Video FPS"
width: fps.width - 2*anchors.margins
elide: Text.ElideRight
}
Text {
id: labelTextQML
anchors {
bottom: parent.bottom
left: parent.left
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
text: "QML FPS"
width: fps.width - 2*anchors.margins
elide: Text.ElideRight
}
Text {
id: fpsTextVideo
anchors {
top: parent.top
right: parent.right
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
}
Text {
id: fpsTextQML
anchors {
bottom: parent.bottom
right: parent.right
margins: 10
}
color: fps.textColor
font.pixelSize: 0.6 * fps.textSize
}
}
}

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file alias="valbok">../../tests/auto/integration/testdata/20190821_075842.jpg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file alias="main.qml">main_qt6.qml</file>
<file alias="valbok">../../tests/auto/integration/testdata/20190821_075842.jpg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,25 @@
TEMPLATE = app
TARGET = qml_video
DEFINES += "QT_AVPLAYER_MULTIMEDIA"
DEFINES += "QT_NO_CAST_FROM_ASCII"
INCLUDEPATH += . ../../src
include(../../src/QtAVPlayer/QtAVPlayer.pri)
# Example
#ANDROID_EXTRA_LIBS += /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavdevice.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavformat.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavutil.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavcodec.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libavfilter.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswscale.so \
# /opt/mobile-ffmpeg/prebuilt/android-arm/ffmpeg/lib/libswresample.so
CONFIG += c++1z
QT += gui multimedia
lessThan(QT_MAJOR_VERSION, 6): QT += qtmultimediaquicktools-private
equals(QT_MAJOR_VERSION, 6): QT += multimediaquick-private
SOURCES += main.cpp
lessThan(QT_MAJOR_VERSION, 6): RESOURCES += qml.qrc
equals(QT_MAJOR_VERSION, 6): RESOURCES += qml_qt6.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/$$TARGET
INSTALLS += target

View File

@ -0,0 +1,136 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include <QtAVPlayer/qavplayer.h>
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudiooutput.h>
#include <QVideoWidget>
#include <QApplication>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
#include <QMediaService>
#include <QMediaObject>
#include <QVideoRendererControl>
class VideoRenderer : public QVideoRendererControl
{
public:
QAbstractVideoSurface *surface() const override
{
return m_surface;
}
void setSurface(QAbstractVideoSurface *surface) override
{
m_surface = surface;
}
QAbstractVideoSurface *m_surface = nullptr;
};
class MediaService : public QMediaService
{
public:
MediaService(VideoRenderer *vr, QObject* parent = nullptr)
: QMediaService(parent)
, m_renderer(vr)
{
}
QMediaControl* requestControl(const char *name) override
{
if (qstrcmp(name, QVideoRendererControl_iid) == 0)
return m_renderer;
return nullptr;
}
void releaseControl(QMediaControl *) override
{
}
VideoRenderer *m_renderer = nullptr;
};
class MediaObject : public QMediaObject
{
public:
explicit MediaObject(VideoRenderer *vr, QObject* parent = nullptr)
: QMediaObject(parent, new MediaService(vr, parent))
{
}
};
class VideoWidget : public QVideoWidget
{
public:
bool setMediaObject(QMediaObject *object) override
{
return QVideoWidget::setMediaObject(object);
}
};
#else
#include <QVideoSink>
#endif
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
VideoRenderer vr;
VideoWidget w;
w.show();
MediaObject mo(&vr);
w.setMediaObject(&mo);
#else
QVideoWidget w;
w.show();
#endif
QAVPlayer p;
QString file = argc > 1 ? QString::fromUtf8(argv[1]) : QString::fromLatin1("http://archive.org/download/big-bunny-sample-video/SampleVideo.ia.mp4");
p.setSource(file);
p.play();
QAVAudioOutput audioOutput;
QObject::connect(&p, &QAVPlayer::audioFrame, &p, [&audioOutput](const QAVAudioFrame &frame) { audioOutput.play(frame); }, Qt::DirectConnection);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
if (vr.m_surface == nullptr)
return;
QVideoFrame videoFrame = frame.convertTo(AV_PIX_FMT_RGB32);
if (!vr.m_surface->isActive() || vr.m_surface->surfaceFormat().frameSize() != videoFrame.size()) {
QVideoSurfaceFormat f(videoFrame.size(), videoFrame.pixelFormat(), videoFrame.handleType());
vr.m_surface->start(f);
}
if (vr.m_surface->isActive())
vr.m_surface->present(videoFrame);
}, Qt::DirectConnection);
#else
QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
QVideoFrame videoFrame = frame;
w.videoSink()->setVideoFrame(videoFrame);
}, Qt::DirectConnection);
#endif
QObject::connect(&p, &QAVPlayer::mediaStatusChanged, [&](auto status) {
qDebug() << "mediaStatusChanged"<< status << p.state();
if (status == QAVPlayer::LoadedMedia) {
qDebug() << "Video streams:" << p.currentVideoStreams().size();
for (const auto &s: p.currentVideoStreams())
qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate";
}
});
return app.exec();
}

View File

@ -0,0 +1,14 @@
TEMPLATE = app
TARGET = widget_video
DEFINES += "QT_AVPLAYER_MULTIMEDIA"
DEFINES += "QT_NO_CAST_FROM_ASCII"
INCLUDEPATH += . ../../src
include(../../src/QtAVPlayer/QtAVPlayer.pri)
CONFIG += c++1z
QT += gui multimedia multimediawidgets
SOURCES += main.cpp
target.path = $$[QT_INSTALL_EXAMPLES]/$$TARGET
INSTALLS += target

View File

@ -0,0 +1,131 @@
QT += concurrent
CONFIG += C++1z
LIBS += -lavcodec -lavformat -lswscale -lavutil -lswresample -lswscale -lavfilter -lavdevice
PRIVATE_HEADERS += \
$$PWD/qavcodec_p.h \
$$PWD/qavcodec_p_p.h \
$$PWD/qavframecodec_p.h \
$$PWD/qavaudiocodec_p.h \
$$PWD/qavvideocodec_p.h \
$$PWD/qavsubtitlecodec_p.h \
$$PWD/qavhwdevice_p.h \
$$PWD/qavdemuxer_p.h \
$$PWD/qavpacket_p.h \
$$PWD/qavstreamframe_p.h \
$$PWD/qavframe_p.h \
$$PWD/qavpacketqueue_p.h \
$$PWD/qavvideobuffer_p.h \
$$PWD/qavvideobuffer_cpu_p.h \
$$PWD/qavvideobuffer_gpu_p.h \
$$PWD/qavfilter_p.h \
$$PWD/qavfilter_p_p.h \
$$PWD/qavvideofilter_p.h \
$$PWD/qavaudiofilter_p.h \
$$PWD/qavfiltergraph_p.h \
$$PWD/qavinoutfilter_p.h \
$$PWD/qavinoutfilter_p_p.h \
$$PWD/qavvideoinputfilter_p.h \
$$PWD/qavaudioinputfilter_p.h \
$$PWD/qavvideooutputfilter_p.h \
$$PWD/qavaudiooutputfilter_p.h \
$$PWD/qavfilters_p.h
PUBLIC_HEADERS += \
$$PWD/qaviodevice.h \
$$PWD/qavaudioformat.h \
$$PWD/qavstreamframe.h \
$$PWD/qavframe.h \
$$PWD/qavvideoframe.h \
$$PWD/qavaudioframe.h \
$$PWD/qavsubtitleframe.h \
$$PWD/qtavplayerglobal.h \
$$PWD/qavstream.h \
$$PWD/qavplayer.h \
SOURCES += \
$$PWD/qavplayer.cpp \
$$PWD/qavcodec.cpp \
$$PWD/qavframecodec.cpp \
$$PWD/qavaudiocodec.cpp \
$$PWD/qavvideocodec.cpp \
$$PWD/qavsubtitlecodec.cpp \
$$PWD/qavdemuxer.cpp \
$$PWD/qavpacket.cpp \
$$PWD/qavframe.cpp \
$$PWD/qavstreamframe.cpp \
$$PWD/qavvideoframe.cpp \
$$PWD/qavaudioframe.cpp \
$$PWD/qavsubtitleframe.cpp \
$$PWD/qavvideobuffer_cpu.cpp \
$$PWD/qavvideobuffer_gpu.cpp \
$$PWD/qavfilter.cpp \
$$PWD/qavvideofilter.cpp \
$$PWD/qavaudiofilter.cpp \
$$PWD/qavfiltergraph.cpp \
$$PWD/qavinoutfilter.cpp \
$$PWD/qavvideoinputfilter.cpp \
$$PWD/qavaudioinputfilter.cpp \
$$PWD/qavvideooutputfilter.cpp \
$$PWD/qavaudiooutputfilter.cpp \
$$PWD/qaviodevice.cpp \
$$PWD/qavstream.cpp \
$$PWD/qavfilters.cpp
contains(DEFINES, QT_AVPLAYER_MULTIMEDIA) {
QT += multimedia
# Needed for QAbstractVideoBuffer
equals(QT_MAJOR_VERSION, 6): QT += multimedia-private
HEADERS += $$PWD/qavaudiooutput.h
SOURCES += $$PWD/qavaudiooutput.cpp
}
contains(DEFINES, QT_AVPLAYER_VA_X11):qtConfig(opengl) {
QMAKE_USE += x11 opengl
LIBS += -lva-x11 -lva
PRIVATE_HEADERS += $$PWD/qavhwdevice_vaapi_x11_glx_p.h
SOURCES += $$PWD/qavhwdevice_vaapi_x11_glx.cpp
}
contains(DEFINES, QT_AVPLAYER_VA_DRM):qtConfig(egl) {
QMAKE_USE += egl opengl
LIBS += -lva-drm -lva
PRIVATE_HEADERS += $$PWD/qavhwdevice_vaapi_drm_egl_p.h
SOURCES += $$PWD/qavhwdevice_vaapi_drm_egl.cpp
}
contains(DEFINES, QT_AVPLAYER_VDPAU) {
PRIVATE_HEADERS += $$PWD/qavhwdevice_vdpau_p.h
SOURCES += $$PWD/qavhwdevice_vdpau.cpp
}
macos|darwin {
PRIVATE_HEADERS += $$PWD/qavhwdevice_videotoolbox_p.h
SOURCES += $$PWD/qavhwdevice_videotoolbox.mm
LIBS += -framework CoreVideo -framework Metal -framework CoreMedia -framework QuartzCore -framework IOSurface
}
win32 {
PRIVATE_HEADERS += $$PWD/qavhwdevice_d3d11_p.h
SOURCES += $$PWD/qavhwdevice_d3d11.cpp
}
android {
QT += core-private
PRIVATE_HEADERS += $$PWD/qavhwdevice_mediacodec_p.h
SOURCES += $$PWD/qavhwdevice_mediacodec.cpp $$PWD/qavandroidsurfacetexture.cpp
equals(ANDROID_TARGET_ARCH, armeabi-v7a): \
LIBS += -L$$(AVPLAYER_ANDROID_LIB_ARMEABI_V7A)
equals(ANDROID_TARGET_ARCH, arm64-v8a): \
LIBS += -L$$(AVPLAYER_ANDROID_LIB_ARMEABI_V8A)
equals(ANDROID_TARGET_ARCH, x86): \
LIBS += -L$$(AVPLAYER_ANDROID_LIB_X86)
equals(ANDROID_TARGET_ARCH, x86_64): \
LIBS += -L$$(AVPLAYER_ANDROID_LIB_X86_64)
}
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS

View File

@ -0,0 +1,74 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavandroidsurfacetexture_p.h"
#include <QtCore/qmutex.h>
#include <QtCore/qcoreapplication.h>
QT_BEGIN_NAMESPACE
QAVAndroidSurfaceTexture::QAVAndroidSurfaceTexture(quint32 texName)
{
Q_STATIC_ASSERT(sizeof (jlong) >= sizeof (void *));
m_surfaceTexture = JniObject("android/graphics/SurfaceTexture", "(I)V", jint(texName));
}
QAVAndroidSurfaceTexture::~QAVAndroidSurfaceTexture()
{
if (m_surface.isValid())
m_surface.callMethod<void>("release");
if (m_surfaceTexture.isValid())
release();
}
void QAVAndroidSurfaceTexture::release()
{
m_surfaceTexture.callMethod<void>("release");
}
void QAVAndroidSurfaceTexture::updateTexImage()
{
if (!m_surfaceTexture.isValid())
return;
m_surfaceTexture.callMethod<void>("updateTexImage");
}
jobject QAVAndroidSurfaceTexture::surfaceTexture()
{
return m_surfaceTexture.object();
}
jobject QAVAndroidSurfaceTexture::surface()
{
if (!m_surface.isValid()) {
m_surface = JniObject("android/view/Surface",
"(Landroid/graphics/SurfaceTexture;)V",
m_surfaceTexture.object());
}
return m_surface.object();
}
void QAVAndroidSurfaceTexture::attachToGLContext(quint32 texName)
{
if (!m_surfaceTexture.isValid())
return;
m_surfaceTexture.callMethod<void>("attachToGLContext", "(I)V", texName);
}
void QAVAndroidSurfaceTexture::detachFromGLContext()
{
if (!m_surfaceTexture.isValid())
return;
m_surfaceTexture.callMethod<void>("detachFromGLContext");
}
QT_END_NAMESPACE

View File

@ -0,0 +1,61 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVANDROIDSURFACETEXTURE_H
#define QAVANDROIDSURFACETEXTURE_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <qobject.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/private/qjni_p.h>
#include <QtCore/private/qjnihelpers_p.h>
using JniObject = QJNIObjectPrivate;
using JniEnvironment = QJNIEnvironmentPrivate;
#else
#include <QtCore/qjniobject.h>
using JniObject = QJniObject;
using JniEnvironment = QJniEnvironment;
#endif
#include <QMatrix4x4>
QT_BEGIN_NAMESPACE
class QAVAndroidSurfaceTexture
{
public:
explicit QAVAndroidSurfaceTexture(quint32 texName = 0);
~QAVAndroidSurfaceTexture();
jobject surfaceTexture();
jobject surface();
inline bool isValid() const { return m_surfaceTexture.isValid(); }
void release(); // API level 14
void updateTexImage();
void attachToGLContext(quint32 texName); // API level 16
void detachFromGLContext(); // API level 16
private:
JniObject m_surfaceTexture;
JniObject m_surface;
};
QT_END_NAMESPACE
#endif // QAVANDROIDSURFACETEXTURE_H

View File

@ -0,0 +1,49 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavaudiocodec_p.h"
#include "qavcodec_p_p.h"
#include <QDebug>
extern "C" {
#include <libavcodec/avcodec.h>
}
QT_BEGIN_NAMESPACE
QAVAudioCodec::QAVAudioCodec()
{
}
QAVAudioFormat QAVAudioCodec::audioFormat() const
{
Q_D(const QAVCodec);
QAVAudioFormat format;
if (!d->avctx)
return format;
auto fmt = AVSampleFormat(d->avctx->sample_fmt);
if (fmt == AV_SAMPLE_FMT_U8)
format.setSampleFormat(QAVAudioFormat::UInt8);
else if (fmt == AV_SAMPLE_FMT_S16)
format.setSampleFormat(QAVAudioFormat::Int16);
else if (fmt == AV_SAMPLE_FMT_S32)
format.setSampleFormat(QAVAudioFormat::Int32);
else if (fmt == AV_SAMPLE_FMT_FLT)
format.setSampleFormat(QAVAudioFormat::Float);
format.setSampleRate(d->avctx->sample_rate);
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(59, 23, 0)
format.setChannelCount(d->avctx->channels);
#else
format.setChannelCount(d->avctx->ch_layout.nb_channels);
#endif
return format;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,39 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOCODEC_P_H
#define QAVAUDIOCODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavframecodec_p.h"
#include "qavaudioformat.h"
QT_BEGIN_NAMESPACE
class QAVAudioCodec : public QAVFrameCodec
{
public:
QAVAudioCodec();
QAVAudioFormat audioFormat() const;
private:
Q_DISABLE_COPY(QAVAudioCodec)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,155 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavaudiofilter_p.h"
#include "qavfilter_p_p.h"
#include "qavcodec_p.h"
#include "qavstream.h"
#include <QDebug>
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
class QAVAudioFilterPrivate : public QAVFilterPrivate
{
public:
QAVAudioFilterPrivate(QAVFilter *q, QMutex &mutex) : QAVFilterPrivate(q, mutex) { }
QList<QAVAudioInputFilter> inputs;
QList<QAVAudioOutputFilter> outputs;
int64_t filter_in_rescale_delta_last = AV_NOPTS_VALUE;
};
QAVAudioFilter::QAVAudioFilter(
const QAVStream &stream,
const QString &name,
const QList<QAVAudioInputFilter> &inputs,
const QList<QAVAudioOutputFilter> &outputs,
QMutex &mutex)
: QAVFilter(
stream,
name,
*new QAVAudioFilterPrivate(this, mutex))
{
Q_D(QAVAudioFilter);
d->inputs = inputs;
d->outputs = outputs;
}
int QAVAudioFilter::write(const QAVFrame &frame)
{
Q_D(QAVAudioFilter);
if (!frame || frame.stream().stream()->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
qWarning() << "Frame is not audio";
return AVERROR(EINVAL);
}
if (!d->isEmpty)
return AVERROR(EAGAIN);
d->sourceFrame = frame;
AVFrame *decoded_frame = d->sourceFrame.frame();
AVRational decoded_frame_tb = d->sourceFrame.stream().stream()->time_base;
// TODO: clear filter_in_rescale_delta_last
if (!d->inputs.isEmpty() && decoded_frame->pts != AV_NOPTS_VALUE) {
decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts,
AVRational{1, decoded_frame->sample_rate},
decoded_frame->nb_samples,
&d->filter_in_rescale_delta_last,
AVRational{1, decoded_frame->sample_rate});
}
for (auto &filter : d->inputs) {
QAVFrame ref = d->sourceFrame;
QMutexLocker locker(&d->graphMutex);
int ret = av_buffersrc_add_frame_flags(filter.ctx(), ref.frame(), AV_BUFFERSRC_FLAG_PUSH);
if (ret < 0)
return ret;
}
d->isEmpty = false;
return 0;
}
int QAVAudioFilter::read(QAVFrame &frame)
{
Q_D(QAVAudioFilter);
if (d->outputs.isEmpty() || d->isEmpty) {
int ret = AVERROR(EAGAIN);
if (d->sourceFrame && d->outputs.isEmpty()) {
frame = d->sourceFrame;
ret = 0;
}
d->sourceFrame = {};
d->isEmpty = true;
return ret;
}
int ret = 0;
if (d->outputFrames.isEmpty()) {
for (int i = 0; i < d->outputs.size(); ++i) {
const auto &filter = d->outputs[i];
while (true) {
QAVFrame out = d->sourceFrame;
// av_buffersink_get_frame_flags allocates frame's data
av_frame_unref(out.frame());
{
QMutexLocker locker(&d->graphMutex);
ret = av_buffersink_get_frame_flags(filter.ctx(), out.frame(), 0);
}
if (ret < 0)
break;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 30, 0)
if (!out.frame()->pkt_duration)
out.frame()->pkt_duration = d->sourceFrame.frame()->pkt_duration;
#else
if (out.frame()->duration == AV_NOPTS_VALUE || out.frame()->duration == 0)
out.frame()->duration = d->sourceFrame.frame()->duration;
#endif
out.setFrameRate(av_buffersink_get_frame_rate(filter.ctx()));
out.setTimeBase(av_buffersink_get_time_base(filter.ctx()));
out.setFilterName(
!filter.name().isEmpty()
? filter.name()
: QString(QLatin1String("%1:%2")).arg(d->name).arg(QString::number(i)));
if (!out.stream())
out.setStream(d->stream);
d->outputFrames.push_back(out);
}
}
}
ret = AVERROR(EAGAIN);
if (!d->outputFrames.isEmpty()) {
frame = d->outputFrames.takeFirst();
ret = 0;
}
if (d->outputFrames.isEmpty()) {
d->sourceFrame = {};
d->isEmpty = true;
}
return ret;
}
void QAVAudioFilter::flush()
{
Q_D(QAVAudioFilter);
for (const auto &filter : d->inputs) {
int ret = av_buffersrc_add_frame(filter.ctx(), nullptr);
if (ret < 0)
qWarning() << "Could not flush:" << ret;
}
d->isEmpty = false;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,53 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOFILTER_P_H
#define QAVAUDIOFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavfilter_p.h"
#include "qavaudioinputfilter_p.h"
#include "qavaudiooutputfilter_p.h"
#include <QList>
#include <QMutex>
QT_BEGIN_NAMESPACE
class QAVAudioFilterPrivate;
class QAVAudioFilter : public QAVFilter
{
public:
QAVAudioFilter(
const QAVStream &stream,
const QString &name,
const QList<QAVAudioInputFilter> &inputs,
const QList<QAVAudioOutputFilter> &outputs,
QMutex &mutex);
int write(const QAVFrame &frame) override;
int read(QAVFrame &frame) override;
void flush() override;
protected:
Q_DECLARE_PRIVATE(QAVAudioFilter)
private:
Q_DISABLE_COPY(QAVAudioFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,61 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOFORMAT_H
#define QAVAUDIOFORMAT_H
#include <QtAVPlayer/qtavplayerglobal.h>
QT_BEGIN_NAMESPACE
class QAVAudioFormat
{
public:
enum SampleFormat
{
Unknown,
UInt8,
Int16,
Int32,
Float
};
SampleFormat sampleFormat() const { return m_sampleFormat; }
void setSampleFormat(SampleFormat f) { m_sampleFormat = f; }
int sampleRate() const { return m_sampleRate; }
void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }
int channelCount() const { return m_channelCount; }
void setChannelCount(int channelCount) { m_channelCount = channelCount; }
operator bool() const
{
return m_sampleFormat != SampleFormat::Unknown && m_sampleRate != 0 && m_channelCount != 0;
}
friend bool operator==(const QAVAudioFormat &a, const QAVAudioFormat &b)
{
return a.m_sampleRate == b.m_sampleRate &&
a.m_channelCount == b.m_channelCount &&
a.m_sampleFormat == b.m_sampleFormat;
}
friend bool operator!=(const QAVAudioFormat &a, const QAVAudioFormat &b)
{
return !(a == b);
}
private:
SampleFormat m_sampleFormat = Unknown;
int m_sampleRate = 0;
int m_channelCount = 0;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,216 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavaudioframe.h"
#include "qavframe_p.h"
#include "qavaudiocodec_p.h"
#include <QDebug>
extern "C" {
#include "libswresample/swresample.h"
}
QT_BEGIN_NAMESPACE
class QAVAudioFramePrivate : public QAVFramePrivate
{
public:
QAVAudioFormat outAudioFormat;
int inSampleRate = 0;
SwrContext *swr_ctx = nullptr;
uint8_t *audioBuf = nullptr;
QByteArray data;
};
QAVAudioFrame::QAVAudioFrame()
: QAVFrame(*new QAVAudioFramePrivate)
{
}
QAVAudioFrame::~QAVAudioFrame()
{
Q_D(QAVAudioFrame);
swr_free(&d->swr_ctx);
av_freep(&d->audioBuf);
}
QAVAudioFrame::QAVAudioFrame(const QAVFrame &other)
: QAVFrame(*new QAVAudioFramePrivate)
{
operator=(other);
}
QAVAudioFrame::QAVAudioFrame(const QAVAudioFrame &other)
: QAVFrame(*new QAVAudioFramePrivate)
{
operator=(other);
}
QAVAudioFrame::QAVAudioFrame(const QAVAudioFormat &format, const QByteArray &data)
: QAVAudioFrame()
{
Q_D(QAVAudioFrame);
d->outAudioFormat = format;
d->data = data;
}
QAVAudioFrame &QAVAudioFrame::operator=(const QAVFrame &other)
{
Q_D(QAVAudioFrame);
QAVFrame::operator=(other);
d->data.clear();
return *this;
}
QAVAudioFrame &QAVAudioFrame::operator=(const QAVAudioFrame &other)
{
Q_D(QAVAudioFrame);
QAVFrame::operator=(other);
auto rhs = reinterpret_cast<QAVAudioFramePrivate *>(other.d_ptr.get());
d->outAudioFormat = rhs->outAudioFormat;
d->data = rhs->data;
return *this;
}
QAVAudioFrame::operator bool() const
{
Q_D(const QAVAudioFrame);
return (d->outAudioFormat &&!d->data.isEmpty()) || QAVFrame::operator bool();
}
static const QAVAudioCodec *audioCodec(const QAVCodec *c)
{
return reinterpret_cast<const QAVAudioCodec *>(c);
}
QAVAudioFormat QAVAudioFrame::format() const
{
Q_D(const QAVAudioFrame);
if (d->outAudioFormat)
return d->outAudioFormat;
if (!d->stream)
return {};
auto c = audioCodec(d->stream.codec().data());
if (!c)
return {};
auto format = c->audioFormat();
if (format.sampleFormat() != QAVAudioFormat::Int32)
format.setSampleFormat(QAVAudioFormat::Int32);
return format;
}
QByteArray QAVAudioFrame::data() const
{
auto d = const_cast<QAVAudioFramePrivate *>(reinterpret_cast<QAVAudioFramePrivate *>(d_ptr.get()));
const auto frame = d->frame;
if (!frame)
return {};
if (d->outAudioFormat && !d->data.isEmpty())
return d->data;
const auto fmt = format();
AVSampleFormat outFormat = AV_SAMPLE_FMT_NONE;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
int64_t outChannelLayout = av_get_default_channel_layout(fmt.channelCount());
#else
AVChannelLayout outChannelLayout;
av_channel_layout_default(&outChannelLayout, fmt.channelCount());
#endif
int outSampleRate = fmt.sampleRate();
switch (fmt.sampleFormat()) {
case QAVAudioFormat::UInt8:
outFormat = AV_SAMPLE_FMT_U8;
break;
case QAVAudioFormat::Int16:
outFormat = AV_SAMPLE_FMT_S16;
break;
case QAVAudioFormat::Int32:
outFormat = AV_SAMPLE_FMT_S32;
break;
case QAVAudioFormat::Float:
outFormat = AV_SAMPLE_FMT_FLT;
break;
default:
qWarning() << "Could not negotiate output format:" << fmt.sampleFormat();
return {};
}
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
int64_t channelLayout = (frame->channel_layout && frame->channels == av_get_channel_layout_nb_channels(frame->channel_layout))
? frame->channel_layout
: av_get_default_channel_layout(frame->channels);
bool needsConvert = frame->format != outFormat || channelLayout != outChannelLayout || frame->sample_rate != outSampleRate;
#else
AVChannelLayout channelLayout =frame->ch_layout;
bool needsConvert = frame->format != outFormat || av_channel_layout_compare(&channelLayout, &outChannelLayout) || frame->sample_rate != outSampleRate;
#endif
if (needsConvert && (fmt != d->outAudioFormat || frame->sample_rate != d->inSampleRate || !d->swr_ctx)) {
swr_free(&d->swr_ctx);
#if LIBSWRESAMPLE_VERSION_INT <= AV_VERSION_INT(4, 4, 0)
d->swr_ctx = swr_alloc_set_opts(nullptr,
outChannelLayout, outFormat, outSampleRate,
channelLayout, AVSampleFormat(frame->format), frame->sample_rate,
0, nullptr);
#else
swr_alloc_set_opts2(&d->swr_ctx,
&outChannelLayout, outFormat, outSampleRate,
&channelLayout, AVSampleFormat(frame->format), frame->sample_rate,
0, nullptr);
#endif
int ret = swr_init(d->swr_ctx);
if (!d->swr_ctx || ret < 0) {
qWarning() << "Could not init SwrContext:" << ret;
return {};
}
}
if (d->swr_ctx) {
const uint8_t **in = (const uint8_t **)frame->extended_data;
int outCount = (int64_t)frame->nb_samples * outSampleRate / frame->sample_rate + 256;
int outSize = av_samples_get_buffer_size(nullptr, fmt.channelCount(), outCount, outFormat, 0);
av_freep(&d->audioBuf);
uint8_t **out = &d->audioBuf;
unsigned bufSize = 0;
av_fast_malloc(&d->audioBuf, &bufSize, outSize);
int samples = swr_convert(d->swr_ctx, out, outCount, in, frame->nb_samples);
if (samples < 0) {
qWarning() << "Could not convert audio samples";
return {};
}
int size = samples * fmt.channelCount() * av_get_bytes_per_sample(outFormat);
// Make deep copy
d->data = QByteArray((const char *)d->audioBuf, size);
} else {
int size = av_samples_get_buffer_size(nullptr,
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
frame->channels,
#else
outChannelLayout.nb_channels,
#endif
frame->nb_samples,
AVSampleFormat(frame->format), 1);
d->data = QByteArray::fromRawData((const char *)frame->data[0], size);
}
d->inSampleRate = frame->sample_rate;
d->outAudioFormat = fmt;
return d->data;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFAUDIORAME_H
#define QAVFAUDIORAME_H
#include <QtAVPlayer/qavframe.h>
#include <QtAVPlayer/qavaudioformat.h>
QT_BEGIN_NAMESPACE
class QAVAudioCodec;
class QAVAudioFramePrivate;
class QAVAudioFrame : public QAVFrame
{
public:
QAVAudioFrame();
~QAVAudioFrame();
QAVAudioFrame(const QAVFrame &other);
QAVAudioFrame(const QAVAudioFrame &other);
QAVAudioFrame(const QAVAudioFormat &format, const QByteArray &data);
QAVAudioFrame &operator=(const QAVFrame &other);
QAVAudioFrame &operator=(const QAVAudioFrame &other);
operator bool() const;
QAVAudioFormat format() const;
QByteArray data() const;
private:
Q_DECLARE_PRIVATE(QAVAudioFrame)
};
Q_DECLARE_METATYPE(QAVAudioFrame)
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,127 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#if !defined(__STDC_FORMAT_MACROS)
#define __STDC_FORMAT_MACROS 1
#endif
#include <inttypes.h>
#include "qavframe.h"
#include "qavaudioinputfilter_p.h"
#include "qavinoutfilter_p_p.h"
#include "qavdemuxer_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/bprint.h>
}
QT_BEGIN_NAMESPACE
class QAVAudioInputFilterPrivate : public QAVInOutFilterPrivate
{
public:
QAVAudioInputFilterPrivate(QAVInOutFilter *q)
: QAVInOutFilterPrivate(q)
{ }
AVSampleFormat format = AV_SAMPLE_FMT_NONE;
int sample_rate = 0;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
uint64_t channel_layout = 0;
int channels = 0;
#else
AVChannelLayout ch_layout;
#endif
};
QAVAudioInputFilter::QAVAudioInputFilter()
: QAVInOutFilter(*new QAVAudioInputFilterPrivate(this))
{
}
QAVAudioInputFilter::QAVAudioInputFilter(const QAVFrame &frame)
: QAVAudioInputFilter()
{
Q_D(QAVAudioInputFilter);
const auto & frm = frame.frame();
const auto & stream = frame.stream().stream();
d->format = frm->format != AV_SAMPLE_FMT_NONE ? AVSampleFormat(frm->format) : AVSampleFormat(stream->codecpar->format);
d->sample_rate = frm->sample_rate ? frm->sample_rate : stream->codecpar->sample_rate;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
d->channel_layout = frm->channel_layout ? frm->channel_layout : stream->codecpar->channel_layout;
d->channels = frm->channels ? frm->channels : stream->codecpar->channels;
#else
d->ch_layout = frm->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC ? frm->ch_layout : stream->codecpar->ch_layout;
#endif
}
QAVAudioInputFilter::QAVAudioInputFilter(const QAVAudioInputFilter &other)
: QAVAudioInputFilter()
{
*this = other;
}
QAVAudioInputFilter::~QAVAudioInputFilter() = default;
QAVAudioInputFilter &QAVAudioInputFilter::operator=(const QAVAudioInputFilter &other)
{
Q_D(QAVAudioInputFilter);
QAVInOutFilter::operator=(other);
d->format = other.d_func()->format;
d->sample_rate = other.d_func()->sample_rate;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
d->channel_layout = other.d_func()->channel_layout;
d->channels = other.d_func()->channels;
#else
d->ch_layout = other.d_func()->ch_layout;
#endif
return *this;
}
int QAVAudioInputFilter::configure(AVFilterGraph *graph, AVFilterInOut *in)
{
QAVInOutFilter::configure(graph, in);
Q_D(QAVAudioInputFilter);
AVBPrint args;
av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprintf(&args, "time_base=%d/%d:sample_rate=%d:sample_fmt=%s",
1, d->sample_rate,
d->sample_rate,
av_get_sample_fmt_name(AVSampleFormat(d->format)));
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 23, 0)
if (d->channel_layout)
av_bprintf(&args, ":channel_layout=0x%" PRIx64, d->channel_layout);
else
av_bprintf(&args, ":channels=%d", d->channels);
#else
if (av_channel_layout_check(&d->ch_layout) &&
d->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) {
av_bprintf(&args, ":channel_layout=");
av_channel_layout_describe_bprint(&d->ch_layout, &args);
} else {
av_bprintf(&args, ":channels=%d", d->ch_layout.nb_channels);
}
#endif
char name[255];
static int index = 0;
snprintf(name, sizeof(name), "abuffer_%d", index++);
int ret = avfilter_graph_create_filter(&d->ctx,
avfilter_get_by_name("abuffer"),
name, args.str, nullptr, graph);
if (ret < 0)
return ret;
return avfilter_link(d->ctx, 0, in->filter_ctx, in->pad_idx);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,45 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOINPUTFILTER_P_H
#define QAVAUDIOINPUTFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavinoutfilter_p.h"
QT_BEGIN_NAMESPACE
class QAVFrame;
class QAVAudioInputFilterPrivate;
class QAVAudioInputFilter : public QAVInOutFilter
{
public:
QAVAudioInputFilter(const QAVFrame &frame);
QAVAudioInputFilter(const QAVAudioInputFilter &other);
~QAVAudioInputFilter();
QAVAudioInputFilter &operator=(const QAVAudioInputFilter &other);
int configure(AVFilterGraph *graph, AVFilterInOut *in) override;
protected:
QAVAudioInputFilter();
Q_DECLARE_PRIVATE(QAVAudioInputFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,305 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavaudiooutput.h"
#include <QDebug>
#include <QtConcurrent/qtconcurrentrun.h>
#include <QFuture>
#include <QWaitCondition>
#include <QCoreApplication>
#include <QThreadPool>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAudioOutput>
#else
#include <QAudioSink>
#include <QMediaDevices>
#endif
extern "C" {
#include "libavutil/time.h"
}
QT_BEGIN_NAMESPACE
static QAudioFormat format(const QAVAudioFormat &from)
{
QAudioFormat out;
out.setSampleRate(from.sampleRate());
out.setChannelCount(from.channelCount());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
out.setByteOrder(QAudioFormat::LittleEndian);
out.setCodec(QLatin1String("audio/pcm"));
#endif
switch (from.sampleFormat()) {
case QAVAudioFormat::UInt8:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
out.setSampleSize(8);
out.setSampleType(QAudioFormat::UnSignedInt);
#else
out.setSampleFormat(QAudioFormat::UInt8);
#endif
break;
case QAVAudioFormat::Int16:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
out.setSampleSize(16);
out.setSampleType(QAudioFormat::SignedInt);
#else
out.setSampleFormat(QAudioFormat::Int16);
#endif
break;
case QAVAudioFormat::Int32:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
out.setSampleSize(32);
out.setSampleType(QAudioFormat::SignedInt);
#else
out.setSampleFormat(QAudioFormat::Int32);
#endif
break;
case QAVAudioFormat::Float:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
out.setSampleSize(32);
out.setSampleType(QAudioFormat::Float);
#else
out.setSampleFormat(QAudioFormat::Float);
#endif
break;
default:
qWarning() << "Could not negotiate output format:" << from.sampleFormat();
return {};
}
return out;
}
class QAVAudioOutputPrivate : public QIODevice
{
public:
QAVAudioOutputPrivate()
{
open(QIODevice::ReadOnly);
threadPool.setMaxThreadCount(1);
}
QFuture<void> audioPlayFuture;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
using AudioOutput = QAudioOutput;
using AudioDevice = QAudioDeviceInfo;
#else
using AudioOutput = QAudioSink;
using AudioDevice = QAudioDevice;
#endif
AudioOutput *audioOutput = nullptr;
qreal volume = 1.0;
int bufferSize = 0;
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
QAudioFormat::ChannelConfig channelConfig = QAudioFormat::ChannelConfigUnknown;
#endif
QList<QAVAudioFrame> frames;
qint64 offset = 0;
bool quit = 0;
AudioDevice defaultAudioDevice;
mutable QMutex mutex;
QWaitCondition cond;
QThreadPool threadPool;
qint64 readData(char *data, qint64 len) override
{
if (!len)
return 0;
QMutexLocker locker(&mutex);
qint64 bytesWritten = 0;
while (len && !quit) {
if (frames.isEmpty()) {
// Wait for more frames
if (bytesWritten == 0)
cond.wait(&mutex);
if (frames.isEmpty())
break;
}
auto frame = frames.front();
auto sampleData = frame.data();
const int toWrite = qMin(sampleData.size() - offset, len);
memcpy(data, sampleData.constData() + offset, toWrite);
bytesWritten += toWrite;
data += toWrite;
len -= toWrite;
offset += toWrite;
if (offset >= sampleData.size()) {
offset = 0;
frames.removeFirst();
}
}
return bytesWritten;
}
qint64 writeData(const char *, qint64) override { return 0; }
qint64 size() const override { return 0; }
qint64 bytesAvailable() const override { return std::numeric_limits<qint64>::max(); }
bool isSequential() const override { return true; }
bool atEnd() const override { return false; }
void tryInit(const QAudioFormat &fmt, int bsize, qreal v)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto audioDevice = QAudioDeviceInfo::defaultOutputDevice();
#else
auto audioDevice = QMediaDevices::defaultAudioOutput();
#endif
if (!audioOutput
|| (fmt.isValid() && audioOutput->format() != fmt)
|| audioOutput->state() == QAudio::StoppedState
|| defaultAudioDevice != audioDevice)
{
if (audioOutput) {
audioOutput->stop();
audioOutput->deleteLater();
}
audioOutput = new AudioOutput(audioDevice, fmt);
defaultAudioDevice = audioDevice;
QObject::connect(audioOutput, &AudioOutput::stateChanged, audioOutput,
[&](QAudio::State state) {
switch (state) {
case QAudio::StoppedState:
if (audioOutput->error() != QAudio::NoError)
qWarning() << "QAudioOutput stopped:" << audioOutput->error();
break;
default:
break;
}
});
if (bsize > 0)
audioOutput->setBufferSize(bsize);
audioOutput->setVolume(v);
audioOutput->start(this);
}
}
void doPlayAudio()
{
while (!quit) {
QMutexLocker locker(&mutex);
cond.wait(&mutex);
auto fmt = !frames.isEmpty() ? format(frames.first().format()) : QAudioFormat();
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
fmt.setChannelConfig(channelConfig);
#endif
auto v = volume;
auto bsize = bufferSize;
locker.unlock();
if (fmt.isValid())
tryInit(fmt, bsize, v);
if (audioOutput)
audioOutput->setVolume(v);
QCoreApplication::processEvents();
}
if (audioOutput) {
audioOutput->stop();
audioOutput->deleteLater();
}
audioOutput = nullptr;
}
void startThreadIfNeeded()
{
if (!audioPlayFuture.isRunning()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
audioPlayFuture = QtConcurrent::run(&threadPool, this, &QAVAudioOutputPrivate::doPlayAudio);
#else
audioPlayFuture = QtConcurrent::run(&threadPool, &QAVAudioOutputPrivate::doPlayAudio, this);
#endif
}
}
};
QAVAudioOutput::QAVAudioOutput(QObject *parent)
: QObject(parent)
, d_ptr(new QAVAudioOutputPrivate)
{
}
QAVAudioOutput::~QAVAudioOutput()
{
Q_D(QAVAudioOutput);
d->quit = true;
d->cond.wakeAll();
d->audioPlayFuture.waitForFinished();
}
void QAVAudioOutput::setVolume(qreal v)
{
Q_D(QAVAudioOutput);
QMutexLocker locker(&d->mutex);
d->volume = v;
d->cond.wakeAll();
}
qreal QAVAudioOutput::volume() const
{
Q_D(const QAVAudioOutput);
QMutexLocker locker(&d->mutex);
return d->volume;
}
void QAVAudioOutput::setBufferSize(int bytes)
{
Q_D(QAVAudioOutput);
QMutexLocker locker(&d->mutex);
d->bufferSize = bytes;
if (d->bufferSize > 0 && d->audioOutput)
d->audioOutput->setBufferSize(d->bufferSize);
}
int QAVAudioOutput::bufferSize() const
{
Q_D(const QAVAudioOutput);
QMutexLocker locker(&d->mutex);
return d->bufferSize;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
void QAVAudioOutput::setChannelConfig(QAudioFormat::ChannelConfig config)
{
Q_D(QAVAudioOutput);
QMutexLocker locker(&d->mutex);
d->channelConfig = config;
}
QAudioFormat::ChannelConfig QAVAudioOutput::channelConfig() const
{
Q_D(const QAVAudioOutput);
QMutexLocker locker(&d->mutex);
return d->channelConfig;
}
#endif
bool QAVAudioOutput::play(const QAVAudioFrame &frame)
{
Q_D(QAVAudioOutput);
if (d->quit || !frame)
return false;
QMutexLocker locker(&d->mutex);
d->startThreadIfNeeded();
d->frames.push_back(frame);
d->cond.wakeAll();
return true;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,47 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOOUTPUT_H
#define QAVAUDIOOUTPUT_H
#include <QtAVPlayer/qavaudioframe.h>
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QAudioFormat>
#include <QObject>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVAudioOutputPrivate;
class QAVAudioOutput : public QObject
{
public:
QAVAudioOutput(QObject *parent = nullptr);
~QAVAudioOutput();
void setVolume(qreal v);
qreal volume() const;
void setBufferSize(int bytes);
int bufferSize() const;
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
void setChannelConfig(QAudioFormat::ChannelConfig);
QAudioFormat::ChannelConfig channelConfig() const;
#endif
bool play(const QAVAudioFrame &frame);
protected:
std::unique_ptr<QAVAudioOutputPrivate> d_ptr;
private:
Q_DISABLE_COPY(QAVAudioOutput)
Q_DECLARE_PRIVATE(QAVAudioOutput)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,43 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavaudiooutputfilter_p.h"
#include "qavinoutfilter_p_p.h"
#include <QDebug>
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavutil/bprint.h>
}
QT_BEGIN_NAMESPACE
QAVAudioOutputFilter::QAVAudioOutputFilter()
: QAVInOutFilter(*new QAVInOutFilterPrivate(this))
{
}
QAVAudioOutputFilter::~QAVAudioOutputFilter() = default;
int QAVAudioOutputFilter::configure(AVFilterGraph *graph, AVFilterInOut *out)
{
QAVInOutFilter::configure(graph, out);
Q_D(QAVInOutFilter);
char name[255];
static int index = 0;
snprintf(name, sizeof(name), "out_%d", index++);
int ret = avfilter_graph_create_filter(&d->ctx,
avfilter_get_by_name("abuffersink"),
name, nullptr, nullptr, graph);
if (ret < 0)
return ret;
return avfilter_link(out->filter_ctx, out->pad_idx, d->ctx, 0);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,38 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVAUDIOOUTPUTFILTER_P_H
#define QAVAUDIOOUTPUTFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavinoutfilter_p.h"
QT_BEGIN_NAMESPACE
class QAVAudioOutputFilterPrivate;
class QAVAudioOutputFilter : public QAVInOutFilter
{
public:
QAVAudioOutputFilter();
~QAVAudioOutputFilter();
int configure(AVFilterGraph *graph, AVFilterInOut *out) override;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,99 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavcodec_p.h"
#include "qavcodec_p_p.h"
#include <QDebug>
extern "C" {
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
QAVCodec::QAVCodec()
: QAVCodec(*new QAVCodecPrivate)
{
}
QAVCodec::QAVCodec(QAVCodecPrivate &d)
: d_ptr(&d)
{
d_ptr->avctx = avcodec_alloc_context3(nullptr);
}
QAVCodec::~QAVCodec()
{
Q_D(QAVCodec);
if (d->avctx)
avcodec_free_context(&d->avctx);
}
void QAVCodec::setCodec(const AVCodec *c)
{
d_func()->codec = c;
}
bool QAVCodec::open(AVStream *stream)
{
Q_D(QAVCodec);
if (!stream)
return false;
int ret = avcodec_parameters_to_context(d->avctx, stream->codecpar);
if (ret < 0) {
qWarning() << "Failed avcodec_parameters_to_context:" << ret;
return false;
}
d->avctx->pkt_timebase = stream->time_base;
d->avctx->framerate = stream->avg_frame_rate;
if (!d->codec)
d->codec = avcodec_find_decoder(d->avctx->codec_id);
if (!d->codec) {
qWarning() << "No decoder could be found for codec";
return false;
}
d->avctx->codec_id = d->codec->id;
av_opt_set_int(d->avctx, "refcounted_frames", true, 0);
av_opt_set_int(d->avctx, "threads", 1, 0);
ret = avcodec_open2(d->avctx, d->codec, nullptr);
if (ret < 0) {
qWarning() << "Could not open the codec:" << d->codec->name << ret;
return false;
}
stream->discard = AVDISCARD_DEFAULT;
d->stream = stream;
return true;
}
AVCodecContext *QAVCodec::avctx() const
{
return d_func()->avctx;
}
const AVCodec *QAVCodec::codec() const
{
return d_func()->codec;
}
void QAVCodec::flushBuffers()
{
Q_D(QAVCodec);
if (!d->avctx)
return;
avcodec_flush_buffers(d->avctx);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,62 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVCODEC_P_H
#define QAVCODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavpacket_p.h"
#include "qavframe.h"
#include <QtAVPlayer/qtavplayerglobal.h>
#include <memory>
QT_BEGIN_NAMESPACE
struct AVCodec;
struct AVCodecContext;
struct AVStream;
class QAVCodecPrivate;
class QAVCodec
{
public:
virtual ~QAVCodec();
bool open(AVStream *stream);
AVCodecContext *avctx() const;
void setCodec(const AVCodec *c);
const AVCodec *codec() const;
void flushBuffers();
// Sends a packet
virtual int write(const QAVPacket &pkt) = 0;
// Receives a frame
// NOTE: There could be multiple frames
virtual int read(QAVStreamFrame &frame) = 0;
protected:
QAVCodec();
QAVCodec(QAVCodecPrivate &d);
std::unique_ptr<QAVCodecPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAVCodec)
private:
Q_DISABLE_COPY(QAVCodec)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVCODEC_P_P_H
#define QAVCODEC_P_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavcodec_p.h"
QT_BEGIN_NAMESPACE
struct AVCodec;
struct AVStream;
struct AVCodecContext;
class QAVCodecPrivate
{
public:
virtual ~QAVCodecPrivate() = default;
AVCodecContext *avctx = nullptr;
const AVCodec *codec = nullptr;
AVStream *stream = nullptr;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,876 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavdemuxer_p.h"
#include "qavvideocodec_p.h"
#include "qavaudiocodec_p.h"
#include "qavsubtitlecodec_p.h"
#include "qavhwdevice_p.h"
#include "qaviodevice.h"
#include <QtAVPlayer/qtavplayerglobal.h>
#if defined(QT_AVPLAYER_VA_X11) && QT_CONFIG(opengl)
#include "qavhwdevice_vaapi_x11_glx_p.h"
#endif
#if defined(QT_AVPLAYER_VA_DRM) && QT_CONFIG(egl)
#include "qavhwdevice_vaapi_drm_egl_p.h"
#endif
#if defined(QT_AVPLAYER_VDPAU)
#include "qavhwdevice_vdpau_p.h"
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include "qavhwdevice_videotoolbox_p.h"
#endif
#if defined(Q_OS_WIN)
#include "qavhwdevice_d3d11_p.h"
#endif
#if defined(Q_OS_ANDROID)
#include "qavhwdevice_mediacodec_p.h"
#include <QtCore/private/qjnihelpers_p.h>
extern "C" {
#include "libavcodec/jni.h"
}
#endif
#include <QDir>
#include <QSharedPointer>
#include <QMutexLocker>
#include <atomic>
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavcodec/avcodec.h>
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 0)
#include <libavcodec/bsf.h>
#endif
}
QT_BEGIN_NAMESPACE
class QAVDemuxerPrivate
{
Q_DECLARE_PUBLIC(QAVDemuxer)
public:
QAVDemuxerPrivate(QAVDemuxer *q)
: q_ptr(q)
{
}
QAVDemuxer *q_ptr = nullptr;
AVFormatContext *ctx = nullptr;
AVBSFContext *bsf_ctx = nullptr;
std::atomic_bool abortRequest = false;
mutable QMutex mutex;
bool seekable = false;
QList<QAVStream> availableStreams;
QList<QAVStream> currentVideoStreams;
QList<QAVStream> currentAudioStreams;
QList<QAVStream> currentSubtitleStreams;
QList<QAVStream::Progress> progress;
QString inputFormat;
QString inputVideoCodec;
QMap<QString, QString> inputOptions;
bool eof = false;
QList<QAVPacket> packets;
QString bsfs;
};
static int decode_interrupt_cb(void *ctx)
{
auto d = reinterpret_cast<QAVDemuxerPrivate *>(ctx);
return d ? int(d->abortRequest) : 0;
}
QAVDemuxer::QAVDemuxer()
: d_ptr(new QAVDemuxerPrivate(this))
{
static bool loaded = false;
if (!loaded) {
#if (LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58,9,100))
av_register_all();
avcodec_register_all();
#endif
avdevice_register_all();
loaded = true;
}
}
QAVDemuxer::~QAVDemuxer()
{
abort(false);
unload();
}
void QAVDemuxer::abort(bool stop)
{
Q_D(QAVDemuxer);
d->abortRequest = stop;
}
static int setup_video_codec(const QString &inputVideoCodec, AVStream *stream, QAVVideoCodec &codec)
{
const AVCodec *videoCodec = nullptr;
if (!inputVideoCodec.isEmpty()) {
qDebug() << "Loading: -vcodec" << inputVideoCodec;
videoCodec = avcodec_find_decoder_by_name(inputVideoCodec.toUtf8().constData());
if (!videoCodec) {
qWarning() << "Could not find decoder:" << inputVideoCodec;
return AVERROR(EINVAL);
}
}
if (videoCodec)
codec.setCodec(videoCodec);
QList<QSharedPointer<QAVHWDevice>> devices;
AVDictionary *opts = NULL;
Q_UNUSED(opts);
#if defined(QT_AVPLAYER_VA_X11) && QT_CONFIG(opengl)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_VAAPI_X11_GLX));
av_dict_set(&opts, "connection_type", "x11", 0);
#endif
#if defined(QT_AVPLAYER_VDPAU)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_VDPAU));
#endif
#if defined(QT_AVPLAYER_VA_DRM) && QT_CONFIG(egl)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_VAAPI_DRM_EGL));
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_VideoToolbox));
#endif
#if defined(Q_OS_WIN)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_D3D11));
#endif
#if defined(Q_OS_ANDROID)
devices.append(QSharedPointer<QAVHWDevice>(new QAVHWDevice_MediaCodec));
if (!codec.codec())
codec.setCodec(avcodec_find_decoder_by_name("h264_mediacodec"));
auto vm = QtAndroidPrivate::javaVM();
av_jni_set_java_vm(vm, NULL);
#endif
const bool ignoreHW = qEnvironmentVariableIsSet("QT_AVPLAYER_NO_HWDEVICE");
if (!ignoreHW) {
AVBufferRef *hw_device_ctx = nullptr;
for (auto &device : devices) {
auto deviceName = av_hwdevice_get_type_name(device->type());
qDebug() << "Creating hardware device context:" << deviceName;
if (av_hwdevice_ctx_create(&hw_device_ctx, device->type(), nullptr, opts, 0) >= 0) {
qDebug() << "Using hardware device context:" << deviceName;
codec.avctx()->hw_device_ctx = hw_device_ctx;
codec.avctx()->pix_fmt = device->format();
codec.setDevice(device);
break;
}
av_buffer_unref(&hw_device_ctx);
}
}
// Open codec after hwdevices
if (!codec.open(stream)) {
qWarning() << "Could not open video codec for stream";
return AVERROR(EINVAL);
}
return 0;
}
static void log_callback(void *ptr, int level, const char *fmt, va_list vl)
{
if (level > av_log_get_level())
return;
va_list vl2;
char line[1024];
static int print_prefix = 1;
va_copy(vl2, vl);
av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix);
va_end(vl2);
qDebug() << "FFmpeg:" << line;
}
QStringList QAVDemuxer::supportedFormats()
{
static QStringList values;
if (values.isEmpty()) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 0, 0)
const AVInputFormat *fmt = nullptr;
void *it = nullptr;
while ((fmt = av_demuxer_iterate(&it))) {
if (fmt->name)
values << QString::fromLatin1(fmt->name).split(QLatin1Char(','),
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QString::SkipEmptyParts
#else
Qt::SkipEmptyParts
#endif
);
}
#endif
}
return values;
}
QStringList QAVDemuxer::supportedVideoCodecs()
{
static QStringList values;
if (values.isEmpty()) {
const AVCodec *c = nullptr;
void *it = nullptr;
while ((c = av_codec_iterate(&it))) {
if (!av_codec_is_decoder(c) || c->type != AVMEDIA_TYPE_VIDEO)
continue;
values.append(QString::fromLatin1(c->name));
}
}
return values;
}
QStringList QAVDemuxer::supportedProtocols()
{
static QStringList values;
if (values.isEmpty()) {
void *opq = 0;
const char *value = nullptr;
while ((value = avio_enum_protocols(&opq, 0)))
values << QString::fromUtf8(value);
}
return values;
}
static int init_output_bsfs(AVBSFContext *ctx, AVStream *st)
{
if (!ctx)
return 0;
int ret = avcodec_parameters_copy(ctx->par_in, st->codecpar);
if (ret < 0)
return ret;
ctx->time_base_in = st->time_base;
ret = av_bsf_init(ctx);
if (ret < 0) {
qWarning() << "Error initializing bitstream filter:" << ctx->filter->name;
return ret;
}
ret = avcodec_parameters_copy(st->codecpar, ctx->par_out);
if (ret < 0)
return ret;
st->time_base = ctx->time_base_out;
return 0;
}
static int apply_bsf(const QString &bsf, AVFormatContext *ctx, AVBSFContext *&bsf_ctx)
{
int ret = !bsf.isEmpty() ? av_bsf_list_parse_str(bsf.toUtf8().constData(), &bsf_ctx) : 0;
if (ret < 0) {
qWarning() << "Error parsing bitstream filter sequence:" << bsf;
return ret;
}
for (std::size_t i = 0; i < ctx->nb_streams; ++i) {
switch (ctx->streams[i]->codecpar->codec_type) {
case AVMEDIA_TYPE_VIDEO:
case AVMEDIA_TYPE_AUDIO:
ret = init_output_bsfs(bsf_ctx, ctx->streams[i]);
break;
default:
break;
}
}
return ret;
}
int QAVDemuxer::load(const QString &url, QAVIODevice *dev)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
if (!d->ctx)
d->ctx = avformat_alloc_context();
d->ctx->flags |= AVFMT_FLAG_GENPTS;
d->ctx->interrupt_callback.callback = decode_interrupt_cb;
d->ctx->interrupt_callback.opaque = d;
if (dev) {
d->ctx->pb = dev->ctx();
d->ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 0)
const
#endif
AVInputFormat *inputFormat = nullptr;
if (!d->inputFormat.isEmpty()) {
qDebug() << "Loading: -f" << d->inputFormat;
inputFormat = av_find_input_format(d->inputFormat.toUtf8().constData());
if (!inputFormat) {
qWarning() << "Could not find input format:" << d->inputFormat;
return AVERROR(EINVAL);
}
}
AVDictionary *opts = nullptr;
for (const auto & key: d->inputOptions.keys())
av_dict_set(&opts, key.toUtf8().constData(), d->inputOptions[key].toUtf8().constData(), 0);
locker.unlock();
int ret = avformat_open_input(&d->ctx, url.toUtf8().constData(), inputFormat, &opts);
if (ret < 0)
return ret;
ret = avformat_find_stream_info(d->ctx, NULL);
if (ret < 0)
return ret;
locker.relock();
av_log_set_callback(log_callback);
d->seekable = d->ctx->iformat->read_seek || d->ctx->iformat->read_seek2;
if (d->ctx->pb)
d->seekable |= bool(d->ctx->pb->seekable);
ret = resetCodecs();
if (ret < 0)
return ret;
const int videoStreamIndex = av_find_best_stream(
d->ctx,
AVMEDIA_TYPE_VIDEO,
-1,
-1,
nullptr,
0);
if (videoStreamIndex >= 0)
d->currentVideoStreams.push_back(d->availableStreams[videoStreamIndex]);
const int audioStreamIndex = av_find_best_stream(
d->ctx,
AVMEDIA_TYPE_AUDIO,
-1,
videoStreamIndex,
nullptr,
0);
if (audioStreamIndex >= 0)
d->currentAudioStreams.push_back(d->availableStreams[audioStreamIndex]);
const int subtitleStreamIndex = av_find_best_stream(
d->ctx,
AVMEDIA_TYPE_SUBTITLE,
-1,
audioStreamIndex >= 0 ? audioStreamIndex : videoStreamIndex,
nullptr,
0);
if (subtitleStreamIndex >= 0)
d->currentSubtitleStreams.push_back(d->availableStreams[subtitleStreamIndex]);
if (ret < 0)
return ret;
if (!d->bsfs.isEmpty())
return apply_bsf(d->bsfs, d->ctx, d->bsf_ctx);
return 0;
}
int QAVDemuxer::resetCodecs()
{
Q_D(QAVDemuxer);
int ret = 0;
for (std::size_t i = 0; i < d->ctx->nb_streams && ret >= 0; ++i) {
if (!d->ctx->streams[i]->codecpar) {
qWarning() << "Could not find codecpar";
return AVERROR(EINVAL);
}
const AVMediaType type = d->ctx->streams[i]->codecpar->codec_type;
switch (type) {
case AVMEDIA_TYPE_VIDEO:
{
QSharedPointer<QAVCodec> codec(new QAVVideoCodec);
d->availableStreams.push_back({ int(i), d->ctx, codec });
ret = setup_video_codec(d->inputVideoCodec, d->ctx->streams[i], *static_cast<QAVVideoCodec *>(codec.data()));
} break;
case AVMEDIA_TYPE_AUDIO:
d->availableStreams.push_back({ int(i), d->ctx, QSharedPointer<QAVCodec>(new QAVAudioCodec) });
if (!d->availableStreams.last().codec()->open(d->ctx->streams[i]))
qWarning() << "Could not open audio codec for stream:" << i;
break;
case AVMEDIA_TYPE_SUBTITLE:
d->availableStreams.push_back({ int(i), d->ctx, QSharedPointer<QAVCodec>(new QAVSubtitleCodec) });
if (!d->availableStreams.last().codec()->open(d->ctx->streams[i]))
qWarning() << "Could not open subtitle codec for stream:" << i;
break;
default:
// Adding default stream
d->availableStreams.push_back({ int(i), d->ctx, nullptr });
break;
}
auto &s = d->availableStreams[int(i)];
d->progress.push_back({ s.duration(), s.framesCount(), s.frameRate() });
}
return ret;
}
static bool findStream(
const QList<QAVStream> &streams,
int index)
{
for (const auto &stream: streams) {
if (index == stream.index())
return true;
}
return false;
}
AVMediaType QAVDemuxer::currentCodecType(int index) const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
// TODO:
if (findStream(d->currentVideoStreams, index))
return AVMEDIA_TYPE_VIDEO;
if (findStream(d->currentAudioStreams, index))
return AVMEDIA_TYPE_AUDIO;
if (findStream(d->currentSubtitleStreams, index))
return AVMEDIA_TYPE_SUBTITLE;
return AVMEDIA_TYPE_UNKNOWN;
}
static QList<QAVStream> availableStreamsByType(
const QList<QAVStream> &streams,
AVMediaType type)
{
QList<QAVStream> ret;
for (auto &stream : streams) {
if (stream.stream()->codecpar->codec_type == type)
ret.push_back(stream);
}
return ret;
}
QList<QAVStream> QAVDemuxer::availableVideoStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return availableStreamsByType(d->availableStreams, AVMEDIA_TYPE_VIDEO);
}
QList<QAVStream> QAVDemuxer::currentVideoStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->currentVideoStreams;
}
static bool setCurrentStreams(
const QList<QAVStream> &streams,
const QList<QAVStream> &availableStreams,
AVMediaType type,
QList<QAVStream> &currentStreams)
{
QList<QAVStream> ret;
for (const auto &stream: streams) {
if (stream.index() >= 0
&& stream.index() < availableStreams.size()
&& availableStreams[stream.index()].stream()->codecpar->codec_type == type)
{
ret.push_back(availableStreams[stream.index()]);
}
}
if (!ret.isEmpty() || streams.isEmpty()) {
currentStreams = ret;
return true;
}
return false;
}
bool QAVDemuxer::setVideoStreams(const QList<QAVStream> &streams)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
return setCurrentStreams(
streams,
d->availableStreams,
AVMEDIA_TYPE_VIDEO,
d->currentVideoStreams);
}
QList<QAVStream> QAVDemuxer::availableAudioStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return availableStreamsByType(
d->availableStreams,
AVMEDIA_TYPE_AUDIO);
}
QList<QAVStream> QAVDemuxer::currentAudioStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->currentAudioStreams;
}
bool QAVDemuxer::setAudioStreams(const QList<QAVStream> &streams)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
return setCurrentStreams(
streams,
d->availableStreams,
AVMEDIA_TYPE_AUDIO,
d->currentAudioStreams);
}
QList<QAVStream> QAVDemuxer::availableSubtitleStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return availableStreamsByType(
d->availableStreams,
AVMEDIA_TYPE_SUBTITLE);
}
QList<QAVStream> QAVDemuxer::currentSubtitleStreams() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->currentSubtitleStreams;
}
bool QAVDemuxer::setSubtitleStreams(const QList<QAVStream> &streams)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
return setCurrentStreams(
streams,
d->availableStreams,
AVMEDIA_TYPE_SUBTITLE,
d->currentSubtitleStreams);
}
void QAVDemuxer::unload()
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
if (d->ctx) {
avformat_close_input(&d->ctx);
avformat_free_context(d->ctx);
}
d->ctx = nullptr;
d->eof = false;
d->abortRequest = 0;
d->currentVideoStreams.clear();
d->currentAudioStreams.clear();
d->currentSubtitleStreams.clear();
d->availableStreams.clear();
d->progress.clear();
av_bsf_free(&d->bsf_ctx);
d->bsf_ctx = nullptr;
}
bool QAVDemuxer::eof() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->eof;
}
QAVPacket QAVDemuxer::read()
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
if (!d->packets.isEmpty())
return d->packets.takeFirst();
if (!d->ctx || d->eof)
return {};
QAVPacket pkt;
locker.unlock();
int ret = av_read_frame(d->ctx, pkt.packet());
if (ret < 0) {
if (ret == AVERROR_EOF || avio_feof(d->ctx->pb)) {
locker.relock();
d->eof = true;
locker.unlock();
}
}
locker.relock();
QAVStream stream = pkt.packet()->stream_index < d->availableStreams.size()
? d->availableStreams[pkt.packet()->stream_index]
: QAVStream();
Q_ASSERT(stream.stream());
pkt.setStream(stream);
if (d->bsf_ctx) {
ret = av_bsf_send_packet(d->bsf_ctx, d->eof ? NULL : pkt.packet());
if (ret >= 0) {
while ((ret = av_bsf_receive_packet(d->bsf_ctx, pkt.packet())) >= 0)
d->packets.append(pkt);
}
if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
qWarning() << "Error applying bitstream filters to an output:" << ret;
return {};
}
} else {
d->packets.append(pkt);
}
return !d->packets.isEmpty() ? d->packets.takeFirst() : QAVPacket{};
}
void QAVDemuxer::decode(const QAVPacket &pkt, QList<QAVFrame> &frames) const
{
if (!pkt.stream())
return;
int sent = 0;
do {
sent = pkt.send();
// AVERROR(EAGAIN): input is not accepted in the current state - user must read output with avcodec_receive_frame()
// (once all output is read, the packet should be resent, and the call will not fail with EAGAIN)
if (sent < 0 && sent != AVERROR(EAGAIN))
return;
while (true) {
QAVFrame frame;
frame.setStream(pkt.stream());
// AVERROR(EAGAIN): output is not available in this state - user must try to send new input
int received = frame.receive();
if (received < 0)
break;
frames.push_back(frame);
}
} while (sent == AVERROR(EAGAIN));
}
void QAVDemuxer::decode(const QAVPacket &pkt, QList<QAVSubtitleFrame> &frames) const
{
if (!pkt.stream())
return;
int sent = pkt.send();
if (sent < 0 && sent != AVERROR(EAGAIN))
return;
QAVSubtitleFrame frame;
frame.setStream(pkt.stream());
if (frame.receive() >= 0)
frames.push_back(frame);
}
void QAVDemuxer::flushCodecBuffers()
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
for (auto &s: d->availableStreams) {
auto c = s.codec();
if (c)
c->flushBuffers();
}
}
bool QAVDemuxer::seekable() const
{
return d_func()->seekable;
}
int QAVDemuxer::seek(double sec)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
if (!d->ctx || !d->seekable)
return AVERROR(EINVAL);
d->eof = false;
locker.unlock();
int flags = AVSEEK_FLAG_BACKWARD;
int64_t target = sec * AV_TIME_BASE;
int64_t min = INT_MIN;
int64_t max = target;
return avformat_seek_file(d->ctx, -1, min, target, max, flags);
}
double QAVDemuxer::duration() const
{
Q_D(const QAVDemuxer);
if (!d->ctx || d->ctx->duration == AV_NOPTS_VALUE)
return 0.0;
return d->ctx->duration * av_q2d({1, AV_TIME_BASE});
}
double QAVDemuxer::videoFrameRate() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
if (d->currentVideoStreams.isEmpty())
return 0.0;
// TODO:
double ret = std::numeric_limits<double>::max();
for (const auto &stream: d->currentVideoStreams) {
AVRational fr = av_guess_frame_rate(d->ctx, d->ctx->streams[stream.index()], NULL);
double rate = fr.num && fr.den ? av_q2d({fr.den, fr.num}) : 0.0;
if (rate < ret)
ret = rate;
}
return ret;
}
QMap<QString, QString> QAVDemuxer::metadata() const
{
Q_D(const QAVDemuxer);
QMap<QString, QString> result;
if (d->ctx == nullptr)
return result;
AVDictionaryEntry *tag = nullptr;
while ((tag = av_dict_get(d->ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
result[QString::fromUtf8(tag->key)] = QString::fromUtf8(tag->value);
return result;
}
QString QAVDemuxer::bitstreamFilter() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->bsfs;
}
int QAVDemuxer::applyBitstreamFilter(const QString &bsfs)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
d->bsfs = bsfs;
int ret = 0;
if (d->ctx) {
av_bsf_free(&d->bsf_ctx);
d->bsf_ctx = nullptr;
ret = apply_bsf(d->bsfs, d->ctx, d->bsf_ctx);
}
return ret;
}
QString QAVDemuxer::inputFormat() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->inputFormat;
}
void QAVDemuxer::setInputFormat(const QString &format)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
d->inputFormat = format;
}
QString QAVDemuxer::inputVideoCodec() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->inputVideoCodec;
}
void QAVDemuxer::setInputVideoCodec(const QString &codec)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
d->inputVideoCodec = codec;
}
QMap<QString, QString> QAVDemuxer::inputOptions() const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
return d->inputOptions;
}
void QAVDemuxer::setInputOptions(const QMap<QString, QString> &opts)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
d->inputOptions = opts;
}
void QAVDemuxer::onFrameSent(const QAVStreamFrame &frame)
{
Q_D(QAVDemuxer);
QMutexLocker locker(&d->mutex);
int index = frame.stream().index();
if (index >= 0 && index < d->progress.size())
d->progress[index].onFrameSent(frame.pts());
}
bool QAVDemuxer::isMasterStream(const QAVStream &stream) const
{
auto s = stream.stream();
switch (s->codecpar->codec_type) {
case AVMEDIA_TYPE_VIDEO:
return s->disposition != AV_DISPOSITION_ATTACHED_PIC;
case AVMEDIA_TYPE_AUDIO:
// Check if there are any video streams available
for (const auto &vs: currentVideoStreams()) {
if (vs.stream()->disposition != AV_DISPOSITION_ATTACHED_PIC)
return false;
}
return true;
default:
Q_ASSERT(false);
return false;
}
}
QAVStream::Progress QAVDemuxer::progress(const QAVStream &s) const
{
Q_D(const QAVDemuxer);
QMutexLocker locker(&d->mutex);
int index = s.index();
if (index >= 0 && index < d->progress.size())
return d->progress[index];
return {};
}
QStringList QAVDemuxer::supportedBitstreamFilters()
{
QStringList result;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 0, 0)
const AVBitStreamFilter *bsf = NULL;
void *opaque = NULL;
while ((bsf = av_bsf_iterate(&opaque)))
result.append(QString::fromUtf8(bsf->name));
#endif
return result;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,114 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVDEMUXER_H
#define QAVDEMUXER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavpacket_p.h"
#include "qavstream.h"
#include "qavframe.h"
#include "qavsubtitleframe.h"
#include <QMap>
#include <memory>
QT_BEGIN_NAMESPACE
extern "C" {
#include <libavutil/avutil.h>
}
class QAVDemuxerPrivate;
class QAVVideoCodec;
class QAVAudioCodec;
class QAVIODevice;
struct AVStream;
struct AVCodecContext;
struct AVFormatContext;
class QAVDemuxer
{
public:
QAVDemuxer();
~QAVDemuxer();
void abort(bool stop = true);
int load(const QString &url, QAVIODevice *dev = nullptr);
void unload();
AVMediaType currentCodecType(int index) const;
QList<QAVStream> availableVideoStreams() const;
QList<QAVStream> currentVideoStreams() const;
bool setVideoStreams(const QList<QAVStream> &streams);
QList<QAVStream> availableAudioStreams() const;
QList<QAVStream> currentAudioStreams() const;
bool setAudioStreams(const QList<QAVStream> &streams);
QList<QAVStream> availableSubtitleStreams() const;
QList<QAVStream> currentSubtitleStreams() const;
bool setSubtitleStreams(const QList<QAVStream> &streams);
QAVPacket read();
void decode(const QAVPacket &pkt, QList<QAVFrame> &frames) const;
void decode(const QAVPacket &pkt, QList<QAVSubtitleFrame> &frames) const;
void flushCodecBuffers();
double duration() const;
bool seekable() const;
int seek(double sec);
bool eof() const;
double videoFrameRate() const;
QMap<QString, QString> metadata() const;
QString bitstreamFilter() const;
int applyBitstreamFilter(const QString &bsfs);
QString inputFormat() const;
void setInputFormat(const QString &format);
QString inputVideoCodec() const;
void setInputVideoCodec(const QString &codec);
QMap<QString, QString> inputOptions() const;
void setInputOptions(const QMap<QString, QString> &opts);
void onFrameSent(const QAVStreamFrame &frame);
QAVStream::Progress progress(const QAVStream &s) const;
bool isMasterStream(const QAVStream &stream) const;
static QStringList supportedFormats();
static QStringList supportedVideoCodecs();
static QStringList supportedProtocols();
static QStringList supportedBitstreamFilters();
protected:
std::unique_ptr<QAVDemuxerPrivate> d_ptr;
private:
int resetCodecs();
Q_DISABLE_COPY(QAVDemuxer)
Q_DECLARE_PRIVATE(QAVDemuxer)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,31 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavfilter_p.h"
#include "qavfilter_p_p.h"
#include <QDebug>
QT_BEGIN_NAMESPACE
QAVFilter::QAVFilter(
const QAVStream &stream,
const QString &name,
QAVFilterPrivate &d)
: d_ptr(&d)
{
d.stream = stream;
d.name = name;
}
QAVFilter::~QAVFilter() = default;
bool QAVFilter::isEmpty() const
{
return d_func()->isEmpty;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,54 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFILTER_P_H
#define QAVFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QtAVPlayer/qavframe.h>
#include <QtAVPlayer/qavstream.h>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVFilterPrivate;
class QAVFilter
{
public:
virtual ~QAVFilter();
virtual int write(const QAVFrame &frame) = 0;
virtual int read(QAVFrame &frame) = 0;
// Checks if all frames have been read
bool isEmpty() const;
virtual void flush() = 0;
protected:
QAVFilter(
const QAVStream &stream,
const QString &name,
QAVFilterPrivate &d);
std::unique_ptr<QAVFilterPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAVFilter)
private:
Q_DISABLE_COPY(QAVFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,48 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFILTER_P_P_H
#define QAVFILTER_P_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtAVPlayer/qavframe.h>
#include <QtAVPlayer/qavstream.h>
#include <QList>
#include <QMutex>
QT_BEGIN_NAMESPACE
class QAVFilter;
class QAVFilterPrivate
{
Q_DECLARE_PUBLIC(QAVFilter)
public:
QAVFilterPrivate(QAVFilter *q, QMutex &mutex) : q_ptr(q), graphMutex(mutex) { }
virtual ~QAVFilterPrivate() = default;
QAVFilter *q_ptr = nullptr;
QAVStream stream;
QString name;
QAVFrame sourceFrame;
QList<QAVFrame> outputFrames;
bool isEmpty = true;
QMutex &graphMutex;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,186 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavfiltergraph_p.h"
#include "qavcodec_p.h"
#include "qavvideocodec_p.h"
#include "qavaudiocodec_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}
QT_BEGIN_NAMESPACE
class QAVFilterGraphPrivate
{
Q_DECLARE_PUBLIC(QAVFilterGraph)
public:
QAVFilterGraphPrivate(QAVFilterGraph *q) : q_ptr(q) { }
int read();
QAVFilterGraph *q_ptr = nullptr;
QString desc;
AVFilterGraph *graph = nullptr;
AVFilterInOut *outputs = nullptr;
AVFilterInOut *inputs = nullptr;
QList<QAVVideoInputFilter> videoInputFilters;
QList<QAVVideoOutputFilter> videoOutputFilters;
QList<QAVAudioInputFilter> audioInputFilters;
QList<QAVAudioOutputFilter> audioOutputFilters;
mutable QMutex mutex;
};
QAVFilterGraph::QAVFilterGraph()
: d_ptr(new QAVFilterGraphPrivate(this))
{
}
QAVFilterGraph::~QAVFilterGraph()
{
Q_D(QAVFilterGraph);
avfilter_graph_free(&d->graph);
avfilter_inout_free(&d->inputs);
avfilter_inout_free(&d->outputs);
}
int QAVFilterGraph::parse(const QString &desc)
{
Q_D(QAVFilterGraph);
d->desc = desc;
avfilter_graph_free(&d->graph);
avfilter_inout_free(&d->inputs);
avfilter_inout_free(&d->outputs);
d->graph = avfilter_graph_alloc();
return avfilter_graph_parse2(d->graph, desc.toUtf8().constData(), &d->inputs, &d->outputs);
}
int QAVFilterGraph::apply(const QAVFrame &frame)
{
Q_D(QAVFilterGraph);
if (!frame.stream())
return 0;
const AVMediaType codec_type = frame.stream().stream()->codecpar->codec_type;
switch (codec_type) {
case AVMEDIA_TYPE_VIDEO:
d->videoInputFilters.clear();
d->videoOutputFilters.clear();
break;
case AVMEDIA_TYPE_AUDIO:
d->audioInputFilters.clear();
d->audioOutputFilters.clear();
break;
default:
qWarning() << "Could not apply frame: Unsupported codec type:" << codec_type;
return AVERROR(EINVAL);
}
int ret = 0;
int i = 0;
AVFilterInOut *cur = nullptr;
for (cur = d->inputs, i = 0; cur; cur = cur->next, ++i) {
switch (avfilter_pad_get_type(cur->filter_ctx->input_pads, cur->pad_idx)) {
case AVMEDIA_TYPE_VIDEO: {
if (codec_type == AVMEDIA_TYPE_VIDEO) {
QAVVideoInputFilter filter(frame);
ret = filter.configure(d->graph, cur);
if (ret < 0)
return ret;
d->videoInputFilters.push_back(filter);
}
} break;
case AVMEDIA_TYPE_AUDIO: {
if (codec_type == AVMEDIA_TYPE_AUDIO) {
QAVAudioInputFilter filter(frame);
ret = filter.configure(d->graph, cur);
if (ret < 0)
return ret;
d->audioInputFilters.push_back(filter);
}
} break;
default:
return AVERROR(EINVAL);
}
}
for (cur = d->outputs, i = 0; cur; cur = cur->next, ++i) {
switch (avfilter_pad_get_type(cur->filter_ctx->output_pads, cur->pad_idx)) {
case AVMEDIA_TYPE_VIDEO: {
if (codec_type == AVMEDIA_TYPE_VIDEO) {
QAVVideoOutputFilter filter;
ret = filter.configure(d->graph, cur);
if (ret < 0)
return ret;
d->videoOutputFilters.push_back(filter);
}
} break;
case AVMEDIA_TYPE_AUDIO: {
if (codec_type == AVMEDIA_TYPE_AUDIO) {
QAVAudioOutputFilter filter;
int ret = filter.configure(d->graph, cur);
if (ret < 0)
return ret;
d->audioOutputFilters.push_back(filter);
}
} break;
default:
return AVERROR(EINVAL);
}
}
return ret;
}
int QAVFilterGraph::config()
{
Q_D(QAVFilterGraph);
return avfilter_graph_config(d->graph, nullptr);
}
QString QAVFilterGraph::desc() const
{
Q_D(const QAVFilterGraph);
return d->desc;
}
QMutex &QAVFilterGraph::mutex()
{
Q_D(QAVFilterGraph);
return d->mutex;
}
AVFilterGraph *QAVFilterGraph::graph() const
{
return d_func()->graph;
}
QList<QAVVideoInputFilter> QAVFilterGraph::videoInputFilters() const
{
return d_func()->videoInputFilters;
}
QList<QAVVideoOutputFilter> QAVFilterGraph::videoOutputFilters() const
{
return d_func()->videoOutputFilters;
}
QList<QAVAudioInputFilter> QAVFilterGraph::audioInputFilters() const
{
return d_func()->audioInputFilters;
}
QList<QAVAudioOutputFilter> QAVFilterGraph::audioOutputFilters() const
{
return d_func()->audioOutputFilters;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,64 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFILTERGRAPH_P_H
#define QAVFILTERGRAPH_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavvideoinputfilter_p.h"
#include "qavvideooutputfilter_p.h"
#include "qavaudioinputfilter_p.h"
#include "qavaudiooutputfilter_p.h"
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QtAVPlayer/qavframe.h>
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudioframe.h>
#include <QMutex>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVFilterGraphPrivate;
class QAVDemuxer;
class QAVFilterGraph
{
public:
QAVFilterGraph();
~QAVFilterGraph();
int parse(const QString &desc);
int apply(const QAVFrame &frame);
int config();
QString desc() const;
QMutex &mutex();
AVFilterGraph *graph() const;
QList<QAVVideoInputFilter> videoInputFilters() const;
QList<QAVVideoOutputFilter> videoOutputFilters() const;
QList<QAVAudioInputFilter> audioInputFilters() const;
QList<QAVAudioOutputFilter> audioOutputFilters() const;
protected:
std::unique_ptr<QAVFilterGraphPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAVFilterGraph)
private:
Q_DISABLE_COPY(QAVFilterGraph)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,220 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavfilters_p.h"
#include "qavvideofilter_p.h"
#include "qavaudiofilter_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
int QAVFilters::createFilters(
const QList<QString> &filterDescs,
const QAVFrame &frame,
const QAVDemuxer &demuxer)
{
QMutexLocker locker(&m_mutex);
m_videoFilters.clear();
m_audioFilters.clear();
m_filterGraphs.clear();
for (int i = 0; i < filterDescs.size(); ++i) {
const auto & filterDesc = filterDescs[i];
std::unique_ptr<QAVFilterGraph> graph(!filterDesc.isEmpty() ? new QAVFilterGraph : nullptr);
if (graph) {
int ret = graph->parse(filterDesc);
if (ret < 0) {
qWarning() << "Could not parse filter desc:" << filterDesc << ret;
return ret;
}
QAVFrame videoFrame;
QAVFrame audioFrame;
const auto videoStreams = demuxer.currentVideoStreams();
const auto videoStream = !videoStreams.isEmpty() ? videoStreams.first() : QAVStream();
videoFrame.setStream(videoStream);
const auto audioStreams = demuxer.currentAudioStreams();
const auto audioStream = !audioStreams.isEmpty() ? audioStreams.first() : QAVStream();
audioFrame.setStream(audioStream);
auto stream = frame.stream().stream();
if (stream) {
switch (stream->codecpar->codec_type) {
case AVMEDIA_TYPE_VIDEO:
videoFrame = frame;
break;
case AVMEDIA_TYPE_AUDIO:
audioFrame = frame;
break;
default:
qWarning() << "Unsupported codec type:" << stream->codecpar->codec_type;
return AVERROR(ENOTSUP);
}
}
ret = graph->apply(videoFrame);
if (ret < 0) {
qWarning() << "Could not create video filters:" << ret;
return ret;
}
ret = graph->apply(audioFrame);
if (ret < 0) {
qWarning() << "Could not create audio filters:" << ret;
return ret;
}
ret = graph->config();
if (ret < 0) {
qWarning() << "Could not configure filter graph:" << ret;
return ret;
}
auto videoInput = graph->videoInputFilters();
auto videoOutput = graph->videoOutputFilters();
m_videoFilters.emplace_back(
std::unique_ptr<QAVFilter>(
new QAVVideoFilter(
videoStream,
QString::number(i),
videoInput,
videoOutput,
graph->mutex())
)
);
auto audioInput = graph->audioInputFilters();
auto audioOutput = graph->audioOutputFilters();
m_audioFilters.emplace_back(
std::unique_ptr<QAVFilter>(
new QAVAudioFilter(
audioStream,
QString::number(i),
audioInput,
audioOutput,
graph->mutex())
)
);
qDebug() << __FUNCTION__ << ":" << filterDesc
<< "video[ input:" << videoInput.size() << "-> output:" << videoOutput.size() << "]"
<< "audio[ input:" << audioInput.size() << "-> output:" << audioOutput.size() << "]";
}
m_filterGraphs.push_back(std::move(graph));
}
m_filterDescs = filterDescs;
return 0;
}
static int writeFrame(
const QAVFrame &decodedFrame,
const std::vector<std::unique_ptr<QAVFilter>> &filters)
{
int ret = 0;
for (size_t i = 0; i < filters.size() && ret >= 0; ++i)
ret = filters[i]->write(decodedFrame);
return ret;
}
int QAVFilters::write(
AVMediaType mediaType,
const QAVFrame &decodedFrame)
{
QMutexLocker locker(&m_mutex);
switch (mediaType) {
case AVMEDIA_TYPE_VIDEO:
return writeFrame(decodedFrame, m_videoFilters);
case AVMEDIA_TYPE_AUDIO:
return writeFrame(decodedFrame, m_audioFilters);
default:
qWarning() << "Unsupported codec type:" << mediaType;
break;
}
return AVERROR(ENOTSUP);
}
static int readFrames(
const QAVFrame &decodedFrame,
const std::vector<std::unique_ptr<QAVFilter>> &filters,
QList<QAVFrame> &filteredFrames)
{
QAVFrame frame;
if (filters.empty()) {
if (decodedFrame)
filteredFrames.append(decodedFrame);
return 0;
}
// Read all frames from all filters at once
for (size_t i = 0; i < filters.size(); ++i) {
do {
int ret = filters[i]->read(frame);
if (ret >= 0 && (!frame.filterName().isEmpty() || i == 0))
filteredFrames.append(frame);
} while (!filters[i]->isEmpty());
}
return 0;
}
int QAVFilters::read(
AVMediaType mediaType,
const QAVFrame &decodedFrame,
QList<QAVFrame> &filteredFrames)
{
QMutexLocker locker(&m_mutex);
switch (mediaType) {
case AVMEDIA_TYPE_VIDEO:
return readFrames(decodedFrame, m_videoFilters, filteredFrames);
case AVMEDIA_TYPE_AUDIO:
return readFrames(decodedFrame, m_audioFilters, filteredFrames);
default:
qWarning() << "Unsupported codec type:" << mediaType;
break;
}
return AVERROR(ENOTSUP);
}
QList<QString> QAVFilters::filterDescs() const
{
QMutexLocker locker(&m_mutex);
return m_filterDescs;
}
static bool filtersEmpty(const std::vector<std::unique_ptr<QAVFilter>> &filters)
{
for (const auto &filter : filters)
if (!filter->isEmpty())
return false;
return true;
}
bool QAVFilters::isEmpty() const
{
QMutexLocker locker(&m_mutex);
return filtersEmpty(m_videoFilters) && filtersEmpty(m_audioFilters);
}
static void flushFilters(const std::vector<std::unique_ptr<QAVFilter>> &filters)
{
for (const auto &filter: filters)
filter->flush();
}
void QAVFilters::flush()
{
QMutexLocker locker(&m_mutex);
flushFilters(m_videoFilters);
flushFilters(m_audioFilters);
}
void QAVFilters::clear()
{
QMutexLocker locker(&m_mutex);
m_videoFilters.clear();
m_audioFilters.clear();
m_filterGraphs.clear();
}
QT_END_NAMESPACE

View File

@ -0,0 +1,65 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFILTERS_P_H
#define QAVFILTERS_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtAVPlayer/qtavplayerglobal.h>
#include "qavframe.h"
#include "qavfilter_p.h"
#include "qavdemuxer_p.h"
#include "qavfiltergraph_p.h"
#include <QMutex>
#include <vector>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVFilters
{
public:
QAVFilters() = default;
int createFilters(
const QList<QString> &filterDescs,
const QAVFrame &frame,
const QAVDemuxer &demuxer);
int write(
AVMediaType mediaType,
const QAVFrame &decodedFrame);
int read(
AVMediaType mediaType,
const QAVFrame &decodedFrame,
QList<QAVFrame> &filteredFrames);
QList<QString> filterDescs() const;
bool isEmpty() const;
void flush();
void clear();
private:
Q_DISABLE_COPY(QAVFilters)
QList<QString> m_filterDescs;
std::vector<std::unique_ptr<QAVFilterGraph>> m_filterGraphs;
std::vector<std::unique_ptr<QAVFilter>> m_videoFilters;
std::vector<std::unique_ptr<QAVFilter>> m_audioFilters;
mutable QMutex m_mutex;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,121 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavframe.h"
#include "qavstream.h"
#include "qavframe_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
QAVFrame::QAVFrame()
: QAVFrame(*new QAVFramePrivate)
{
}
QAVFrame::QAVFrame(const QAVFrame &other)
: QAVFrame()
{
*this = other;
}
QAVFrame::QAVFrame(QAVFramePrivate &d)
: QAVStreamFrame(d)
{
d.frame = av_frame_alloc();
}
QAVFrame &QAVFrame::operator=(const QAVFrame &other)
{
Q_D(QAVFrame);
QAVStreamFrame::operator=(other);
auto other_priv = static_cast<QAVFramePrivate *>(other.d_ptr.get());
int64_t pts = d->frame->pts;
av_frame_unref(d->frame);
av_frame_ref(d->frame, other_priv->frame);
if (d->frame->pts < 0)
d->frame->pts = pts;
d->frameRate = other_priv->frameRate;
d->timeBase = other_priv->timeBase;
d->filterName = other_priv->filterName;
return *this;
}
QAVFrame::operator bool() const
{
Q_D(const QAVFrame);
return QAVStreamFrame::operator bool() && d->frame && (d->frame->data[0] || d->frame->data[1] || d->frame->data[2] || d->frame->data[3]);
}
QAVFrame::~QAVFrame()
{
Q_D(QAVFrame);
av_frame_free(&d->frame);
}
AVFrame *QAVFrame::frame() const
{
Q_D(const QAVFrame);
return d->frame;
}
void QAVFrame::setFrameRate(const AVRational &value)
{
Q_D(QAVFrame);
d->frameRate = value;
}
void QAVFrame::setTimeBase(const AVRational &value)
{
Q_D(QAVFrame);
d->timeBase = value;
}
QString QAVFrame::filterName() const
{
return d_func()->filterName;
}
void QAVFrame::setFilterName(const QString &name)
{
Q_D(QAVFrame);
d->filterName = name;
}
double QAVFramePrivate::pts() const
{
if (!frame || !stream)
return NAN;
AVRational tb = timeBase.num && timeBase.den ? timeBase : stream.stream()->time_base;
return frame->pts == AV_NOPTS_VALUE ? NAN : frame->pts * av_q2d(tb);
}
double QAVFramePrivate::duration() const
{
if (!frame || !stream)
return 0.0;
return frameRate.den && frameRate.num
? av_q2d(AVRational{frameRate.den, frameRate.num})
:
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 30, 0)
frame->pkt_duration
#else
frame->duration
#endif
* av_q2d(stream.stream()->time_base);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFRAME_H
#define QAVFRAME_H
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QtAVPlayer/qavstreamframe.h>
QT_BEGIN_NAMESPACE
struct AVFrame;
struct AVRational;
class QAVFramePrivate;
class QAVFrame : public QAVStreamFrame
{
public:
QAVFrame();
~QAVFrame();
QAVFrame(const QAVFrame &other);
QAVFrame &operator=(const QAVFrame &other);
operator bool() const;
AVFrame *frame() const;
void setFrameRate(const AVRational &value);
void setTimeBase(const AVRational &value);
QString filterName() const;
void setFilterName(const QString &name);
protected:
QAVFrame(QAVFramePrivate &d);
Q_DECLARE_PRIVATE(QAVFrame)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,48 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFRAME_P_H
#define QAVFRAME_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavstreamframe_p.h"
extern "C" {
#include <libavutil/frame.h>
}
QT_BEGIN_NAMESPACE
struct AVFrame;
class QAVFramePrivate : public QAVStreamFramePrivate
{
public:
double pts() const override;
double duration() const override;
AVFrame *frame = nullptr;
// Overridden data from filters if any
AVRational frameRate{};
AVRational timeBase{};
// Name of a filter the frame has retrieved from
QString filterName;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,46 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavframecodec_p.h"
#include "qavcodec_p_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
QT_BEGIN_NAMESPACE
QAVFrameCodec::QAVFrameCodec()
{
}
QAVFrameCodec::QAVFrameCodec(QAVCodecPrivate &d)
: QAVCodec(d)
{
}
int QAVFrameCodec::write(const QAVPacket &pkt)
{
Q_D(QAVCodec);
if (!d->avctx)
return AVERROR(EINVAL);
return avcodec_send_packet(d->avctx, pkt ? pkt.packet() : nullptr);
}
int QAVFrameCodec::read(QAVStreamFrame &frame)
{
Q_D(QAVCodec);
if (!d->avctx)
return AVERROR(EINVAL);
auto f = static_cast<QAVFrame *>(&frame);
return avcodec_receive_frame(d->avctx, f->frame());
}
QT_END_NAMESPACE

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFRAMECODEC_P_H
#define QAVFRAMECODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavframe.h"
#include "qavcodec_p.h"
#include "qavpacket_p.h"
QT_BEGIN_NAMESPACE
class QAVFrameCodec : public QAVCodec
{
public:
int write(const QAVPacket &pkt) override;
int read(QAVStreamFrame &frame) override;
protected:
QAVFrameCodec();
QAVFrameCodec(QAVCodecPrivate &d);
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,225 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_d3d11_p.h"
#include "qavvideobuffer_gpu_p.h"
#include <d3d11.h>
#ifdef QT_AVPLAYER_MULTIMEDIA
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
#include <private/qrhi_p.h>
#include <private/qrhid3d11_p.h>
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 2)
#include <private/qcomptr_p.h>
#else
#include <private/qwindowsiupointer_p.h>
template <class T>
using ComPtr = QWindowsIUPointer<T>;
#endif
#include <system_error>
#endif
#endif // QT_AVPLAYER_MULTIMEDIA
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavcodec/d3d11va.h>
#include <libavutil/hwcontext_d3d11va.h>
}
QT_BEGIN_NAMESPACE
void QAVHWDevice_D3D11::init(AVCodecContext *avctx)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
int ret = avcodec_get_hw_frames_parameters(avctx,
avctx->hw_device_ctx,
AV_PIX_FMT_D3D11,
&avctx->hw_frames_ctx);
if (ret < 0) {
qWarning() << "Failed to allocate HW frames context:" << ret;
return;
}
auto frames_ctx = (AVHWFramesContext *)avctx->hw_frames_ctx->data;
auto hwctx = (AVD3D11VAFramesContext *)frames_ctx->hwctx;
hwctx->MiscFlags = D3D11_RESOURCE_MISC_SHARED;
hwctx->BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE;
ret = av_hwframe_ctx_init(avctx->hw_frames_ctx);
if (ret < 0) {
qWarning() << "Failed to initialize HW frames context:" << ret;
av_buffer_unref(&avctx->hw_frames_ctx);
}
#else
Q_UNUSED(avctx);
#endif
}
AVPixelFormat QAVHWDevice_D3D11::format() const
{
return AV_PIX_FMT_D3D11;
}
AVHWDeviceType QAVHWDevice_D3D11::type() const
{
return AV_HWDEVICE_TYPE_D3D11VA;
}
#ifdef QT_AVPLAYER_MULTIMEDIA
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
template <class T>
static T **address(ComPtr<T> &ptr)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 2)
return ptr.GetAddressOf();
#else
return ptr.address();
#endif
}
template <class T>
static T *get(const ComPtr<T> &ptr)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 2)
return ptr.Get();
#else
return ptr.get();
#endif
}
static ComPtr<ID3D11Texture2D> shareTexture(ID3D11Device *dev, ID3D11Texture2D *tex)
{
ComPtr<IDXGIResource> dxgiResource;
HRESULT hr = tex->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<void **>(address(dxgiResource)));
if (FAILED(hr)) {
qWarning() << "Failed to obtain resource handle from FFmpeg texture:" << hr << std::system_category().message(hr);
return {};
}
HANDLE shared = nullptr;
hr = dxgiResource->GetSharedHandle(&shared);
if (FAILED(hr)) {
qWarning() << "Failed to obtain shared handle for FFmpeg texture:" << hr << std::system_category().message(hr);
return {};
}
ComPtr<ID3D11Texture2D> sharedTex;
hr = dev->OpenSharedResource(shared, __uuidof(ID3D11Texture2D), reinterpret_cast<void **>(address(sharedTex)));
if (FAILED(hr))
qWarning() << "Failed to share FFmpeg texture:" << hr << std::system_category().message(hr);
return sharedTex;
}
static ComPtr<ID3D11Texture2D> copyTexture(ID3D11Device *dev, ID3D11Texture2D *from, int index)
{
D3D11_TEXTURE2D_DESC fromDesc = {};
from->GetDesc(&fromDesc);
D3D11_TEXTURE2D_DESC toDesc = {};
toDesc.Width = fromDesc.Width;
toDesc.Height = fromDesc.Height;
toDesc.Format = fromDesc.Format;
toDesc.ArraySize = 1;
toDesc.MipLevels = 1;
toDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
toDesc.MiscFlags = 0;
toDesc.SampleDesc = { 1, 0 };
ComPtr<ID3D11Texture2D> copy;
HRESULT hr = dev->CreateTexture2D(&toDesc, nullptr, address(copy));
if (FAILED(hr)) {
qWarning() << "Failed to create texture:" << hr << std::system_category().message(hr);
return {};
}
ComPtr<ID3D11DeviceContext> ctx;
dev->GetImmediateContext(address(ctx));
ctx->CopySubresourceRegion(get(copy), 0, 0, 0, 0, from, index, nullptr);
return copy;
}
class VideoBuffer_D3D11: public QAVVideoBuffer_GPU
{
public:
VideoBuffer_D3D11(const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
{
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::D3D11Texture2DHandle;
}
QVariant handle(QRhi *rhi) const override
{
if (!rhi || rhi->backend() != QRhi::D3D11)
return {};
if (!m_texture) {
if (frame().format() != AV_PIX_FMT_NV12) {
qWarning() << "Only NV12 is supported";
return {};
}
auto av_frame = frame().frame();
auto texture = (ID3D11Texture2D *)(uintptr_t)av_frame->data[0];
auto texture_index = (intptr_t)av_frame->data[1];
if (!texture) {
qWarning() << "No texture in the frame" << frame().pts();
return {};
}
auto nh = static_cast<const QRhiD3D11NativeHandles *>(rhi->nativeHandles());
if (!nh) {
qWarning() << "No QRhiD3D11NativeHandles";
return {};
}
auto dev = reinterpret_cast<ID3D11Device *>(nh->dev);
if (!dev) {
qWarning() << "No ID3D11Device device";
return {};
}
auto shared = shareTexture(dev, texture);
if (shared)
const_cast<VideoBuffer_D3D11*>(this)->m_texture = copyTexture(dev, get(shared), texture_index);
}
QList<quint64> textures = {quint64(get(m_texture)), quint64(get(m_texture))};
return QVariant::fromValue(textures);
}
ComPtr<ID3D11Texture2D> m_texture;
};
QAVVideoBuffer *QAVHWDevice_D3D11::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_D3D11(frame);
}
#else // QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
QAVVideoBuffer *QAVHWDevice_D3D11::videoBuffer(const QAVVideoFrame &frame) const
{
return new QAVVideoBuffer_GPU(frame);
}
#endif
#else // QT_AVPLAYER_MULTIMEDIA
QAVVideoBuffer *QAVHWDevice_D3D11::videoBuffer(const QAVVideoFrame &frame) const
{
return new QAVVideoBuffer_GPU(frame);
}
#endif // QT_AVPLAYER_MULTIMEDIA
QT_END_NAMESPACE

View File

@ -0,0 +1,44 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_D3D11_P_H
#define QAVHWDEVICE_D3D11_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
QT_BEGIN_NAMESPACE
struct AVCodecContext;
class QAVHWDevice_D3D11 : public QAVHWDevice
{
public:
QAVHWDevice_D3D11() = default;
~QAVHWDevice_D3D11() = default;
void init(AVCodecContext *avctx) override;
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
Q_DISABLE_COPY(QAVHWDevice_D3D11)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,115 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_mediacodec_p.h"
#include "qavvideobuffer_gpu_p.h"
#include "qavcodec_p.h"
#include "qavandroidsurfacetexture_p.h"
#include <GLES/gl.h>
extern "C" {
#include <libavutil/pixdesc.h>
#include <libavcodec/mediacodec.h>
#include <libavutil/hwcontext_mediacodec.h>
}
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QAVAndroidSurfaceTexture, androidSurfaceTexture);
class QAVHWDevice_MediaCodecPrivate
{
public:
GLuint texture = 0;
};
QAVHWDevice_MediaCodec::QAVHWDevice_MediaCodec()
: d_ptr(new QAVHWDevice_MediaCodecPrivate)
{
}
QAVHWDevice_MediaCodec::~QAVHWDevice_MediaCodec()
{
Q_D(QAVHWDevice_MediaCodec);
if (d->texture)
glDeleteTextures(1, &d->texture);
}
void QAVHWDevice_MediaCodec::init(AVCodecContext *avctx)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
AVBufferRef *hw_device_ctx = avctx->hw_device_ctx;
if (hw_device_ctx) {
AVHWDeviceContext *deviceContext = reinterpret_cast<AVHWDeviceContext *>(hw_device_ctx->data);
if (deviceContext->hwctx) {
AVMediaCodecDeviceContext *mediaDeviceContext =
reinterpret_cast<AVMediaCodecDeviceContext *>(deviceContext->hwctx);
if (mediaDeviceContext)
mediaDeviceContext->surface = androidSurfaceTexture->surface();
}
}
#else
Q_UNUSED(avctx);
#endif
}
AVPixelFormat QAVHWDevice_MediaCodec::format() const
{
return AV_PIX_FMT_MEDIACODEC;
}
AVHWDeviceType QAVHWDevice_MediaCodec::type() const
{
return AV_HWDEVICE_TYPE_MEDIACODEC;
}
class VideoBuffer_MediaCodec : public QAVVideoBuffer_GPU
{
public:
VideoBuffer_MediaCodec(QAVHWDevice_MediaCodecPrivate *hw, const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
, m_hw(hw)
{
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::GLTextureHandle;
}
QVariant handle(QRhi */*rhi*/) const override
{
if (!androidSurfaceTexture->isValid())
return {};
if (!m_hw->texture) {
androidSurfaceTexture->detachFromGLContext();
glGenTextures(1, &m_hw->texture);
androidSurfaceTexture->attachToGLContext(m_hw->texture);
}
AVMediaCodecBuffer *buffer = reinterpret_cast<AVMediaCodecBuffer *>(frame().frame()->data[3]);
if (!buffer) {
qWarning() << "Received a frame without AVMediaCodecBuffer.";
} else if (av_mediacodec_release_buffer(buffer, 1) < 0) {
qWarning() << "Failed to render buffer to surface.";
return {};
}
androidSurfaceTexture->updateTexImage();
return m_hw->texture;
}
QAVHWDevice_MediaCodecPrivate *m_hw = nullptr;
};
QAVVideoBuffer *QAVHWDevice_MediaCodec::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_MediaCodec(d_ptr.get(), frame);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,47 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_MEDIACODEC_P_H
#define QAVHWDEVICE_MEDIACODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
#include <memory>
QT_BEGIN_NAMESPACE
class QAVHWDevice_MediaCodecPrivate;
class QAVHWDevice_MediaCodec : public QAVHWDevice
{
public:
QAVHWDevice_MediaCodec();
~QAVHWDevice_MediaCodec();
void init(AVCodecContext *avctx) override;
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
std::unique_ptr<QAVHWDevice_MediaCodecPrivate> d_ptr;
Q_DISABLE_COPY(QAVHWDevice_MediaCodec)
Q_DECLARE_PRIVATE(QAVHWDevice_MediaCodec)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,51 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_P_H
#define QAVHWDEVICE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavvideoframe.h"
#include "qtavplayerglobal.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
}
QT_BEGIN_NAMESPACE
struct AVCodecContext;
class QAVVideoBuffer;
class QAVHWDevice
{
public:
QAVHWDevice() = default;
virtual ~QAVHWDevice() = default;
virtual void init(AVCodecContext *) { }
virtual AVPixelFormat format() const = 0;
virtual AVHWDeviceType type() const = 0;
virtual QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const = 0;
private:
Q_DISABLE_COPY(QAVHWDevice)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,163 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_vaapi_drm_egl_p.h"
#include "qavvideocodec_p.h"
#include "qavvideobuffer_gpu_p.h"
#include "qavstream.h"
#include <QDebug>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <va/va_drmcommon.h>
#include <drm/drm_fourcc.h>
extern "C" {
#include <libavutil/hwcontext_vaapi.h>
#include <libavcodec/avcodec.h>
}
static PFNEGLCREATEIMAGEKHRPROC s_eglCreateImageKHR = nullptr;
static PFNEGLDESTROYIMAGEKHRPROC s_eglDestroyImageKHR = nullptr;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC s_glEGLImageTargetTexture2DOES = nullptr;
QT_BEGIN_NAMESPACE
class QAVHWDevice_VAAPI_DRM_EGLPrivate
{
public:
GLuint textures[2] = {0};
};
QAVHWDevice_VAAPI_DRM_EGL::QAVHWDevice_VAAPI_DRM_EGL()
: d_ptr(new QAVHWDevice_VAAPI_DRM_EGLPrivate)
{
}
QAVHWDevice_VAAPI_DRM_EGL::~QAVHWDevice_VAAPI_DRM_EGL()
{
Q_D(QAVHWDevice_VAAPI_DRM_EGL);
if (d->textures[0])
glDeleteTextures(2, &d->textures[0]);
}
AVPixelFormat QAVHWDevice_VAAPI_DRM_EGL::format() const
{
return AV_PIX_FMT_VAAPI;
}
AVHWDeviceType QAVHWDevice_VAAPI_DRM_EGL::type() const
{
return AV_HWDEVICE_TYPE_VAAPI;
}
class VideoBuffer_EGL : public QAVVideoBuffer_GPU
{
public:
VideoBuffer_EGL(QAVHWDevice_VAAPI_DRM_EGLPrivate *hw, const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
, m_hw(hw)
{
if (!s_eglCreateImageKHR) {
s_eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
s_eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
s_glEGLImageTargetTexture2DOES = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
}
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::GLTextureHandle;
}
QVariant textures() const
{
return QList<QVariant>() << m_hw->textures[0] << m_hw->textures[1];
}
QVariant handle(QRhi */*rhi*/) const override
{
if (!m_hw->textures[0])
glGenTextures(2, m_hw->textures);
auto va_frame = frame().frame();
AVHWDeviceContext *hwctx = (AVHWDeviceContext *)frame().stream().codec()->avctx()->hw_device_ctx->data;
AVVAAPIDeviceContext *vactx = (AVVAAPIDeviceContext *)hwctx->hwctx;
VADisplay va_display = vactx->display;
VASurfaceID va_surface = (VASurfaceID)(uintptr_t)va_frame->data[3];
VADRMPRIMESurfaceDescriptor prime;
auto status = vaExportSurfaceHandle(va_display, va_surface,
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
&prime);
if (status != VA_STATUS_SUCCESS) {
qWarning() << "vaExportSurfaceHandle failed" << status;
return textures();
}
if (prime.fourcc != VA_FOURCC_NV12) {
qWarning() << "prime.fourcc != VA_FOURCC_NV12";
return textures();
}
vaSyncSurface(va_display, va_surface);
static const uint32_t formats[2] = { DRM_FORMAT_R8, DRM_FORMAT_GR88 };
for (int i = 0; i < 2; ++i) {
if (prime.layers[i].drm_format != formats[i])
qWarning() << "Wrong DRM format:" << prime.layers[i].drm_format << formats[i];
EGLint img_attr[] = {
EGL_LINUX_DRM_FOURCC_EXT, EGLint(formats[i]),
EGL_WIDTH, va_frame->width / (i + 1),
EGL_HEIGHT, va_frame->height / (i + 1),
EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[i].object_index[0]].fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGLint(prime.layers[i].offset[0]),
EGL_DMA_BUF_PLANE0_PITCH_EXT, EGLint(prime.layers[i].pitch[0]),
EGL_NONE
};
EGLImage img = s_eglCreateImageKHR(eglGetCurrentDisplay(),
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
NULL, img_attr);
if (!img) {
qWarning() << "eglCreateImageKHR failed";
return textures();
}
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, m_hw->textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img);
if (glGetError())
qWarning() << "glEGLImageTargetTexture2DOES failed";
glBindTexture(GL_TEXTURE_2D, 0);
s_eglDestroyImageKHR(eglGetCurrentDisplay(), img);
}
return textures();
}
QAVHWDevice_VAAPI_DRM_EGLPrivate *m_hw = nullptr;
};
QAVVideoBuffer *QAVHWDevice_VAAPI_DRM_EGL::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_EGL(d_ptr.get(), frame);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,46 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_VAAPI_DRM_EGL_P_H
#define QAVHWDEVICE_VAAPI_DRM_EGL_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
#include <memory>
QT_BEGIN_NAMESPACE
class QAVHWDevice_VAAPI_DRM_EGLPrivate;
class QAVHWDevice_VAAPI_DRM_EGL : public QAVHWDevice
{
public:
QAVHWDevice_VAAPI_DRM_EGL();
~QAVHWDevice_VAAPI_DRM_EGL();
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
std::unique_ptr<QAVHWDevice_VAAPI_DRM_EGLPrivate> d_ptr;
Q_DISABLE_COPY(QAVHWDevice_VAAPI_DRM_EGL)
Q_DECLARE_PRIVATE(QAVHWDevice_VAAPI_DRM_EGL)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,180 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_vaapi_x11_glx_p.h"
#include "qavvideocodec_p.h"
#include "qavstream.h"
#include "qavvideobuffer_gpu_p.h"
#include <QDebug>
#include <GL/glx.h>
#include <va/va_x11.h>
extern "C" {
#include <libavutil/hwcontext_vaapi.h>
#include <libavcodec/avcodec.h>
}
typedef void (*glXBindTexImageEXT_)(Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list);
typedef void (*glXReleaseTexImageEXT_)(Display *dpy, GLXDrawable draw, int buffer);
static glXBindTexImageEXT_ s_glXBindTexImageEXT = nullptr;
static glXReleaseTexImageEXT_ s_glXReleaseTexImageEXT = nullptr;
QT_BEGIN_NAMESPACE
class QAVHWDevice_VAAPI_X11_GLXPrivate
{
public:
Pixmap pixmap = 0;
GLXPixmap glxpixmap = 0;
Display *display = nullptr;
GLuint texture = 0;
};
QAVHWDevice_VAAPI_X11_GLX::QAVHWDevice_VAAPI_X11_GLX()
: d_ptr(new QAVHWDevice_VAAPI_X11_GLXPrivate)
{
}
QAVHWDevice_VAAPI_X11_GLX::~QAVHWDevice_VAAPI_X11_GLX()
{
Q_D(QAVHWDevice_VAAPI_X11_GLX);
if (d->glxpixmap) {
s_glXReleaseTexImageEXT(d->display, d->glxpixmap, GLX_FRONT_EXT);
glXDestroyPixmap(d->display, d->glxpixmap);
}
if (d->pixmap)
XFreePixmap(d->display, d->pixmap);
if (d->texture)
glDeleteTextures(1, &d->texture);
}
AVPixelFormat QAVHWDevice_VAAPI_X11_GLX::format() const
{
return AV_PIX_FMT_VAAPI;
}
AVHWDeviceType QAVHWDevice_VAAPI_X11_GLX::type() const
{
return AV_HWDEVICE_TYPE_VAAPI;
}
class VideoBuffer_GLX : public QAVVideoBuffer_GPU
{
public:
VideoBuffer_GLX(QAVHWDevice_VAAPI_X11_GLXPrivate *hw, const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
, m_hw(hw)
{
if (!s_glXBindTexImageEXT) {
s_glXBindTexImageEXT = (glXBindTexImageEXT_) glXGetProcAddressARB((const GLubyte *)"glXBindTexImageEXT");
s_glXReleaseTexImageEXT = (glXReleaseTexImageEXT_) glXGetProcAddressARB((const GLubyte *)"glXReleaseTexImageEXT");
}
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::GLTextureHandle;
}
QVariant handle(QRhi */*rhi*/) const override
{
if (!s_glXBindTexImageEXT) {
qWarning() << "Could not get proc address: s_glXBindTexImageEXT";
return 0;
}
auto av_frame = frame().frame();
AVHWDeviceContext *hwctx = (AVHWDeviceContext *)frame().stream().codec()->avctx()->hw_device_ctx->data;
AVVAAPIDeviceContext *vactx = (AVVAAPIDeviceContext *)hwctx->hwctx;
VADisplay va_display = vactx->display;
VASurfaceID va_surface = (VASurfaceID)(uintptr_t)av_frame->data[3];
int w = av_frame->width;
int h = av_frame->height;
if (!m_hw->display) {
glGenTextures(1, &m_hw->texture);
auto display = (Display *)glXGetCurrentDisplay();
m_hw->display = display;
int xscr = DefaultScreen(display);
const char *glxext = glXQueryExtensionsString(display, xscr);
if (!glxext || !strstr(glxext, "GLX_EXT_texture_from_pixmap")) {
qWarning() << "GLX_EXT_texture_from_pixmap is not supported";
return 0;
}
int attribs[] = {
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_RENDERABLE, True,
GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
GLX_Y_INVERTED_EXT, True,
GLX_DOUBLEBUFFER, False,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
None
};
int fbcount;
GLXFBConfig *fbcs = glXChooseFBConfig(display, xscr, attribs, &fbcount);
if (!fbcount) {
XFree(fbcs);
qWarning() << "No texture-from-pixmap support";
return 0;
}
GLXFBConfig fbc = fbcs[0];
XFree(fbcs);
XWindowAttributes xwa;
XGetWindowAttributes(display, DefaultRootWindow(display), &xwa);
m_hw->pixmap = XCreatePixmap(display, DefaultRootWindow(display), w, h, xwa.depth);
const int attribs_pixmap[] = {
GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
GLX_TEXTURE_FORMAT_EXT, xwa.depth == 32 ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT,
GLX_MIPMAP_TEXTURE_EXT, False,
None,
};
m_hw->glxpixmap = glXCreatePixmap(display, fbc, m_hw->pixmap, attribs_pixmap);
}
vaSyncSurface(va_display, va_surface);
auto status = vaPutSurface(va_display, va_surface, m_hw->pixmap,
0, 0, w, h,
0, 0, w, h,
NULL, 0, VA_FRAME_PICTURE | VA_SRC_BT709);
if (status != VA_STATUS_SUCCESS) {
qWarning() << "vaPutSurface failed" << status;
return 0;
}
XSync(m_hw->display, False);
glBindTexture(GL_TEXTURE_2D, m_hw->texture);
s_glXBindTexImageEXT(m_hw->display, m_hw->glxpixmap, GLX_FRONT_EXT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
return m_hw->texture;
}
QAVHWDevice_VAAPI_X11_GLXPrivate *m_hw = nullptr;
};
QAVVideoBuffer *QAVHWDevice_VAAPI_X11_GLX::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_GLX(d_ptr.get(), frame);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,46 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_VAAPI_X11_GLX_P_H
#define QAVHWDEVICE_VAAPI_X11_GLX_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
#include <memory>
QT_BEGIN_NAMESPACE
class QAVHWDevice_VAAPI_X11_GLXPrivate;
class QAVHWDevice_VAAPI_X11_GLX : public QAVHWDevice
{
public:
QAVHWDevice_VAAPI_X11_GLX();
~QAVHWDevice_VAAPI_X11_GLX();
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
std::unique_ptr<QAVHWDevice_VAAPI_X11_GLXPrivate> d_ptr;
Q_DISABLE_COPY(QAVHWDevice_VAAPI_X11_GLX)
Q_DECLARE_PRIVATE(QAVHWDevice_VAAPI_X11_GLX)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,235 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_vdpau_p.h"
#include "qavvideocodec_p.h"
#include "qavvideobuffer_gpu_p.h"
#include <QDebug>
#include <GL/glx.h>
extern "C" {
#include <libavutil/hwcontext_vdpau.h>
#include <libavcodec/avcodec.h>
}
typedef void (*VDPAUInitNV_)(const GLvoid *, const GLvoid *);
static VDPAUInitNV_ s_VDPAUInitNV = nullptr;
typedef void (*VDPAUFiniNV_)(void);
static VDPAUFiniNV_ s_VDPAUFiniNV = nullptr;
typedef GLvdpauSurfaceNV (*VDPAURegisterOutputSurfaceNV_)(GLvoid *, GLenum, GLsizei, const GLuint *);
static VDPAURegisterOutputSurfaceNV_ s_VDPAURegisterOutputSurfaceNV = nullptr;
typedef void (*VDPAUSurfaceAccessNV_)(GLvdpauSurfaceNV, GLenum);
static VDPAUSurfaceAccessNV_ s_VDPAUSurfaceAccessNV = nullptr;
typedef void (*VDPAUMapSurfacesNV_)(GLsizei, const GLvdpauSurfaceNV *);
static VDPAUMapSurfacesNV_ s_VDPAUMapSurfacesNV = nullptr;
typedef void (*VDPAUUnmapSurfacesNV_)(GLsizei, const GLvdpauSurfaceNV *);
static VDPAUUnmapSurfacesNV_ s_VDPAUUnmapSurfacesNV = nullptr;
typedef void (*VDPAUUnregisterSurfaceNV_)(GLvdpauSurfaceNV);
static VDPAUUnregisterSurfaceNV_ s_VDPAUUnregisterSurfaceNV = nullptr;
static VdpOutputSurfaceCreate *s_output_surface_create = nullptr;
static VdpOutputSurfaceDestroy *s_output_surface_destroy = nullptr;
static VdpVideoMixerCreate *s_video_mixer_create = nullptr;
static VdpVideoMixerDestroy *s_video_mixer_destroy = nullptr;
static VdpVideoSurfaceGetParameters *s_video_surface_get_parameters = nullptr;
static VdpVideoMixerRender *s_video_mixer_render = nullptr;
#define VDP_NUM_MIXER_PARAMETER 3
#define MAX_NUM_FEATURES 6
#define MP_VDP_HISTORY_FRAMES 2
QT_BEGIN_NAMESPACE
QAVHWDevice_VDPAU::QAVHWDevice_VDPAU()
{
}
QAVHWDevice_VDPAU::~QAVHWDevice_VDPAU()
{
}
AVPixelFormat QAVHWDevice_VDPAU::format() const
{
return AV_PIX_FMT_VDPAU;
}
AVHWDeviceType QAVHWDevice_VDPAU::type() const
{
return AV_HWDEVICE_TYPE_VDPAU;
}
template <class T>
static int get_proc_address(AVVDPAUDeviceContext *vactx, VdpFuncId funcid, T &out)
{
void *tmp = nullptr;
auto err = vactx->get_proc_address(vactx->device, funcid, &tmp);
if (err != VDP_STATUS_OK)
qWarning() << "Could not get proc address:" << funcid;
out = reinterpret_cast<T>(tmp);
return err;
}
class VideoBuffer_VDPAU_GLX : public QAVVideoBuffer_GPU
{
public:
VideoBuffer_VDPAU_GLX(const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
{
if (!s_VDPAUInitNV) {
s_VDPAUInitNV = (VDPAUInitNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUInitNV");
s_VDPAUFiniNV = (VDPAUFiniNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUFiniNV");
s_VDPAURegisterOutputSurfaceNV = (VDPAURegisterOutputSurfaceNV_) glXGetProcAddressARB((const GLubyte *)"VDPAURegisterOutputSurfaceNV");
s_VDPAUSurfaceAccessNV = (VDPAUSurfaceAccessNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUSurfaceAccessNV");
s_VDPAUMapSurfacesNV = (VDPAUMapSurfacesNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUMapSurfacesNV");
s_VDPAUUnmapSurfacesNV = (VDPAUUnmapSurfacesNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUUnmapSurfacesNV");
s_VDPAUUnregisterSurfaceNV = (VDPAUUnregisterSurfaceNV_) glXGetProcAddressARB((const GLubyte *)"VDPAUUnregisterSurfaceNV");
}
for (int n = 0; n < MP_VDP_HISTORY_FRAMES; ++n)
past[n] = future[n] = VDP_INVALID_HANDLE;
}
~VideoBuffer_VDPAU_GLX()
{
if (vdpgl_surface) {
s_VDPAUUnmapSurfacesNV(1, &vdpgl_surface);
s_VDPAUUnregisterSurfaceNV(vdpgl_surface);
}
if (vdp_surface != VDP_INVALID_HANDLE)
s_output_surface_destroy(vdp_surface);
if (video_mixer != VDP_INVALID_HANDLE)
s_video_mixer_destroy(video_mixer);
if (gl_texture) {
glDeleteTextures(1, &gl_texture);
s_VDPAUFiniNV();
}
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::GLTextureHandle;
}
GLuint texture()
{
if (!s_VDPAUInitNV) {
qWarning() << "Could not get proc address: s_VDPAUInitNV";
return 0;
}
if (!frame())
return 0;
if (gl_texture)
return gl_texture;
auto av_frame = frame().frame();
int w = av_frame->width;
int h = av_frame->height;
AVHWDeviceContext *hwctx = (AVHWDeviceContext *)frame().stream().codec()->avctx()->hw_device_ctx->data;
AVVDPAUDeviceContext *vactx = (AVVDPAUDeviceContext *)hwctx->hwctx;
VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)av_frame->data[3];
if (!gl_texture) {
if (!s_output_surface_create) {
get_proc_address(vactx, VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, s_output_surface_create);
get_proc_address(vactx, VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, s_output_surface_destroy);
get_proc_address(vactx, VDP_FUNC_ID_VIDEO_MIXER_CREATE, s_video_mixer_create);
get_proc_address(vactx, VDP_FUNC_ID_VIDEO_MIXER_DESTROY, s_video_mixer_destroy);
get_proc_address(vactx, VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, s_video_surface_get_parameters);
get_proc_address(vactx, VDP_FUNC_ID_VIDEO_MIXER_RENDER, s_video_mixer_render);
get_proc_address(vactx, VDP_FUNC_ID_VIDEO_MIXER_RENDER, s_video_mixer_render);
}
VdpVideoMixerFeature features[MAX_NUM_FEATURES];
static const VdpVideoMixerParameter parameters[VDP_NUM_MIXER_PARAMETER] = {
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
};
VdpChromaType chroma_type;
uint32_t s_w, s_h;
s_video_surface_get_parameters(surf, &chroma_type, &s_w, &s_h);
const void *const parameter_values[VDP_NUM_MIXER_PARAMETER] = {
&s_w,
&s_h,
&chroma_type,
};
s_video_mixer_create(vactx->device, 0, features,
VDP_NUM_MIXER_PARAMETER,
parameters, parameter_values,
&video_mixer);
glGenTextures(1, &gl_texture);
s_VDPAUInitNV(reinterpret_cast<const GLvoid *>(vactx->device), (const GLvoid *)vactx->get_proc_address);
glBindTexture(GL_TEXTURE_2D, gl_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
s_output_surface_create(vactx->device,
VDP_RGBA_FORMAT_B8G8R8A8,
s_w,
s_h,
&vdp_surface);
vdpgl_surface = s_VDPAURegisterOutputSurfaceNV(reinterpret_cast<GLvoid *>(vdp_surface),
GL_TEXTURE_2D,
1, &gl_texture);
s_VDPAUSurfaceAccessNV(vdpgl_surface, GL_READ_ONLY);
}
VdpRect video_rect = {0, 0, static_cast<uint32_t>(w), static_cast<uint32_t>(h)};
s_video_mixer_render(video_mixer, VDP_INVALID_HANDLE,
0, field,
MP_VDP_HISTORY_FRAMES, past,
surf,
MP_VDP_HISTORY_FRAMES, future,
&video_rect,
vdp_surface, NULL, NULL,
0, NULL);
s_VDPAUMapSurfacesNV(1, &vdpgl_surface);
return gl_texture;
}
QVariant handle(QRhi */*rhi*/) const override
{
return const_cast<VideoBuffer_VDPAU_GLX *>(this)->texture();
}
GLuint gl_texture = 0;
VdpOutputSurface vdp_surface = VDP_INVALID_HANDLE;
GLvdpauSurfaceNV vdpgl_surface = 0;
VdpVideoMixer video_mixer = VDP_INVALID_HANDLE;
VdpVideoSurface past[MP_VDP_HISTORY_FRAMES];
VdpVideoSurface future[MP_VDP_HISTORY_FRAMES];
VdpVideoMixerPictureStructure field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
};
QAVVideoBuffer *QAVHWDevice_VDPAU::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_VDPAU_GLX(frame);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,42 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_VDPAU_P_H
#define QAVHWDEVICE_VDPAU_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
QT_BEGIN_NAMESPACE
class QAVHWDevice_VDPAU : public QAVHWDevice
{
public:
QAVHWDevice_VDPAU();
~QAVHWDevice_VDPAU();
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
Q_DISABLE_COPY(QAVHWDevice_VDPAU)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,112 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavhwdevice_videotoolbox_p.h"
#include "qavvideobuffer_gpu_p.h"
#import <CoreVideo/CoreVideo.h>
#if defined(Q_OS_MACOS)
#import <IOSurface/IOSurface.h>
#else
#import <IOSurface/IOSurfaceRef.h>
#endif
#import <Metal/Metal.h>
#include <QList>
#include <QVariant>
#include <QDebug>
QT_BEGIN_NAMESPACE
class QAVHWDevice_VideoToolboxPrivate
{
public:
id<MTLDevice> device = nullptr;
CVPixelBufferRef pbuf = nullptr;
};
QAVHWDevice_VideoToolbox::QAVHWDevice_VideoToolbox()
: d_ptr(new QAVHWDevice_VideoToolboxPrivate)
{
}
QAVHWDevice_VideoToolbox::~QAVHWDevice_VideoToolbox()
{
Q_D(QAVHWDevice_VideoToolbox);
CVPixelBufferRelease(d->pbuf);
[d->device release];
}
AVPixelFormat QAVHWDevice_VideoToolbox::format() const
{
return AV_PIX_FMT_VIDEOTOOLBOX;
}
AVHWDeviceType QAVHWDevice_VideoToolbox::type() const
{
return AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
}
class VideoBuffer_MTL : public QAVVideoBuffer_GPU
{
public:
VideoBuffer_MTL(QAVHWDevice_VideoToolboxPrivate *hw, const QAVVideoFrame &frame)
: QAVVideoBuffer_GPU(frame)
, m_hw(hw)
{
}
QAVVideoFrame::HandleType handleType() const override
{
return QAVVideoFrame::MTLTextureHandle;
}
QVariant handle(QRhi */*rhi*/) const override
{
CVPixelBufferRelease(m_hw->pbuf);
m_hw->pbuf = (CVPixelBufferRef)frame().frame()->data[3];
CVPixelBufferRetain(m_hw->pbuf);
QList<QVariant> textures = { 0, 0 };
if (!m_hw->pbuf)
return textures;
if (CVPixelBufferGetDataSize(m_hw->pbuf) <= 0)
return textures;
auto format = CVPixelBufferGetPixelFormatType(m_hw->pbuf);
if (format != '420v') {
qWarning() << "420v is supported only";
return textures;
}
if (!m_hw->device)
m_hw->device = MTLCreateSystemDefaultDevice();
IOSurfaceRef surface = CVPixelBufferGetIOSurface(m_hw->pbuf);
int planes = CVPixelBufferGetPlaneCount(m_hw->pbuf);
for (int i = 0; i < planes; ++i) {
int w = IOSurfaceGetWidthOfPlane(surface, i);
int h = IOSurfaceGetHeightOfPlane(surface, i) ;
MTLPixelFormat f = i ? MTLPixelFormatRG8Unorm : MTLPixelFormatR8Unorm;
MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:f width:w height:h mipmapped:NO];
textures[i] = quint64([m_hw->device newTextureWithDescriptor:desc iosurface:surface plane:i]);
}
return textures;
}
QAVHWDevice_VideoToolboxPrivate *m_hw = nullptr;
};
QAVVideoBuffer *QAVHWDevice_VideoToolbox::videoBuffer(const QAVVideoFrame &frame) const
{
return new VideoBuffer_MTL(d_ptr.get(), frame);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,46 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVHWDEVICE_VIDEOTOOLBOX_P_H
#define QAVHWDEVICE_VIDEOTOOLBOX_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavhwdevice_p.h"
#include <memory>
QT_BEGIN_NAMESPACE
class QAVHWDevice_VideoToolboxPrivate;
class QAVHWDevice_VideoToolbox : public QAVHWDevice
{
public:
QAVHWDevice_VideoToolbox();
~QAVHWDevice_VideoToolbox();
AVPixelFormat format() const override;
AVHWDeviceType type() const override;
QAVVideoBuffer *videoBuffer(const QAVVideoFrame &frame) const override;
private:
std::unique_ptr<QAVHWDevice_VideoToolboxPrivate> d_ptr;
Q_DISABLE_COPY(QAVHWDevice_VideoToolbox)
Q_DECLARE_PRIVATE(QAVHWDevice_VideoToolbox)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,64 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavinoutfilter_p.h"
#include "qavinoutfilter_p_p.h"
#include <QDebug>
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}
QT_BEGIN_NAMESPACE
QAVInOutFilter::QAVInOutFilter()
: QAVInOutFilter(*new QAVInOutFilterPrivate(this))
{
}
QAVInOutFilter::QAVInOutFilter(QAVInOutFilterPrivate &d)
: d_ptr(&d)
{
}
QAVInOutFilter::~QAVInOutFilter() = default;
QAVInOutFilter::QAVInOutFilter(const QAVInOutFilter &other)
: QAVInOutFilter()
{
*this = other;
}
QAVInOutFilter &QAVInOutFilter::operator=(const QAVInOutFilter &other)
{
d_ptr->ctx = other.d_ptr->ctx;
d_ptr->name = other.d_ptr->name;
return *this;
}
int QAVInOutFilter::configure(AVFilterGraph *graph, AVFilterInOut *in)
{
Q_D(QAVInOutFilter);
Q_UNUSED(graph);
if (in->name)
d->name = QString::fromUtf8(in->name);
return 0;
}
AVFilterContext *QAVInOutFilter::ctx() const
{
Q_D(const QAVInOutFilter);
return d->ctx;
}
QString QAVInOutFilter::name() const
{
return d_func()->name;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,50 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVINOUTFILTER_P_H
#define QAVINOUTFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtAVPlayer/qtavplayerglobal.h>
#include <memory>
QT_BEGIN_NAMESPACE
struct AVFilterGraph;
struct AVFilterInOut;
struct AVFilterContext;
class QAVInOutFilterPrivate;
class QAVInOutFilter
{
public:
QAVInOutFilter();
virtual ~QAVInOutFilter();
QAVInOutFilter(const QAVInOutFilter &other);
QAVInOutFilter &operator=(const QAVInOutFilter &other);
virtual int configure(AVFilterGraph *graph, AVFilterInOut *in);
AVFilterContext *ctx() const;
QString name() const;
protected:
std::unique_ptr<QAVInOutFilterPrivate> d_ptr;
QAVInOutFilter(QAVInOutFilterPrivate &d);
Q_DECLARE_PRIVATE(QAVInOutFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVINOUTFILTER_P_P_H
#define QAVINOUTFILTER_P_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QString>
QT_BEGIN_NAMESPACE
class QAVInOutFilter;
struct AVFilterContext;
class QAVInOutFilterPrivate
{
public:
QAVInOutFilterPrivate(QAVInOutFilter *q) : q_ptr(q) { }
virtual ~QAVInOutFilterPrivate() = default;
QAVInOutFilter *q_ptr = nullptr;
AVFilterContext *ctx = nullptr;
QString name;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,173 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qaviodevice.h"
#include <QMutex>
#include <QWaitCondition>
extern "C" {
#include <libavformat/avio.h>
#include <libavutil/mem.h>
#include <libavutil/error.h>
}
QT_BEGIN_NAMESPACE
struct ReadRequest
{
ReadRequest() = default;
ReadRequest(unsigned char *data, int maxSize): data(data), maxSize(maxSize) { }
int wroteBytes = 0;
unsigned char *data = nullptr;
int maxSize = 0;
};
class QAVIODevicePrivate
{
Q_DECLARE_PUBLIC(QAVIODevice)
public:
explicit QAVIODevicePrivate(QAVIODevice *q, const QSharedPointer<QIODevice> &device)
: q_ptr(q)
, device(device)
, buffer(static_cast<unsigned char*>(av_malloc(buffer_size)))
, ctx(avio_alloc_context(buffer, static_cast<int>(buffer_size), 0, this, &QAVIODevicePrivate::read, nullptr, !device->isSequential() ? &QAVIODevicePrivate::seek : nullptr))
{
if (!device->isSequential())
ctx->seekable = AVIO_SEEKABLE_NORMAL;
}
~QAVIODevicePrivate()
{
av_free(ctx);
}
void readData()
{
QMutexLocker locker(&mutex);
// if no request or it is being processed
if (readRequest.data == nullptr || readRequest.wroteBytes)
return;
readRequest.wroteBytes = !device->atEnd() ? device->read((char *)readRequest.data, readRequest.maxSize) : AVERROR_EOF;
// Unblock the decoder thread when there is available bytes
if (readRequest.wroteBytes) {
waitCond.wakeAll();
wakeRead = true;
}
}
static int read(void *opaque, unsigned char *data, int maxSize)
{
auto d = static_cast<QAVIODevicePrivate *>(opaque);
QMutexLocker locker(&d->mutex);
if (d->aborted)
return ECANCELED;
d->readRequest = { data, maxSize };
// When decoder thread is the same as current
d->wakeRead = false;
locker.unlock();
// Reading is done on thread where the object is created
QMetaObject::invokeMethod(d->q_ptr, [d] { d->readData(); }, nullptr);
locker.relock();
// Blocks until data is available
if (!d->wakeRead)
d->waitCond.wait(&d->mutex);
int bytes = d->readRequest.wroteBytes;
d->readRequest = {};
return bytes;
}
static int64_t seek(void *opaque, int64_t offset, int whence)
{
auto d = static_cast<QAVIODevicePrivate *>(opaque);
QMutexLocker locker(&d->mutex);
if (d->aborted)
return ECANCELED;
int64_t pos = 0;
bool wake = false;
locker.unlock();
QMetaObject::invokeMethod(d->q_ptr, [&] {
QMutexLocker locker(&d->mutex);
if (whence == AVSEEK_SIZE) {
pos = d->device->size() > 0 ? d->device->size() : 0;
} else {
if (whence == SEEK_END)
offset = d->device->size() - offset;
else if (whence == SEEK_CUR)
offset = d->device->pos() + offset;
pos = d->device->seek(offset) ? d->device->pos() : -1;
}
d->waitCond.wakeAll();
wake = true;
}, nullptr);
locker.relock();
if (!wake)
d->waitCond.wait(&d->mutex);
return pos;
}
size_t buffer_size = 64 * 1024;
QAVIODevice *q_ptr = nullptr;
QSharedPointer<QIODevice> device;
unsigned char *buffer = nullptr;
AVIOContext *ctx = nullptr;
mutable QMutex mutex;
QWaitCondition waitCond;
bool aborted = false;
bool wakeRead = false;
ReadRequest readRequest;
};
QAVIODevice::QAVIODevice(const QSharedPointer<QIODevice> &device, QObject *parent)
: QObject(parent)
, d_ptr(new QAVIODevicePrivate(this, device))
{
connect(device.get(), &QIODevice::readyRead, this, [this] {
Q_D(QAVIODevice);
d->readData();
});
}
QAVIODevice::~QAVIODevice()
{
abort(true);
}
AVIOContext *QAVIODevice::ctx() const
{
return d_func()->ctx;
}
void QAVIODevice::abort(bool aborted)
{
Q_D(QAVIODevice);
QMutexLocker locker(&d->mutex);
d->aborted = aborted;
d->waitCond.wakeAll();
}
void QAVIODevice::setBufferSize(size_t size)
{
Q_D(QAVIODevice);
QMutexLocker locker(&d->mutex);
d->buffer_size = size;
}
size_t QAVIODevice::bufferSize() const
{
Q_D(const QAVIODevice);
QMutexLocker locker(&d->mutex);
return d->buffer_size;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFIODEVICE_P_H
#define QAVFIODEVICE_P_H
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QIODevice>
#include <QSharedPointer>
#include <memory>
QT_BEGIN_NAMESPACE
struct AVIOContext;
class QAVIODevicePrivate;
class QAVIODevice : public QObject
{
public:
QAVIODevice(const QSharedPointer<QIODevice> &device, QObject *parent = nullptr);
~QAVIODevice();
AVIOContext *ctx() const;
void abort(bool aborted);
void setBufferSize(size_t size);
size_t bufferSize() const;
protected:
std::unique_ptr<QAVIODevicePrivate> d_ptr;
private:
Q_DECLARE_PRIVATE(QAVIODevice)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,108 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavpacket_p.h"
#include "qavcodec_p.h"
#include "qavstream.h"
#include <QSharedPointer>
#include <QDebug>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
}
QT_BEGIN_NAMESPACE
class QAVPacketPrivate
{
public:
AVPacket *pkt = nullptr;
QAVStream stream;
};
QAVPacket::QAVPacket()
: d_ptr(new QAVPacketPrivate)
{
d_ptr->pkt = av_packet_alloc();
d_ptr->pkt->size = 0;
d_ptr->pkt->stream_index = -1;
d_ptr->pkt->pts = AV_NOPTS_VALUE;
}
QAVPacket::QAVPacket(const QAVPacket &other)
: QAVPacket()
{
*this = other;
}
QAVPacket &QAVPacket::operator=(const QAVPacket &other)
{
av_packet_unref(d_ptr->pkt);
av_packet_ref(d_ptr->pkt, other.d_ptr->pkt);
d_ptr->stream = other.d_ptr->stream;
return *this;
}
QAVPacket::operator bool() const
{
Q_D(const QAVPacket);
return d->pkt->size;
}
QAVPacket::~QAVPacket()
{
Q_D(QAVPacket);
av_packet_free(&d->pkt);
}
AVPacket *QAVPacket::packet() const
{
return d_func()->pkt;
}
double QAVPacket::duration() const
{
Q_D(const QAVPacket);
if (!d->stream)
return 0.0;
auto tb = d->stream.stream()->time_base;
return tb.num && tb.den ? d->pkt->duration * av_q2d(tb) : 0.0;
}
double QAVPacket::pts() const
{
Q_D(const QAVPacket);
if (!d->stream)
return 0.0;
auto tb = d->stream.stream()->time_base;
return tb.num && tb.den ? d->pkt->pts * av_q2d(tb) : 0.0;
}
QAVStream QAVPacket::stream() const
{
Q_D(const QAVPacket);
return d->stream;
}
void QAVPacket::setStream(const QAVStream &stream)
{
Q_D(QAVPacket);
d->stream = stream;
}
int QAVPacket::send() const
{
Q_D(const QAVPacket);
return d->stream ? d->stream.codec()->write(*this) : 0;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,58 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVPACKET_H
#define QAVPACKET_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavframe.h"
#include "qavstream.h"
#include <memory>
QT_BEGIN_NAMESPACE
struct AVPacket;
class QAVPacketPrivate;
class QAVPacket
{
public:
QAVPacket();
~QAVPacket();
QAVPacket(const QAVPacket &other);
QAVPacket &operator=(const QAVPacket &other);
operator bool() const;
AVPacket *packet() const;
double duration() const;
double pts() const;
QAVStream stream() const;
void setStream(const QAVStream &stream);
// Sends the packet to the codec
int send() const;
protected:
std::unique_ptr<QAVPacketPrivate> d_ptr;
private:
Q_DECLARE_PRIVATE(QAVPacket)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,275 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVPACKETQUEUE_H
#define QAVPACKETQUEUE_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavpacket_p.h"
#include "qavfilter_p.h"
#include "qavfiltergraph_p.h"
#include "qavframe.h"
#include "qavsubtitleframe.h"
#include "qavstreamframe.h"
#include "qavdemuxer_p.h"
#include <QMutex>
#include <QWaitCondition>
#include <QList>
#include <math.h>
#include <memory>
extern "C" {
#include <libavutil/time.h>
#include <libavcodec/avcodec.h>
}
QT_BEGIN_NAMESPACE
class QAVQueueClock
{
public:
QAVQueueClock(double v = 0.0)
: frameRate(v)
{
}
bool wait(bool shouldSync, double pts, double speed = 1.0, double master = -1)
{
QMutexLocker locker(&m_mutex);
double delay = pts - prevPts;
if (isnan(delay) || delay <= 0 || delay > maxFrameDuration)
delay = frameRate;
if (master > 0) {
double diff = pts - master;
double sync_threshold = qMax(minThreshold, qMin(maxThreshold, delay));
if (!isnan(diff) && fabs(diff) < maxFrameDuration) {
if (diff <= -sync_threshold)
delay = qMax(0.0, delay + diff);
else if (diff >= sync_threshold && delay > frameDuplicationThreshold)
delay = delay + diff;
else if (diff >= sync_threshold)
delay = 2 * delay;
}
}
delay /= speed;
const double time = av_gettime_relative() / 1000000.0;
if (shouldSync) {
if (time < frameTimer + delay) {
double remaining_time = qMin(frameTimer + delay - time, refreshRate);
locker.unlock();
av_usleep((int64_t)(remaining_time * 1000000.0));
return false;
}
}
prevPts = pts;
frameTimer += delay;
if ((delay > 0 && time - frameTimer > maxThreshold) || !shouldSync)
frameTimer = time;
return true;
}
double pts() const
{
QMutexLocker locker(&m_mutex);
return prevPts;
}
void clear()
{
QMutexLocker locker(&m_mutex);
prevPts = 0;
frameTimer = 0;
}
void setFrameRate(double v)
{
QMutexLocker locker(&m_mutex);
frameRate = v;
}
private:
double frameRate = 0;
double frameTimer = 0;
double prevPts = 0;
mutable QMutex m_mutex;
const double maxFrameDuration = 10.0;
const double minThreshold = 0.04;
const double maxThreshold = 0.1;
const double frameDuplicationThreshold = 0.1;
const double refreshRate = 0.01;
};
template<class T>
class QAVPacketQueue
{
public:
QAVPacketQueue(AVMediaType mediaType, QAVDemuxer &demuxer)
: m_mediaType(mediaType)
, m_demuxer(demuxer)
{
}
~QAVPacketQueue()
{
abort();
}
AVMediaType mediaType() const
{
return m_mediaType;
}
bool isEmpty() const
{
QMutexLocker locker(&m_mutex);
return m_packets.isEmpty() && m_decodedFrames.isEmpty();
}
void enqueue(const QAVPacket &packet)
{
QMutexLocker locker(&m_mutex);
m_packets.append(packet);
m_bytes += packet.packet()->size + sizeof(packet);
m_duration += packet.duration();
m_consumerWaiter.wakeAll();
m_abort = false;
m_waitingForPackets = false;
}
bool frontFrame(T &frame)
{
QMutexLocker locker(&m_mutex);
if (m_decodedFrames.isEmpty())
m_demuxer.decode(dequeue(), m_decodedFrames);
if (m_decodedFrames.isEmpty())
return false;
frame = m_decodedFrames.front();
return true;
}
void popFrame()
{
QMutexLocker locker(&m_mutex);
if (!m_decodedFrames.isEmpty())
m_decodedFrames.pop_front();
}
void waitForEmpty()
{
QMutexLocker locker(&m_mutex);
clearPackets();
if (!m_abort && !m_waitingForPackets)
m_producerWaiter.wait(&m_mutex);
}
void abort()
{
QMutexLocker locker(&m_mutex);
m_abort = true;
m_waitingForPackets = true;
m_consumerWaiter.wakeAll();
m_producerWaiter.wakeAll();
}
bool enough() const
{
QMutexLocker locker(&m_mutex);
const int minFrames = 15;
return m_packets.size() > minFrames && (!m_duration || m_duration > 1.0);
}
int bytes() const
{
QMutexLocker locker(&m_mutex);
return m_bytes;
}
void clear()
{
QMutexLocker locker(&m_mutex);
clearPackets();
}
void clearFrames()
{
QMutexLocker locker(&m_mutex);
m_decodedFrames.clear();
}
void wake(bool wake)
{
QMutexLocker locker(&m_mutex);
if (wake)
m_consumerWaiter.wakeAll();
m_wake = wake;
}
private:
QAVPacket dequeue()
{
if (m_packets.isEmpty()) {
m_producerWaiter.wakeAll();
if (!m_abort && !m_wake) {
m_waitingForPackets = true;
m_consumerWaiter.wait(&m_mutex);
m_waitingForPackets = false;
}
}
if (m_packets.isEmpty())
return {};
auto packet = m_packets.takeFirst();
m_bytes -= packet.packet()->size + sizeof(packet);
m_duration -= packet.duration();
return packet;
}
void clearPackets()
{
m_packets.clear();
m_decodedFrames.clear();
m_bytes = 0;
m_duration = 0;
}
const AVMediaType m_mediaType = AVMEDIA_TYPE_UNKNOWN;
QAVDemuxer &m_demuxer;
QList<QAVPacket> m_packets;
// Tracks decoded frames to prevent EOF if not all frames are landed
QList<T> m_decodedFrames;
mutable QMutex m_mutex;
QWaitCondition m_consumerWaiter;
QWaitCondition m_producerWaiter;
bool m_abort = false;
bool m_waitingForPackets = true;
bool m_wake = false;
int m_bytes = 0;
int m_duration = 0;
private:
Q_DISABLE_COPY(QAVPacketQueue)
};
QT_END_NAMESPACE
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVPLAYER_H
#define QAVPLAYER_H
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudioframe.h>
#include <QtAVPlayer/qavsubtitleframe.h>
#include <QtAVPlayer/qavstream.h>
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QString>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVIODevice;
class QAVPlayerPrivate;
class QAVPlayer : public QObject
{
Q_OBJECT
Q_ENUMS(State)
Q_ENUMS(MediaStatus)
Q_ENUMS(Error)
public:
enum State
{
StoppedState,
PlayingState,
PausedState
};
enum MediaStatus
{
NoMedia,
LoadedMedia,
EndOfMedia,
InvalidMedia
};
enum Error
{
NoError,
ResourceError,
FilterError
};
QAVPlayer(QObject *parent = nullptr);
~QAVPlayer();
void setSource(const QString &url, const QSharedPointer<QAVIODevice> &dev = {});
QString source() const;
QList<QAVStream> availableVideoStreams() const;
QList<QAVStream> currentVideoStreams() const;
void setVideoStream(const QAVStream &stream);
void setVideoStreams(const QList<QAVStream> &streams);
QList<QAVStream> availableAudioStreams() const;
QList<QAVStream> currentAudioStreams() const;
void setAudioStream(const QAVStream &stream);
void setAudioStreams(const QList<QAVStream> &streams);
QList<QAVStream> availableSubtitleStreams() const;
QList<QAVStream> currentSubtitleStreams() const;
void setSubtitleStream(const QAVStream &stream);
void setSubtitleStreams(const QList<QAVStream> &streams);
State state() const;
MediaStatus mediaStatus() const;
qint64 duration() const;
qint64 position() const;
qreal speed() const;
double videoFrameRate() const;
void setFilter(const QString &desc);
void setFilters(const QList<QString> &filters);
QList<QString> filters() const;
void setBitstreamFilter(const QString &desc);
QString bitstreamFilter() const;
bool isSeekable() const;
bool isSynced() const;
void setSynced(bool sync);
QString inputFormat() const;
void setInputFormat(const QString &format);
QString inputVideoCodec() const;
void setInputVideoCodec(const QString &codec);
static QStringList supportedVideoCodecs();
QMap<QString, QString> inputOptions() const;
void setInputOptions(const QMap<QString, QString> &opts);
QAVStream::Progress progress(const QAVStream &stream) const;
public Q_SLOTS:
void play();
void pause();
void stop();
void seek(qint64 position);
void setSpeed(qreal rate);
void stepForward();
void stepBackward();
Q_SIGNALS:
void sourceChanged(const QString &url);
void stateChanged(QAVPlayer::State newState);
void mediaStatusChanged(QAVPlayer::MediaStatus status);
void errorOccurred(QAVPlayer::Error, const QString &str);
void durationChanged(qint64 duration);
void seekableChanged(bool seekable);
void speedChanged(qreal rate);
void videoFrameRateChanged(double rate);
void videoStreamsChanged(const QList<QAVStream> &streams);
void audioStreamsChanged(const QList<QAVStream> &streams);
void subtitleStreamsChanged(const QList<QAVStream> &streams);
void played(qint64 pos);
void paused(qint64 pos);
void stopped(qint64 pos);
void stepped(qint64 pos);
void seeked(qint64 pos);
void filtersChanged(const QList<QString> &filters);
void bitstreamFilterChanged(const QString &desc);
void syncedChanged(bool sync);
void inputFormatChanged(const QString &format);
void inputVideoCodecChanged(const QString &codec);
void inputOptionsChanged(const QMap<QString, QString> &opts);
void videoFrame(const QAVVideoFrame &frame);
void audioFrame(const QAVAudioFrame &frame);
void subtitleFrame(const QAVSubtitleFrame &frame);
protected:
std::unique_ptr<QAVPlayerPrivate> d_ptr;
private:
Q_DISABLE_COPY(QAVPlayer)
Q_DECLARE_PRIVATE(QAVPlayer)
};
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug, QAVPlayer::State);
QDebug operator<<(QDebug, QAVPlayer::MediaStatus);
QDebug operator<<(QDebug, QAVPlayer::Error);
#endif
Q_DECLARE_METATYPE(QAVPlayer::State)
Q_DECLARE_METATYPE(QAVPlayer::MediaStatus)
Q_DECLARE_METATYPE(QAVPlayer::Error)
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,285 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavstream.h"
#include "qavdemuxer_p.h"
#include "qavcodec_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/display.h>
#include <libavutil/time.h>
#include <libavcodec/version.h>
}
QT_BEGIN_NAMESPACE
class QAVStreamPrivate
{
Q_DECLARE_PUBLIC(QAVStream)
public:
QAVStreamPrivate(QAVStream *q) : q_ptr(q) { }
QAVStream *q_ptr = nullptr;
int index = -1;
AVFormatContext *ctx = nullptr;
QSharedPointer<QAVCodec> codec;
QMap<QString, QString> metadata;
};
QAVStream::QAVStream()
: d_ptr(new QAVStreamPrivate(this))
{
}
QAVStream::QAVStream(int index, AVFormatContext *ctx, const QSharedPointer<QAVCodec> &codec)
: QAVStream()
{
d_ptr->index = index;
d_ptr->ctx = ctx;
d_ptr->codec = codec;
}
QAVStream::~QAVStream()
{
}
QAVStream::QAVStream(const QAVStream &other)
: QAVStream()
{
*this = other;
}
QAVStream &QAVStream::operator=(const QAVStream &other)
{
d_ptr->index = other.d_ptr->index;
d_ptr->ctx = other.d_ptr->ctx;
d_ptr->codec = other.d_ptr->codec;
return *this;
}
QAVStream::operator bool() const
{
Q_D(const QAVStream);
return d->ctx != nullptr && d->codec && d->index >= 0;
}
AVStream *QAVStream::stream() const
{
Q_D(const QAVStream);
return d->index >= 0 && d->index < static_cast<int>(d->ctx->nb_streams) ? d->ctx->streams[d->index] : nullptr;
}
int QAVStream::index() const
{
return d_func()->index;
}
static int streamRotation(const AVStream *stream)
{
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 29, 100)
auto ptr = av_packet_side_data_get(stream->codecpar->coded_side_data,
stream->codecpar->nb_coded_side_data,
AV_PKT_DATA_DISPLAYMATRIX);
auto sideData = ptr ? ptr->data : nullptr;
#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(55, 18, 0)
auto sideData = av_stream_get_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, nullptr);
#else
auto cb = [](const auto &data) { return data.type == AV_PKT_DATA_DISPLAYMATRIX; };
auto end = stream->side_data + stream->nb_side_data;
auto ptr = std::find_if(stream->side_data, end, cb);
auto sideData = ptr != end ? ptr->data : nullptr;
#endif
if (!sideData)
return 0;
auto rotation = static_cast<int>(std::round(av_display_rotation_get(reinterpret_cast<const int32_t *>(sideData))));
if (rotation % 90 != 0)
return 0;
return rotation > 0 ? -rotation % 360 + 360 : -rotation % 360;
}
static QMap<QString, QString> streamMetadata(const AVStream *stream)
{
QMap<QString, QString> metadata;
AVDictionaryEntry *tag = nullptr;
while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
metadata[QString::fromUtf8(tag->key)] = QString::fromUtf8(tag->value);
if (!metadata.contains(QString::fromLatin1("rotate")))
metadata[QString::fromLatin1("rotate")] = QString::number(streamRotation(stream));
return metadata;
}
QMap<QString, QString> QAVStream::metadata() const
{
Q_D(const QAVStream);
if (!d->metadata.isEmpty())
return d->metadata;
auto s = stream();
if (!s)
return {};
const_cast<QAVStreamPrivate *>(d)->metadata = streamMetadata(s);
return d->metadata;
}
QSharedPointer<QAVCodec> QAVStream::codec() const
{
Q_D(const QAVStream);
return d->codec;
}
double QAVStream::duration() const
{
Q_D(const QAVStream);
auto s = stream();
if (!s)
return 0.0;
double ret = 0.0;
if (s->duration != AV_NOPTS_VALUE)
ret = s->duration * av_q2d(s->time_base);
if (!ret && d->ctx->duration != AV_NOPTS_VALUE)
ret = d->ctx->duration / AV_TIME_BASE;
return ret;
}
int64_t QAVStream::framesCount() const
{
auto s = stream();
if (s == nullptr)
return 0;
auto frames = s->nb_frames;
if (frames)
return frames;
auto dur = duration();
// If frame count is not known, estimating it
if (s->avg_frame_rate.num && s->avg_frame_rate.den && dur)
return dur * av_q2d(s->avg_frame_rate);
const auto tb = s->time_base;
if ((tb.num == 1 && tb.den >= 24 && tb.den <= 60) ||
(tb.num == 1001 && tb.den >= 24000 && tb.den <= 60000))
{
return s->duration;
}
return 0;
}
double QAVStream::frameRate() const
{
Q_D(const QAVStream);
auto s = stream();
if (s == nullptr)
return 0.0;
AVRational fr = av_guess_frame_rate(d->ctx, s, nullptr);
return fr.num && fr.den ? av_q2d({fr.den, fr.num}) : 0.0;
}
QAVStream::Progress::Progress(double duration, qint64 frames, double fr)
: m_duration(duration)
, m_expectedFramesCount(frames)
, m_expectedFrameRate(fr)
{
}
QAVStream::Progress::Progress(const Progress &other)
{
*this = other;
}
QAVStream::Progress &QAVStream::Progress::operator=(const Progress &other)
{
m_pts = other.m_pts;
m_duration = other.m_duration;
m_framesCount = other.m_framesCount;
m_expectedFramesCount = other.m_expectedFramesCount;
m_expectedFrameRate = other.m_expectedFrameRate;
m_time = other.m_time;
m_diffs = other.m_diffs;
return *this;
}
double QAVStream::Progress::pts() const
{
return m_pts;
}
double QAVStream::Progress::duration() const
{
return m_duration;
}
qint64 QAVStream::Progress::framesCount() const
{
return m_framesCount;
}
qint64 QAVStream::Progress::expectedFramesCount() const
{
return m_expectedFramesCount;
}
double QAVStream::Progress::expectedFrameRate() const
{
return m_expectedFrameRate;
}
double QAVStream::Progress::frameRate() const
{
return m_framesCount ? m_diffs / 1000000.0 / static_cast<double>(m_framesCount) : 0.0;
}
unsigned QAVStream::Progress::fps() const
{
double fr = frameRate();
return fr ? static_cast<unsigned>(1 / fr) : 0;
}
void QAVStream::Progress::onFrameSent(double pts)
{
m_pts = pts;
qint64 cur = av_gettime_relative();
if (m_framesCount++ > 0) {
qint64 diff = cur - m_time;
if (diff > 0)
m_diffs += diff;
}
m_time = cur;
}
bool operator==(const QAVStream &lhs, const QAVStream &rhs)
{
return lhs.index() == rhs.index();
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QAVStream &stream)
{
QDebugStateSaver saver(dbg);
dbg.nospace();
return dbg << QString(QLatin1String("QAVStream(%1)" )).arg(stream.index()).toLatin1().constData();
}
QDebug operator<<(QDebug dbg, const QAVStream::Progress &p)
{
QDebugStateSaver saver(dbg);
dbg.nospace();
return dbg << QString(QLatin1String("Progress(%1/%2 pts, %3/%4 frames, %5/%6 frame rate, %7 fps)"))
.arg(p.pts())
.arg(p.duration())
.arg(p.framesCount())
.arg(p.expectedFramesCount())
.arg(p.frameRate())
.arg(p.expectedFrameRate())
.arg(p.fps()).toLatin1().constData();
}
#endif
QT_END_NAMESPACE

View File

@ -0,0 +1,83 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVSTREAM_H
#define QAVSTREAM_H
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QMap>
#include <QSharedPointer>
#include <memory>
QT_BEGIN_NAMESPACE
struct AVStream;
struct AVFormatContext;
class QAVCodec;
class QAVStreamPrivate;
class QAVStream
{
public:
QAVStream();
QAVStream(int index, AVFormatContext *ctx = nullptr, const QSharedPointer<QAVCodec> &codec = {});
QAVStream(const QAVStream &other);
~QAVStream();
QAVStream &operator=(const QAVStream &other);
operator bool() const;
int index() const;
AVStream *stream() const;
double duration() const;
int64_t framesCount() const;
double frameRate() const;
QMap<QString, QString> metadata() const;
QSharedPointer<QAVCodec> codec() const;
class Progress
{
public:
Progress(double duration = 0.0, qint64 frames = 0, double fr = 0.0);
Progress(const Progress &other);
Progress &operator=(const Progress &other);
double pts() const;
double duration() const;
qint64 framesCount() const;
qint64 expectedFramesCount() const;
double frameRate() const;
double expectedFrameRate() const;
unsigned fps() const;
void onFrameSent(double pts);
private:
double m_pts = 0.0;
double m_duration = 0.0;
qint64 m_framesCount = 0;
qint64 m_expectedFramesCount = 0;
double m_expectedFrameRate = 0.0;
qint64 m_time = 0;
qint64 m_diffs = 0;
};
private:
std::unique_ptr<QAVStreamPrivate> d_ptr;
Q_DECLARE_PRIVATE(QAVStream)
};
bool operator==(const QAVStream &lhs, const QAVStream &rhs);
Q_DECLARE_METATYPE(QAVStream)
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug, const QAVStream &);
QDebug operator<<(QDebug, const QAVStream::Progress &);
#endif
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,81 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavstreamframe.h"
#include "qavstreamframe_p.h"
#include "qavframe_p.h"
#include "qavcodec_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
QAVStreamFrame::QAVStreamFrame()
: QAVStreamFrame(*new QAVStreamFramePrivate)
{
}
QAVStreamFrame::QAVStreamFrame(const QAVStreamFrame &other)
: QAVStreamFrame()
{
*this = other;
}
QAVStreamFrame::QAVStreamFrame(QAVStreamFramePrivate &d)
: d_ptr(&d)
{
}
QAVStreamFrame::~QAVStreamFrame()
{
}
QAVStream QAVStreamFrame::stream() const
{
return d_ptr->stream;
}
void QAVStreamFrame::setStream(const QAVStream &stream)
{
Q_D(QAVStreamFrame);
d->stream = stream;
}
QAVStreamFrame &QAVStreamFrame::operator=(const QAVStreamFrame &other)
{
d_ptr->stream = other.d_ptr->stream;
return *this;
}
QAVStreamFrame::operator bool() const
{
Q_D(const QAVStreamFrame);
return d->stream;
}
double QAVStreamFrame::pts() const
{
Q_D(const QAVStreamFrame);
return d->pts();
}
double QAVStreamFrame::duration() const
{
Q_D(const QAVStreamFrame);
return d->duration();
}
int QAVStreamFrame::receive()
{
Q_D(QAVStreamFrame);
return d->stream ? d->stream.codec()->read(*this) : 0;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,45 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVSTREAMFRAME_H
#define QAVSTREAMFRAME_H
#include <QtAVPlayer/qtavplayerglobal.h>
#include <QtAVPlayer/qavstream.h>
#include <memory>
QT_BEGIN_NAMESPACE
class QAVStreamFramePrivate;
class QAVStreamFrame
{
public:
QAVStreamFrame();
QAVStreamFrame(const QAVStreamFrame &other);
~QAVStreamFrame();
QAVStreamFrame &operator=(const QAVStreamFrame &other);
QAVStream stream() const;
void setStream(const QAVStream &stream);
operator bool() const;
double pts() const;
double duration() const;
// Receives a data from the codec from the stream
int receive();
protected:
QAVStreamFrame(QAVStreamFramePrivate &d);
std::unique_ptr<QAVStreamFramePrivate> d_ptr;
Q_DECLARE_PRIVATE(QAVStreamFrame)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,41 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVSTREAMFRAME_P_H
#define QAVSTREAMFRAME_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavstream.h"
#include <cmath>
QT_BEGIN_NAMESPACE
class QAVStreamFramePrivate
{
public:
QAVStreamFramePrivate() = default;
virtual ~QAVStreamFramePrivate() = default;
virtual double pts() const { return NAN; }
virtual double duration() const { return 0.0; }
QAVStream stream;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,60 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavsubtitlecodec_p.h"
#include "qavcodec_p_p.h"
#include <QDebug>
extern "C" {
#include <libavcodec/avcodec.h>
}
QT_BEGIN_NAMESPACE
class QAVSubtitleCodecPrivate : public QAVCodecPrivate
{
Q_DECLARE_PUBLIC(QAVSubtitleCodec)
public:
QAVSubtitleCodecPrivate(QAVSubtitleCodec *q) : q_ptr(q) { }
QAVSubtitleCodec *q_ptr = nullptr;
QAVSubtitleFrame frame;
int gotOutput = 0;
};
QAVSubtitleCodec::QAVSubtitleCodec()
: QAVCodec(*new QAVSubtitleCodecPrivate(this))
{
}
int QAVSubtitleCodec::write(const QAVPacket &pkt)
{
Q_D(QAVSubtitleCodec);
if (!d->avctx)
return AVERROR(EINVAL);
d->frame.setStream(pkt.stream());
return avcodec_decode_subtitle2(
d->avctx,
d->frame.subtitle(),
&d->gotOutput,
const_cast<AVPacket *>(pkt.packet()));
}
int QAVSubtitleCodec::read(QAVStreamFrame &frame)
{
Q_D(QAVSubtitleCodec);
if (!d->avctx)
return AVERROR(EINVAL);
if (!d->gotOutput)
return AVERROR(EAGAIN);
*static_cast<QAVSubtitleFrame *>(&frame) = d->frame;
d->gotOutput = 0;
d->frame = {};
return 0;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,43 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVSUBTITLECODEC_P_H
#define QAVSUBTITLECODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavsubtitleframe.h"
#include "qavcodec_p.h"
#include "qavpacket_p.h"
QT_BEGIN_NAMESPACE
class QAVSubtitleCodecPrivate;
class QAVSubtitleCodec : public QAVCodec
{
public:
QAVSubtitleCodec();
int write(const QAVPacket &pkt) override;
int read(QAVStreamFrame &frame) override;
private:
Q_DECLARE_PRIVATE(QAVSubtitleCodec)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,83 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavsubtitleframe.h"
#include "qavstreamframe_p.h"
#include <QDebug>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
QT_BEGIN_NAMESPACE
class QAVSubtitleFramePrivate : public QAVStreamFramePrivate
{
public:
QSharedPointer<AVSubtitle> subtitle;
double pts() const override;
double duration() const override;
};
static void subtitle_free(AVSubtitle *subtitle)
{
avsubtitle_free(subtitle);
}
QAVSubtitleFrame::QAVSubtitleFrame()
: QAVStreamFrame(*new QAVSubtitleFramePrivate)
{
Q_D(QAVSubtitleFrame);
d->subtitle.reset(new AVSubtitle, subtitle_free);
memset(d->subtitle.data(), 0, sizeof(*d->subtitle.data()));
}
QAVSubtitleFrame::~QAVSubtitleFrame()
{
}
QAVSubtitleFrame::QAVSubtitleFrame(const QAVSubtitleFrame &other)
: QAVSubtitleFrame()
{
operator=(other);
}
QAVSubtitleFrame &QAVSubtitleFrame::operator=(const QAVSubtitleFrame &other)
{
Q_D(QAVSubtitleFrame);
QAVStreamFrame::operator=(other);
d->subtitle = static_cast<QAVSubtitleFramePrivate *>(other.d_ptr.get())->subtitle;
return *this;
}
AVSubtitle *QAVSubtitleFrame::subtitle() const
{
Q_D(const QAVSubtitleFrame);
return d->subtitle.data();
}
double QAVSubtitleFramePrivate::pts() const
{
if (!subtitle)
return NAN;
AVRational tb;
tb.num = 1;
tb.den = AV_TIME_BASE;
return subtitle->pts == AV_NOPTS_VALUE ? NAN : subtitle->pts * av_q2d(tb);
}
double QAVSubtitleFramePrivate::duration() const
{
if (!subtitle)
return 0.0;
return (subtitle->end_display_time - subtitle->start_display_time) / 1000.0;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,35 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFSUBTITLERAME_H
#define QAVFSUBTITLERAME_H
#include <QtAVPlayer/qavstreamframe.h>
QT_BEGIN_NAMESPACE
struct AVSubtitle;
class QAVSubtitleFramePrivate;
class QAVSubtitleFrame : public QAVStreamFrame
{
public:
QAVSubtitleFrame();
~QAVSubtitleFrame();
QAVSubtitleFrame(const QAVSubtitleFrame &other);
QAVSubtitleFrame &operator=(const QAVSubtitleFrame &other);
AVSubtitle *subtitle() const;
private:
Q_DECLARE_PRIVATE(QAVSubtitleFrame)
};
Q_DECLARE_METATYPE(QAVSubtitleFrame)
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,38 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideobuffer_cpu_p.h"
extern "C" {
#include <libavutil/imgutils.h>
#include <libavutil/frame.h>
}
QT_BEGIN_NAMESPACE
QAVVideoFrame::MapData QAVVideoBuffer_CPU::map()
{
QAVVideoFrame::MapData mapData;
auto frame = m_frame.frame();
if (frame->format == AV_PIX_FMT_NONE)
return mapData;
mapData.size = av_image_get_buffer_size(AVPixelFormat(frame->format), frame->width, frame->height, 1);
mapData.format = AVPixelFormat(frame->format);
for (int i = 0; i < 4; ++i) {
if (!frame->linesize[i])
break;
mapData.bytesPerLine[i] = frame->linesize[i];
mapData.data[i] = static_cast<uchar *>(frame->data[i]);
}
return mapData;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,38 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOBUFFER_CPU_P_H
#define QAVVIDEOBUFFER_CPU_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavvideobuffer_p.h"
QT_BEGIN_NAMESPACE
class QAVVideoBuffer_CPU : public QAVVideoBuffer
{
public:
QAVVideoBuffer_CPU() = default;
~QAVVideoBuffer_CPU() = default;
explicit QAVVideoBuffer_CPU(const QAVVideoFrame &frame) : QAVVideoBuffer(frame) { }
QAVVideoFrame::MapData map() override;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,34 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideobuffer_gpu_p.h"
#include <QDebug>
extern "C" {
#include <libavutil/hwcontext.h>
#include <libavutil/pixdesc.h>
}
QT_BEGIN_NAMESPACE
QAVVideoFrame::MapData QAVVideoBuffer_GPU::map()
{
auto mapData = m_cpu.map();
if (mapData.format == AV_PIX_FMT_NONE) {
int ret = av_hwframe_transfer_data(m_cpu.frame().frame(), m_frame.frame(), 0);
if (ret < 0) {
qWarning() << "Could not av_hwframe_transfer_data:" << ret;
return {};
}
m_frame = QAVVideoFrame();
mapData = m_cpu.map();
}
return mapData;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,42 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOBUFFER_GPU_P_H
#define QAVVIDEOBUFFER_GPU_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavvideobuffer_p.h"
#include "qavvideobuffer_cpu_p.h"
QT_BEGIN_NAMESPACE
class QAVVideoBuffer_GPU : public QAVVideoBuffer
{
public:
QAVVideoBuffer_GPU() = default;
explicit QAVVideoBuffer_GPU(const QAVVideoFrame &frame) : QAVVideoBuffer(frame) { }
~QAVVideoBuffer_GPU() = default;
QAVVideoFrame::MapData map() override;
protected:
QAVVideoBuffer_CPU m_cpu;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,45 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOBUFFER_P_H
#define QAVVIDEOBUFFER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtAVPlayer/qavvideoframe.h>
#include <QVariant>
QT_BEGIN_NAMESPACE
class QRhi;
class QAVVideoBuffer
{
public:
QAVVideoBuffer() = default;
explicit QAVVideoBuffer(const QAVVideoFrame &frame) : m_frame(frame) { }
virtual ~QAVVideoBuffer() = default;
const QAVVideoFrame &frame() const { return m_frame; }
virtual QAVVideoFrame::MapData map() = 0;
virtual QAVVideoFrame::HandleType handleType() const { return QAVVideoFrame::NoHandle; }
virtual QVariant handle(QRhi */*rhi*/ = nullptr) const { return {}; }
protected:
QAVVideoFrame m_frame;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,147 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideocodec_p.h"
#include "qavhwdevice_p.h"
#include "qavcodec_p_p.h"
#include "qavpacket_p.h"
#include "qavframe.h"
#include "qavvideoframe.h"
#include <QDebug>
extern "C" {
#include <libavutil/pixdesc.h>
#include <libavcodec/avcodec.h>
}
QT_BEGIN_NAMESPACE
class QAVVideoCodecPrivate : public QAVCodecPrivate
{
public:
QSharedPointer<QAVHWDevice> hw_device;
};
static bool isSoftwarePixelFormat(AVPixelFormat from)
{
switch (from) {
case AV_PIX_FMT_VAAPI:
case AV_PIX_FMT_VDPAU:
case AV_PIX_FMT_MEDIACODEC:
case AV_PIX_FMT_VIDEOTOOLBOX:
case AV_PIX_FMT_D3D11:
case AV_PIX_FMT_D3D11VA_VLD:
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 0, 0)
case AV_PIX_FMT_OPENCL:
#endif
case AV_PIX_FMT_CUDA:
case AV_PIX_FMT_DXVA2_VLD:
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 58, 101)
case AV_PIX_FMT_XVMC:
#endif
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 134, 0)
case AV_PIX_FMT_VULKAN:
#endif
case AV_PIX_FMT_DRM_PRIME:
case AV_PIX_FMT_MMAL:
case AV_PIX_FMT_QSV:
return false;
default:
return true;
}
}
static AVPixelFormat negotiate_pixel_format(AVCodecContext *c, const AVPixelFormat *f)
{
auto d = reinterpret_cast<QAVVideoCodecPrivate *>(c->opaque);
QList<AVHWDeviceType> supported;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 0, 0)
for (int i = 0;; ++i) {
const AVCodecHWConfig *config = avcodec_get_hw_config(c->codec, i);
if (!config)
break;
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
supported.append(config->device_type);
}
if (!supported.isEmpty()) {
qDebug() << c->codec->name << ": supported hardware device contexts:";
for (auto a: supported)
qDebug() << " " << av_hwdevice_get_type_name(a);
} else {
qWarning() << "None of the hardware accelerations are supported";
}
#endif
QList<AVPixelFormat> softwareFormats;
QList<AVPixelFormat> hardwareFormats;
for (int i = 0; f[i] != AV_PIX_FMT_NONE; ++i) {
if (!isSoftwarePixelFormat(f[i])) {
hardwareFormats.append(f[i]);
continue;
}
softwareFormats.append(f[i]);
}
qDebug() << "Available pixel formats:";
for (auto a : softwareFormats) {
auto dsc = av_pix_fmt_desc_get(a);
qDebug() << " " << dsc->name << ": AVPixelFormat(" << a << ")";
}
for (auto a : hardwareFormats) {
auto dsc = av_pix_fmt_desc_get(a);
qDebug() << " " << dsc->name << ": AVPixelFormat(" << a << ")";
}
AVPixelFormat pf = !softwareFormats.isEmpty() ? softwareFormats[0] : AV_PIX_FMT_NONE;
const char *decStr = "software";
if (d->hw_device) {
for (auto f : hardwareFormats) {
if (f == d->hw_device->format()) {
d->hw_device->init(c);
pf = d->hw_device->format();
decStr = "hardware";
break;
}
}
}
auto dsc = av_pix_fmt_desc_get(pf);
if (dsc)
qDebug() << "Using" << decStr << "decoding in" << dsc->name;
else
qDebug() << "None of the pixel formats";
return pf;
}
QAVVideoCodec::QAVVideoCodec()
: QAVFrameCodec(*new QAVVideoCodecPrivate)
{
d_ptr->avctx->opaque = d_ptr.get();
d_ptr->avctx->get_format = negotiate_pixel_format;
}
QAVVideoCodec::~QAVVideoCodec()
{
av_buffer_unref(&avctx()->hw_device_ctx);
}
void QAVVideoCodec::setDevice(const QSharedPointer<QAVHWDevice> &d)
{
d_func()->hw_device = d;
}
QAVHWDevice *QAVVideoCodec::device() const
{
return d_func()->hw_device.data();
}
QT_END_NAMESPACE

View File

@ -0,0 +1,44 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOCODEC_P_H
#define QAVVIDEOCODEC_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavframecodec_p.h"
QT_BEGIN_NAMESPACE
class QAVVideoCodecPrivate;
class QAVHWDevice;
class QAVVideoCodec : public QAVFrameCodec
{
public:
QAVVideoCodec();
~QAVVideoCodec();
void setDevice(const QSharedPointer<QAVHWDevice> &d);
QAVHWDevice *device() const;
private:
Q_DISABLE_COPY(QAVVideoCodec)
Q_DECLARE_PRIVATE(QAVVideoCodec)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,149 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideofilter_p.h"
#include "qavfilter_p_p.h"
#include "qavcodec_p.h"
#include "qavvideoframe.h"
#include "qavstream.h"
#include <QDebug>
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavformat/avformat.h>
}
QT_BEGIN_NAMESPACE
class QAVVideoFilterPrivate : public QAVFilterPrivate
{
public:
QAVVideoFilterPrivate(QAVFilter *q, QMutex &mutex) : QAVFilterPrivate(q, mutex) { }
QList<QAVVideoInputFilter> inputs;
QList<QAVVideoOutputFilter> outputs;
};
QAVVideoFilter::QAVVideoFilter(
const QAVStream &stream,
const QString &name,
const QList<QAVVideoInputFilter> &inputs,
const QList<QAVVideoOutputFilter> &outputs,
QMutex &mutex)
: QAVFilter(
stream,
name,
*new QAVVideoFilterPrivate(this, mutex))
{
Q_D(QAVVideoFilter);
d->inputs = inputs;
d->outputs = outputs;
}
int QAVVideoFilter::write(const QAVFrame &frame)
{
Q_D(QAVVideoFilter);
if (!frame || frame.stream().stream()->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
qWarning() << "Frame is not video";
return AVERROR(EINVAL);
}
if (!d->isEmpty)
return AVERROR(EAGAIN);
d->sourceFrame = frame;
for (const auto &filter : d->inputs) {
if (!filter.supports(d->sourceFrame)) {
d->sourceFrame = {};
return AVERROR(ENOTSUP);
}
QAVFrame ref = d->sourceFrame;
QMutexLocker locker(&d->graphMutex);
int ret = av_buffersrc_add_frame_flags(filter.ctx(), ref.frame(), AV_BUFFERSRC_FLAG_PUSH);
if (ret < 0)
return ret;
}
d->isEmpty = false;
return 0;
}
int QAVVideoFilter::read(QAVFrame &frame)
{
Q_D(QAVVideoFilter);
if (d->outputs.isEmpty() || d->isEmpty) {
int ret = AVERROR(EAGAIN);
if (d->sourceFrame && d->outputs.isEmpty()) {
frame = d->sourceFrame;
ret = 0;
}
d->sourceFrame = {};
d->isEmpty = true;
return ret;
}
int ret = 0;
if (d->outputFrames.isEmpty()) {
for (int i = 0; i < d->outputs.size(); ++i) {
const auto &filter = d->outputs[i];
while (true) {
QAVFrame out = d->sourceFrame;
// av_buffersink_get_frame_flags allocates frame's data
av_frame_unref(out.frame());
{
QMutexLocker locker(&d->graphMutex);
ret = av_buffersink_get_frame_flags(filter.ctx(), out.frame(), 0);
}
if (ret < 0)
break;
#if LIBAVUTIL_VERSION_INT <= AV_VERSION_INT(57, 30, 0)
if (!out.frame()->pkt_duration)
out.frame()->pkt_duration = d->sourceFrame.frame()->pkt_duration;
#else
if (out.frame()->duration == AV_NOPTS_VALUE || out.frame()->duration == 0)
out.frame()->duration = d->sourceFrame.frame()->duration;
#endif
out.setFrameRate(av_buffersink_get_frame_rate(filter.ctx()));
out.setTimeBase(av_buffersink_get_time_base(filter.ctx()));
out.setFilterName(
!filter.name().isEmpty()
? filter.name()
: QString(QLatin1String("%1:%2")).arg(d->name).arg(QString::number(i)));
if (!out.stream())
out.setStream(d->stream);
d->outputFrames.push_back(out);
}
}
}
ret = AVERROR(EAGAIN);
if (!d->outputFrames.isEmpty()) {
frame = d->outputFrames.takeFirst();
ret = 0;
}
if (d->outputFrames.isEmpty()) {
d->sourceFrame = {};
d->isEmpty = true;
}
return ret;
}
void QAVVideoFilter::flush()
{
Q_D(QAVVideoFilter);
for (const auto &filter : d->inputs) {
int ret = av_buffersrc_add_frame(filter.ctx(), nullptr);
if (ret < 0)
qWarning() << "Could not flush:" << ret;
}
d->isEmpty = false;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,52 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOFILTER_P_H
#define QAVVIDEOFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavfilter_p.h"
#include "qavvideoinputfilter_p.h"
#include "qavvideooutputfilter_p.h"
#include <QMutex>
QT_BEGIN_NAMESPACE
class QAVVideoFilterPrivate;
class QAVVideoFilter : public QAVFilter
{
public:
QAVVideoFilter(
const QAVStream &stream,
const QString &name,
const QList<QAVVideoInputFilter> &inputs,
const QList<QAVVideoOutputFilter> &outputs,
QMutex &mutex);
int write(const QAVFrame &frame) override;
int read(QAVFrame &frame) override;
void flush() override;
protected:
Q_DECLARE_PRIVATE(QAVVideoFilter)
private:
Q_DISABLE_COPY(QAVVideoFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,475 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideoframe.h"
#include "qavvideobuffer_cpu_p.h"
#include "qavframe_p.h"
#include "qavvideocodec_p.h"
#include "qavhwdevice_p.h"
#include <QSize>
#ifdef QT_AVPLAYER_MULTIMEDIA
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAbstractVideoSurface>
#else
#include <QtMultimedia/private/qabstractvideobuffer_p.h>
#include <QtMultimedia/private/qvideotexturehelper_p.h>
#endif
#endif
#include <QDebug>
extern "C" {
#include <libswscale/swscale.h>
#include <libavutil/pixdesc.h>
#include "libavutil/imgutils.h"
#include <libavutil/mastering_display_metadata.h>
};
QT_BEGIN_NAMESPACE
static const QAVVideoCodec *videoCodec(const QAVCodec *c)
{
return reinterpret_cast<const QAVVideoCodec *>(c);
}
class QAVVideoFramePrivate : public QAVFramePrivate
{
Q_DECLARE_PUBLIC(QAVVideoFrame)
public:
QAVVideoFramePrivate(QAVVideoFrame *q) : q_ptr(q) { }
QAVVideoBuffer &videoBuffer() const
{
if (!buffer) {
auto c = videoCodec(stream.codec().data());
auto buf = c && c->device() && frame->format == c->device()->format() ? c->device()->videoBuffer(*q_ptr) : new QAVVideoBuffer_CPU(*q_ptr);
const_cast<QAVVideoFramePrivate*>(this)->buffer.reset(buf);
}
return *buffer;
}
QAVVideoFrame *q_ptr = nullptr;
QScopedPointer<QAVVideoBuffer> buffer;
};
QAVVideoFrame::QAVVideoFrame()
: QAVFrame(*new QAVVideoFramePrivate(this))
{
}
QAVVideoFrame::QAVVideoFrame(const QAVFrame &other)
: QAVVideoFrame()
{
operator=(other);
}
QAVVideoFrame::QAVVideoFrame(const QAVVideoFrame &other)
: QAVVideoFrame()
{
operator=(other);
}
QAVVideoFrame::QAVVideoFrame(const QSize &size, AVPixelFormat fmt)
: QAVVideoFrame()
{
frame()->format = fmt;
frame()->width = size.width();
frame()->height = size.height();
av_frame_get_buffer(frame(), 1);
}
QAVVideoFrame &QAVVideoFrame::operator=(const QAVFrame &other)
{
Q_D(QAVVideoFrame);
QAVFrame::operator=(other);
d->buffer.reset();
return *this;
}
QAVVideoFrame &QAVVideoFrame::operator=(const QAVVideoFrame &other)
{
Q_D(QAVVideoFrame);
QAVFrame::operator=(other);
d->buffer.reset();
return *this;
}
QSize QAVVideoFrame::size() const
{
Q_D(const QAVFrame);
return {d->frame->width, d->frame->height};
}
QAVVideoFrame::MapData QAVVideoFrame::map() const
{
Q_D(const QAVVideoFrame);
return d->videoBuffer().map();
}
QAVVideoFrame::HandleType QAVVideoFrame::handleType() const
{
Q_D(const QAVVideoFrame);
return d->videoBuffer().handleType();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QVariant QAVVideoFrame::handle(QRhi *rhi) const
{
Q_D(const QAVVideoFrame);
return d->videoBuffer().handle(rhi);
}
#else
QVariant QAVVideoFrame::handle() const
{
Q_D(const QAVVideoFrame);
return d->videoBuffer().handle();
}
#endif
AVPixelFormat QAVVideoFrame::format() const
{
return static_cast<AVPixelFormat>(frame()->format);
}
QString QAVVideoFrame::formatName() const
{
return QLatin1String(av_pix_fmt_desc_get(QAVVideoFrame::format())->name);
}
QAVVideoFrame QAVVideoFrame::convertTo(AVPixelFormat fmt) const
{
if (fmt == frame()->format)
return *this;
auto mapData = map();
if (mapData.format == AV_PIX_FMT_NONE) {
qWarning() << __FUNCTION__ << "Could not map:" << formatName();
return QAVVideoFrame();
}
auto ctx = sws_getContext(size().width(), size().height(), mapData.format,
size().width(), size().height(), fmt,
SWS_BICUBIC, NULL, NULL, NULL);
if (ctx == nullptr) {
qWarning() << __FUNCTION__ << ": Could not get sws context:" << formatName();
return QAVVideoFrame();
}
int ret = sws_setColorspaceDetails(ctx, sws_getCoefficients(SWS_CS_ITU601),
0, sws_getCoefficients(SWS_CS_ITU709), 0, 0, 1 << 16, 1 << 16);
if (ret == -1) {
qWarning() << __FUNCTION__ << "Colorspace not support";
return QAVVideoFrame();
}
QAVVideoFrame result(size(), fmt);
result.d_ptr->stream = d_ptr->stream;
sws_scale(ctx, mapData.data, mapData.bytesPerLine, 0, result.size().height(), result.frame()->data, result.frame()->linesize);
sws_freeContext(ctx);
return result;
}
#ifdef QT_AVPLAYER_MULTIMEDIA
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
class PlanarVideoBuffer : public QAbstractPlanarVideoBuffer
{
public:
PlanarVideoBuffer(const QAVVideoFrame &frame, HandleType type = NoHandle)
: QAbstractPlanarVideoBuffer(type), m_frame(frame)
{
}
QVariant handle() const override
{
return m_frame.handle();
}
MapMode mapMode() const override { return m_mode; }
using QAbstractPlanarVideoBuffer::map;
int map(MapMode mode, int *numBytes, int bytesPerLine[4], uchar *data[4]) override
{
if (m_mode != NotMapped || mode == NotMapped)
return 0;
auto mapData = m_frame.map();
m_mode = mode;
if (numBytes)
*numBytes = mapData.size;
int i = 0;
for (; i < 4; ++i) {
if (!mapData.bytesPerLine[i])
break;
bytesPerLine[i] = mapData.bytesPerLine[i];
data[i] = mapData.data[i];
}
return i;
}
void unmap() override { m_mode = NotMapped; }
private:
QAVVideoFrame m_frame;
MapMode m_mode = NotMapped;
};
#else
class PlanarVideoBuffer : public QAbstractVideoBuffer
{
public:
PlanarVideoBuffer(const QAVVideoFrame &frame, QVideoFrameFormat::PixelFormat format
, QVideoFrame::HandleType type = QVideoFrame::NoHandle)
: QAbstractVideoBuffer(type)
, m_frame(frame)
, m_pixelFormat(format)
{
}
quint64 textureHandle(int plane) const override
{
if (m_textures.isNull())
const_cast<PlanarVideoBuffer *>(this)->m_textures = m_frame.handle(m_rhi);
if (m_textures.canConvert<QList<QVariant>>()) {
auto textures = m_textures.toList();
auto r = plane < textures.size() ? textures[plane].toULongLong() : 0;
return r;
}
return m_textures.toULongLong();
}
QVideoFrame::MapMode mapMode() const override { return m_mode; }
MapData map(QVideoFrame::MapMode mode) override
{
MapData res;
if (m_mode != QVideoFrame::NotMapped || mode == QVideoFrame::NotMapped)
return res;
m_mode = mode;
auto mapData = m_frame.map();
auto *desc = QVideoTextureHelper::textureDescription(m_pixelFormat);
res.nPlanes = desc->nplanes;
for (int i = 0; i < res.nPlanes; ++i) {
if (!mapData.bytesPerLine[i])
break;
res.data[i] = mapData.data[i];
res.bytesPerLine[i] = mapData.bytesPerLine[i];
// TODO: Reimplement heightForPlane
res.size[i] = mapData.bytesPerLine[i] * desc->heightForPlane(m_frame.size().height(), i);
}
return res;
}
void unmap() override { m_mode = QVideoFrame::NotMapped; }
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
std::unique_ptr<QVideoFrameTextures> mapTextures(QRhi *rhi) override
{
m_rhi = rhi;
if (m_textures.isNull())
m_textures = m_frame.handle(m_rhi);
return nullptr;
}
static QVideoFrameFormat::ColorSpace colorSpace(const AVFrame *frame)
{
switch (frame->colorspace) {
default:
case AVCOL_SPC_UNSPECIFIED:
case AVCOL_SPC_RESERVED:
case AVCOL_SPC_FCC:
case AVCOL_SPC_SMPTE240M:
case AVCOL_SPC_YCGCO:
case AVCOL_SPC_SMPTE2085:
case AVCOL_SPC_CHROMA_DERIVED_NCL:
case AVCOL_SPC_CHROMA_DERIVED_CL:
case AVCOL_SPC_ICTCP: // BT.2100 ICtCp
return QVideoFrameFormat::ColorSpace_Undefined;
case AVCOL_SPC_RGB:
return QVideoFrameFormat::ColorSpace_AdobeRgb;
case AVCOL_SPC_BT709:
return QVideoFrameFormat::ColorSpace_BT709;
case AVCOL_SPC_BT470BG: // BT601
case AVCOL_SPC_SMPTE170M: // Also BT601
return QVideoFrameFormat::ColorSpace_BT601;
case AVCOL_SPC_BT2020_NCL: // Non constant luminence
case AVCOL_SPC_BT2020_CL: // Constant luminence
return QVideoFrameFormat::ColorSpace_BT2020;
}
}
static QVideoFrameFormat::ColorTransfer colorTransfer(const AVFrame *frame)
{
switch (frame->color_trc) {
case AVCOL_TRC_BT709:
// The following three cases have transfer characteristics identical to BT709
case AVCOL_TRC_BT1361_ECG:
case AVCOL_TRC_BT2020_10:
case AVCOL_TRC_BT2020_12:
case AVCOL_TRC_SMPTE240M: // almost identical to bt709
return QVideoFrameFormat::ColorTransfer_BT709;
case AVCOL_TRC_GAMMA22:
case AVCOL_TRC_SMPTE428: // No idea, let's hope for the best...
case AVCOL_TRC_IEC61966_2_1: // sRGB, close enough to 2.2...
case AVCOL_TRC_IEC61966_2_4: // not quite, but probably close enough
return QVideoFrameFormat::ColorTransfer_Gamma22;
case AVCOL_TRC_GAMMA28:
return QVideoFrameFormat::ColorTransfer_Gamma28;
case AVCOL_TRC_SMPTE170M:
return QVideoFrameFormat::ColorTransfer_BT601;
case AVCOL_TRC_LINEAR:
return QVideoFrameFormat::ColorTransfer_Linear;
case AVCOL_TRC_SMPTE2084:
return QVideoFrameFormat::ColorTransfer_ST2084;
case AVCOL_TRC_ARIB_STD_B67:
return QVideoFrameFormat::ColorTransfer_STD_B67;
default:
break;
}
return QVideoFrameFormat::ColorTransfer_Unknown;
}
static QVideoFrameFormat::ColorRange colorRange(const AVFrame *frame)
{
switch (frame->color_range) {
case AVCOL_RANGE_MPEG:
return QVideoFrameFormat::ColorRange_Video;
case AVCOL_RANGE_JPEG:
return QVideoFrameFormat::ColorRange_Full;
default:
return QVideoFrameFormat::ColorRange_Unknown;
}
}
static float maxNits(const AVFrame *frame)
{
float maxNits = -1;
for (int i = 0; i < frame->nb_side_data; ++i) {
AVFrameSideData *sd = frame->side_data[i];
// TODO: Longer term we might want to also support HDR10+ dynamic metadata
if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) {
auto data = reinterpret_cast<AVMasteringDisplayMetadata *>(sd->data);
auto b = data->max_luminance;
auto maybeLum = b.den != 0 ? 10'000.0 * qreal(b.num) / qreal(b.den) : std::optional<qreal>{};
if (maybeLum)
maxNits = float(maybeLum.value());
}
}
return maxNits;
}
#endif
private:
QAVVideoFrame m_frame;
QVideoFrameFormat::PixelFormat m_pixelFormat = QVideoFrameFormat::Format_Invalid;
QVideoFrame::MapMode m_mode = QVideoFrame::NotMapped;
QVariant m_textures;
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
QRhi *m_rhi = nullptr;
#endif
};
#endif // #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QAVVideoFrame::operator QVideoFrame() const
{
QAVVideoFrame result = *this;
if (!result)
return QVideoFrame();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
using VideoFrame = QVideoFrame;
#else
using VideoFrame = QVideoFrameFormat;
#endif
VideoFrame::PixelFormat format = VideoFrame::Format_Invalid;
switch (frame()->format) {
case AV_PIX_FMT_RGB32:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
format = VideoFrame::Format_RGB32;
#else
format = QVideoFrameFormat::Format_BGRA8888;
#endif
break;
case AV_PIX_FMT_YUV420P:
format = VideoFrame::Format_YUV420P;
break;
case AV_PIX_FMT_YUV444P:
case AV_PIX_FMT_YUV422P:
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
result = convertTo(AV_PIX_FMT_YUV420P);
format = VideoFrame::Format_YUV420P;
#else
format = VideoFrame::Format_YUV422P;
#endif
break;
case AV_PIX_FMT_VAAPI:
case AV_PIX_FMT_VDPAU:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
format = VideoFrame::Format_BGRA32;
#else
format = QVideoFrameFormat::Format_RGBA8888;
#endif
break;
case AV_PIX_FMT_D3D11:
case AV_PIX_FMT_VIDEOTOOLBOX:
case AV_PIX_FMT_NV12:
format = VideoFrame::Format_NV12;
break;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
case AV_PIX_FMT_MEDIACODEC:
format = VideoFrame::Format_SamplerExternalOES;
break;
#endif
default:
// TODO: Add more supported formats instead of converting
result = convertTo(AV_PIX_FMT_YUV420P);
format = VideoFrame::Format_YUV420P;
break;
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
using HandleType = QAbstractVideoBuffer::HandleType;
#else
using HandleType = QVideoFrame::HandleType;
#endif
HandleType type = HandleType::NoHandle;
switch (handleType()) {
case GLTextureHandle:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
type = HandleType::GLTextureHandle;
#else
type = HandleType::RhiTextureHandle;
#endif
break;
case MTLTextureHandle:
case D3D11Texture2DHandle:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
type = HandleType::RhiTextureHandle;
#endif
break;
default:
break;
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return QVideoFrame(new PlanarVideoBuffer(result, type), size(), format);
#else
QVideoFrameFormat videoFormat(size(), format);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
videoFormat.setColorSpace(PlanarVideoBuffer::colorSpace(frame()));
videoFormat.setColorTransfer(PlanarVideoBuffer::colorTransfer(frame()));
videoFormat.setColorRange(PlanarVideoBuffer::colorRange(frame()));
videoFormat.setMaxLuminance(PlanarVideoBuffer::maxNits(frame()));
#endif
return QVideoFrame(new PlanarVideoBuffer(result, format, type), videoFormat);
#endif
}
#endif // #ifdef QT_AVPLAYER_MULTIMEDIA
QT_END_NAMESPACE

View File

@ -0,0 +1,80 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVFVIDEORAME_H
#define QAVFVIDEORAME_H
#include <QtAVPlayer/qavframe.h>
#include <QVariant>
#ifdef QT_AVPLAYER_MULTIMEDIA
#include <QVideoFrame>
#endif
extern "C" {
#include <libavutil/frame.h>
}
QT_BEGIN_NAMESPACE
class QAVVideoFramePrivate;
class QAVCodec;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
class QRhi;
#endif
class QAVVideoFrame : public QAVFrame
{
public:
enum HandleType
{
NoHandle,
GLTextureHandle,
MTLTextureHandle,
D3D11Texture2DHandle
};
QAVVideoFrame();
QAVVideoFrame(const QAVFrame &other);
QAVVideoFrame(const QAVVideoFrame &other);
QAVVideoFrame(const QSize &size, AVPixelFormat fmt);
QAVVideoFrame &operator=(const QAVFrame &other);
QAVVideoFrame &operator=(const QAVVideoFrame &other);
QSize size() const;
struct MapData
{
int size = 0;
int bytesPerLine[4] = {0};
uchar *data[4] = {nullptr};
AVPixelFormat format = AV_PIX_FMT_NONE;
};
MapData map() const;
HandleType handleType() const;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QVariant handle(QRhi *rhi = nullptr) const;
#else
QVariant handle() const;
#endif
AVPixelFormat format() const;
QString formatName() const;
QAVVideoFrame convertTo(AVPixelFormat fmt) const;
#ifdef QT_AVPLAYER_MULTIMEDIA
operator QVideoFrame() const;
#endif
protected:
Q_DECLARE_PRIVATE(QAVVideoFrame)
};
Q_DECLARE_METATYPE(QAVVideoFrame)
Q_DECLARE_METATYPE(AVPixelFormat)
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,116 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavframe.h"
#include "qavvideoinputfilter_p.h"
#include "qavinoutfilter_p_p.h"
#include "qavdemuxer_p.h"
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/bprint.h>
}
QT_BEGIN_NAMESPACE
class QAVVideoInputFilterPrivate : public QAVInOutFilterPrivate
{
public:
QAVVideoInputFilterPrivate(QAVInOutFilter *q)
: QAVInOutFilterPrivate(q)
{ }
AVPixelFormat format = AV_PIX_FMT_NONE;
int width = 0;
int height = 0;
AVRational sample_aspect_ratio{};
AVRational time_base{};
AVRational frame_rate{};
};
QAVVideoInputFilter::QAVVideoInputFilter()
: QAVInOutFilter(*new QAVVideoInputFilterPrivate(this))
{
}
QAVVideoInputFilter::QAVVideoInputFilter(const QAVFrame &frame)
: QAVVideoInputFilter()
{
Q_D(QAVVideoInputFilter);
const auto & frm = frame.frame();
const auto & stream = frame.stream().stream();
d->format = frm->format != AV_PIX_FMT_NONE ? AVPixelFormat(frm->format) : AVPixelFormat(stream->codecpar->format);
d->width = frm->width ? frm->width : stream->codecpar->width;
d->height = frm->height ? frm->height : stream->codecpar->height;
d->sample_aspect_ratio = frm->sample_aspect_ratio.num && frm->sample_aspect_ratio.den ? frm->sample_aspect_ratio : stream->codecpar->sample_aspect_ratio;
d->time_base = stream->time_base;
d->frame_rate = stream->avg_frame_rate;
}
QAVVideoInputFilter::QAVVideoInputFilter(const QAVVideoInputFilter &other)
: QAVVideoInputFilter()
{
*this = other;
}
QAVVideoInputFilter::~QAVVideoInputFilter() = default;
QAVVideoInputFilter &QAVVideoInputFilter::operator=(const QAVVideoInputFilter &other)
{
Q_D(QAVVideoInputFilter);
QAVInOutFilter::operator=(other);
d->format = other.d_func()->format;
d->width = other.d_func()->width;
d->height = other.d_func()->height;
d->sample_aspect_ratio = other.d_func()->sample_aspect_ratio;
d->time_base = other.d_func()->time_base;
d->frame_rate = other.d_func()->frame_rate;
return *this;
}
int QAVVideoInputFilter::configure(AVFilterGraph *graph, AVFilterInOut *in)
{
QAVInOutFilter::configure(graph, in);
Q_D(QAVVideoInputFilter);
AVBPrint args;
av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprintf(&args,
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:"
"pixel_aspect=%d/%d",
d->width, d->height, d->format,
d->time_base.num, d->time_base.den,
d->sample_aspect_ratio.num, qMax(d->sample_aspect_ratio.den, 1));
if (d->frame_rate.num && d->frame_rate.den)
av_bprintf(&args, ":frame_rate=%d/%d", d->frame_rate.num, d->frame_rate.den);
static int index = 0;
char name[255];
snprintf(name, sizeof(name), "buffer_%d", index++);
int ret = avfilter_graph_create_filter(&d->ctx,
avfilter_get_by_name("buffer"),
name, args.str, nullptr, graph);
if (ret < 0)
return ret;
return avfilter_link(d->ctx, 0, in->filter_ctx, in->pad_idx);
}
bool QAVVideoInputFilter::supports(const QAVFrame &frame) const
{
Q_D(const QAVVideoInputFilter);
if (!frame)
return true;
const auto & frm = frame.frame();
return d->width == frm->width
&& d->height == frm->height
&& d->format == frm->format;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,46 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOINPUTFILTER_P_H
#define QAVVIDEOINPUTFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavinoutfilter_p.h"
QT_BEGIN_NAMESPACE
class QAVFrame;
class QAVVideoInputFilterPrivate;
class QAVVideoInputFilter : public QAVInOutFilter
{
public:
QAVVideoInputFilter(const QAVFrame &frame);
QAVVideoInputFilter(const QAVVideoInputFilter &other);
~QAVVideoInputFilter();
QAVVideoInputFilter &operator=(const QAVVideoInputFilter &other);
int configure(AVFilterGraph *graph, AVFilterInOut *in) override;
bool supports(const QAVFrame &frame) const;
protected:
QAVVideoInputFilter();
Q_DECLARE_PRIVATE(QAVVideoInputFilter)
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,42 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#include "qavvideooutputfilter_p.h"
#include "qavinoutfilter_p_p.h"
#include <QDebug>
extern "C" {
#include <libavfilter/buffersink.h>
}
QT_BEGIN_NAMESPACE
QAVVideoOutputFilter::QAVVideoOutputFilter()
: QAVInOutFilter(*new QAVInOutFilterPrivate(this))
{
}
QAVVideoOutputFilter::~QAVVideoOutputFilter() = default;
int QAVVideoOutputFilter::configure(AVFilterGraph *graph, AVFilterInOut *out)
{
QAVInOutFilter::configure(graph, out);
Q_D(QAVInOutFilter);
static int index = 0;
char name[255];
snprintf(name, sizeof(name), "buffersink_%d", index++);
int ret = avfilter_graph_create_filter(&d->ctx,
avfilter_get_by_name("buffersink"),
name, nullptr, nullptr, graph);
if (ret < 0)
return ret;
return avfilter_link(out->filter_ctx, out->pad_idx, d->ctx, 0);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,38 @@
/*********************************************************
* Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QAVVIDEOOUTPUTFILTER_P_H
#define QAVVIDEOOUTPUTFILTER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qavinoutfilter_p.h"
QT_BEGIN_NAMESPACE
class QAVVideoOutputFilterPrivate;
class QAVVideoOutputFilter : public QAVInOutFilter
{
public:
QAVVideoOutputFilter();
~QAVVideoOutputFilter();
int configure(AVFilterGraph *graph, AVFilterInOut *out) override;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,13 @@
/*********************************************************
* Copyright (C) 2020, Val Doroshchuk <valbok@gmail.com> *
* *
* This file is part of QtAVPlayer. *
* Free Qt Media Player based on FFmpeg. *
*********************************************************/
#ifndef QTAVPLAYERGLOBAL_H
#define QTAVPLAYERGLOBAL_H
#include <QtCore/qglobal.h>
#endif

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# JustVideo #
JustVideo is a playlist based video player that also works as a font-end
video surveillance player for JustMotion.
In surveillance mode it automatically detects and plays video footage of any
JustMotion based mounted filesystem. If not playing from JustMotion it will
playlist all videos found in the opened folder.
# Playback Support #
JustVideo uses libav so it supports a very wide verity of audio/video codecs
depending on your installation. Run the following cmd to get a full list:
```
ffmpeg -codecs
```
### Build/Install ###
This application is currently only compatible with a Linux based operating
systems that are capable of installing python3 and the QT API (QT6.X.X or
better).
```
./build.py <--run this first
./install.py <--run this next
```
```
note 1: the build script will search for the QT api installed in your
system. if not found, it will ask you where it is. either way
it is recommended to install the QT API before running this
script.
note 2: both scripts assume python3 is already installed.
```

262
build.py Executable file
View File

@ -0,0 +1,262 @@
#!/usr/bin/python3
import os
import re
import subprocess
import shutil
import sys
import platform
def get_app_target(text):
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
def get_app_ver(text):
return re.search(r'(APP_VERSION) +(\"(.*?)\")', text).group(3)
def get_app_name(text):
return re.search(r'(APP_NAME) +(\"(.*?)\")', text).group(3)
def get_qt_path():
try:
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
except:
print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.")
return input("Please enter the QT bin path (leave blank to cancel the build): ")
def get_qt_from_cli():
for arg in sys.argv:
if arg == "-qt_dir":
index = sys.argv.index(arg)
try:
return sys.argv[index + 1]
except:
return ""
return ""
def get_ver_header():
current_dir = os.path.dirname(__file__)
if current_dir == "":
return "src" + os.sep + "common.h"
else:
return current_dir + os.sep + "src" + os.sep + "common.h"
def get_nearest_subdir(path, sub_name):
dir_list = os.listdir(path)
ret = ""
for entry in dir_list:
if sub_name in entry:
ret = entry
break
return ret
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
if os.path.exists(dst) and os.path.isdir(dst):
shutil.rmtree(dst)
try:
# ignore errors thrown by shutil.copytree()
# it's likely not actually failing to copy
# the directory but still throws errors if
# it fails to apply the same file stats as
# the source. this type of error can be
# ignored.
shutil.copytree(src, dst)
except:
pass
elif os.path.exists(src):
shutil.copyfile(src, dst)
else:
print("wrn: " + src + " does not exists. skipping.")
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
if not os.path.exists("app_dir/platforms"):
os.makedirs("app_dir/platforms")
if not os.path.exists("app_dir/xcbglintegrations"):
os.makedirs("app_dir/xcbglintegrations")
if not os.path.exists("app_dir/multimedia"):
os.makedirs("app_dir/multimedia")
if not os.path.exists("app_dir/platformthemes"):
os.makedirs("app_dir/platformthemes")
if not os.path.exists("app_dir/lib"):
os.makedirs("app_dir/lib")
if not os.path.exists("app_dir/icons"):
os.makedirs("app_dir/icons")
verbose_copy(qt_bin + "/../plugins/platforms", "app_dir/platforms")
verbose_copy(qt_bin + "/../plugins/xcbglintegrations", "app_dir/xcbglintegrations")
verbose_copy(qt_bin + "/../plugins/multimedia", "app_dir/multimedia")
verbose_copy(qt_bin + "/../plugins/platformthemes", "app_dir/platformthemes")
verbose_copy("build/" + app_target, "app_dir/" + app_target)
verbose_copy("icons/main.svg", "app_dir/icons/scalable.svg")
img_sizes = [8, 16, 22, 24, 28, 32, 36, 42, 48, 64, 72, 96, 128, 192, 256, 512]
for i in img_sizes:
subprocess.run(["inkscape", "-w", str(i), "-h", str(i), "icons/main.svg", "-o", "app_dir/icons/" + str(i) + ".png"])
shutil.copyfile("build/" + app_target, "/tmp/" + app_target)
# copying the executable file from the build folder to
# temp bypasses any -noexe retrictions a linux file
# system may have. there is a chance temp is also
# restricted in this way but that kind of config is
# rare. ldd will not run correctly with -noexe
# enabled.
lines = str(subprocess.check_output(["ldd", "/tmp/" + app_target]), 'utf-8').split("\n")
os.remove("/tmp/" + app_target)
for line in lines:
if " => " in line and "libc" not in line:
#if ("libQt" in line) or ("libicu" in line) or ("libGL.so" in line) or ("libpcre16.so" in line) or ("libpcre.so" in line):
if " (0x0" in line:
start_index = line.index("> ") + 2
end_index = line.index(" (0x0")
src_file = line[start_index:end_index]
file_name = os.path.basename(src_file)
verbose_copy(src_file, "app_dir/lib/" + file_name)
if "/usr/lib/x86_64-linux-gnu/qt6/bin" == qt_bin:
verbose_copy(qt_bin + "/../../libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
verbose_copy(qt_bin + "/../../libQt6XcbQpa.so.6", "app_dir/lib/libQt6XcbQpa.so.6")
else:
verbose_copy(qt_bin + "/../lib/libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
verbose_copy(qt_bin + "/../lib/libQt6XcbQpa.so.6", "app_dir/lib/libQt6XcbQpa.so.6")
verbose_copy(qt_bin + "/../lib/libQt6OpenGL.so.6", "app_dir/lib/libQt6OpenGL.so.6")
verbose_copy("templates/linux_run_script.sh", "app_dir/" + app_target + ".sh")
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.sh")
verbose_copy("templates/linux_icon.desktop", "app_dir/" + app_target + ".desktop")
complete(app_ver, app_target)
def complete(app_ver, app_target):
print("Build complete for version: " + app_ver)
print("You can now run the install.py script to install onto this machine or create an installer.")
def get_like_distro():
info = platform.freedesktop_os_release()
ids = [info["ID"]]
if "ID_LIKE" in info:
# ids are space separated and ordered by precedence
ids.extend(info["ID_LIKE"].split())
return ids
def list_installed_packages():
like_distro = get_like_distro()
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
return str(subprocess.check_output(["apt", "list", "--installed"]), 'utf-8')
elif ("fedora" in like_distro) or ("rhel" in like_distro):
return str(subprocess.check_output(["dnf", "list", "installed"]), 'utf-8')
elif ("arch" in like_distro):
return str(subprocess.check_output(["pacman", "-Q"]), 'utf-8')
else:
print("Warning: unable to determine a package manager for this platform.")
return []
def list_of_words_in_text(list_of_words, text_body):
for word in list_of_words:
if not word in text_body:
return False
return True
def platform_setup():
ins_packages = list_installed_packages()
like_distro = get_like_distro()
dep_pkgs_a = ["pkg-config"]
dep_pkgs_b = ["ffmpeg", "libavcodec-dev", "libavformat-dev", "libavfilter-dev", "libavdevice-dev", "libilmbase-dev", "libvdpau-dev", "libxkbcommon-dev", "libgl-dev", "libxcb-cursor0", "inkscape"]
if not list_of_words_in_text(dep_pkgs_a, ins_packages) or not list_of_words_in_text(dep_pkgs_b, ins_packages):
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
subprocess.run(["sudo", "apt", "update", "-y"])
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_a)
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_b)
elif ("fedora" in like_distro) or ("rhel" in like_distro):
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_a)
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_b)
elif ("arch" in like_distro):
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_b)
def main():
platform_setup()
with open(get_ver_header()) as file:
text = file.read()
app_target = get_app_target(text)
app_ver = get_app_ver(text)
app_name = get_app_name(text)
qt_bin = get_qt_from_cli()
if qt_bin == "":
qt_bin = get_qt_path()
if qt_bin != "":
print("app_target = " + app_target)
print("app_version = " + app_ver)
print("app_name = " + app_name)
print("qt_bin = " + qt_bin)
cd()
result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"])
if result.returncode == 0:
result = subprocess.run(["make"])
if result.returncode == 0:
if os.path.exists("app_dir"):
shutil.rmtree("app_dir")
os.makedirs("app_dir")
with open("app_dir" + os.sep + "info.txt", "w") as info_file:
info_file.write(app_target + "\n")
info_file.write(app_ver + "\n")
info_file.write(app_name + "\n")
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
if __name__ == "__main__":
main()

BIN
icons/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Some files were not shown because too many files have changed in this diff Show More