v1.0
-first ccommit for stable version of JustVideo
This commit is contained in:
commit
ef90c77e8a
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal 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
56
JustVideo.pro
Normal 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
621
LICENSE.md
Normal 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
21
QtAVPlayer/LICENSE
Normal 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
218
QtAVPlayer/README.md
Normal 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
|
||||
|
||||
|
13
QtAVPlayer/examples/extract_frames/extract_frames.pro
Normal file
13
QtAVPlayer/examples/extract_frames/extract_frames.pro
Normal 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
|
30
QtAVPlayer/examples/extract_frames/main.cpp
Normal file
30
QtAVPlayer/examples/extract_frames/main.cpp
Normal 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();
|
||||
}
|
||||
|
204
QtAVPlayer/examples/qml_video/main.cpp
Normal file
204
QtAVPlayer/examples/qml_video/main.cpp
Normal 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
|
80
QtAVPlayer/examples/qml_video/main.qml
Normal file
80
QtAVPlayer/examples/qml_video/main.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
80
QtAVPlayer/examples/qml_video/main_qt6.qml
Normal file
80
QtAVPlayer/examples/qml_video/main_qt6.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
6
QtAVPlayer/examples/qml_video/qml.qrc
Normal file
6
QtAVPlayer/examples/qml_video/qml.qrc
Normal 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>
|
6
QtAVPlayer/examples/qml_video/qml_qt6.qrc
Normal file
6
QtAVPlayer/examples/qml_video/qml_qt6.qrc
Normal 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>
|
25
QtAVPlayer/examples/qml_video/qml_video.pro
Normal file
25
QtAVPlayer/examples/qml_video/qml_video.pro
Normal 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
|
136
QtAVPlayer/examples/widget_video/main.cpp
Normal file
136
QtAVPlayer/examples/widget_video/main.cpp
Normal 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();
|
||||
}
|
||||
|
14
QtAVPlayer/examples/widget_video/widget_video.pro
Normal file
14
QtAVPlayer/examples/widget_video/widget_video.pro
Normal 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
|
131
QtAVPlayer/src/QtAVPlayer/QtAVPlayer.pri
Normal file
131
QtAVPlayer/src/QtAVPlayer/QtAVPlayer.pri
Normal 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
|
74
QtAVPlayer/src/QtAVPlayer/qavandroidsurfacetexture.cpp
Normal file
74
QtAVPlayer/src/QtAVPlayer/qavandroidsurfacetexture.cpp
Normal 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
|
61
QtAVPlayer/src/QtAVPlayer/qavandroidsurfacetexture_p.h
Normal file
61
QtAVPlayer/src/QtAVPlayer/qavandroidsurfacetexture_p.h
Normal 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
|
49
QtAVPlayer/src/QtAVPlayer/qavaudiocodec.cpp
Normal file
49
QtAVPlayer/src/QtAVPlayer/qavaudiocodec.cpp
Normal 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
|
39
QtAVPlayer/src/QtAVPlayer/qavaudiocodec_p.h
Normal file
39
QtAVPlayer/src/QtAVPlayer/qavaudiocodec_p.h
Normal 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
|
155
QtAVPlayer/src/QtAVPlayer/qavaudiofilter.cpp
Normal file
155
QtAVPlayer/src/QtAVPlayer/qavaudiofilter.cpp
Normal 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
|
53
QtAVPlayer/src/QtAVPlayer/qavaudiofilter_p.h
Normal file
53
QtAVPlayer/src/QtAVPlayer/qavaudiofilter_p.h
Normal 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
|
61
QtAVPlayer/src/QtAVPlayer/qavaudioformat.h
Normal file
61
QtAVPlayer/src/QtAVPlayer/qavaudioformat.h
Normal 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
|
216
QtAVPlayer/src/QtAVPlayer/qavaudioframe.cpp
Normal file
216
QtAVPlayer/src/QtAVPlayer/qavaudioframe.cpp
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavaudioframe.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavaudioframe.h
Normal 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
|
127
QtAVPlayer/src/QtAVPlayer/qavaudioinputfilter.cpp
Normal file
127
QtAVPlayer/src/QtAVPlayer/qavaudioinputfilter.cpp
Normal 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
|
45
QtAVPlayer/src/QtAVPlayer/qavaudioinputfilter_p.h
Normal file
45
QtAVPlayer/src/QtAVPlayer/qavaudioinputfilter_p.h
Normal 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
|
305
QtAVPlayer/src/QtAVPlayer/qavaudiooutput.cpp
Normal file
305
QtAVPlayer/src/QtAVPlayer/qavaudiooutput.cpp
Normal 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
|
47
QtAVPlayer/src/QtAVPlayer/qavaudiooutput.h
Normal file
47
QtAVPlayer/src/QtAVPlayer/qavaudiooutput.h
Normal 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
|
43
QtAVPlayer/src/QtAVPlayer/qavaudiooutputfilter.cpp
Normal file
43
QtAVPlayer/src/QtAVPlayer/qavaudiooutputfilter.cpp
Normal 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
|
38
QtAVPlayer/src/QtAVPlayer/qavaudiooutputfilter_p.h
Normal file
38
QtAVPlayer/src/QtAVPlayer/qavaudiooutputfilter_p.h
Normal 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
|
99
QtAVPlayer/src/QtAVPlayer/qavcodec.cpp
Normal file
99
QtAVPlayer/src/QtAVPlayer/qavcodec.cpp
Normal 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
|
62
QtAVPlayer/src/QtAVPlayer/qavcodec_p.h
Normal file
62
QtAVPlayer/src/QtAVPlayer/qavcodec_p.h
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavcodec_p_p.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavcodec_p_p.h
Normal 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
|
876
QtAVPlayer/src/QtAVPlayer/qavdemuxer.cpp
Normal file
876
QtAVPlayer/src/QtAVPlayer/qavdemuxer.cpp
Normal 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> ¤tStreams)
|
||||
{
|
||||
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
|
114
QtAVPlayer/src/QtAVPlayer/qavdemuxer_p.h
Normal file
114
QtAVPlayer/src/QtAVPlayer/qavdemuxer_p.h
Normal 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
|
31
QtAVPlayer/src/QtAVPlayer/qavfilter.cpp
Normal file
31
QtAVPlayer/src/QtAVPlayer/qavfilter.cpp
Normal 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
|
54
QtAVPlayer/src/QtAVPlayer/qavfilter_p.h
Normal file
54
QtAVPlayer/src/QtAVPlayer/qavfilter_p.h
Normal 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
|
48
QtAVPlayer/src/QtAVPlayer/qavfilter_p_p.h
Normal file
48
QtAVPlayer/src/QtAVPlayer/qavfilter_p_p.h
Normal 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
|
186
QtAVPlayer/src/QtAVPlayer/qavfiltergraph.cpp
Normal file
186
QtAVPlayer/src/QtAVPlayer/qavfiltergraph.cpp
Normal 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
|
64
QtAVPlayer/src/QtAVPlayer/qavfiltergraph_p.h
Normal file
64
QtAVPlayer/src/QtAVPlayer/qavfiltergraph_p.h
Normal 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
|
220
QtAVPlayer/src/QtAVPlayer/qavfilters.cpp
Normal file
220
QtAVPlayer/src/QtAVPlayer/qavfilters.cpp
Normal 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
|
65
QtAVPlayer/src/QtAVPlayer/qavfilters_p.h
Normal file
65
QtAVPlayer/src/QtAVPlayer/qavfilters_p.h
Normal 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
|
121
QtAVPlayer/src/QtAVPlayer/qavframe.cpp
Normal file
121
QtAVPlayer/src/QtAVPlayer/qavframe.cpp
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavframe.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavframe.h
Normal 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
|
48
QtAVPlayer/src/QtAVPlayer/qavframe_p.h
Normal file
48
QtAVPlayer/src/QtAVPlayer/qavframe_p.h
Normal 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
|
46
QtAVPlayer/src/QtAVPlayer/qavframecodec.cpp
Normal file
46
QtAVPlayer/src/QtAVPlayer/qavframecodec.cpp
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavframecodec_p.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavframecodec_p.h
Normal 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
|
225
QtAVPlayer/src/QtAVPlayer/qavhwdevice_d3d11.cpp
Normal file
225
QtAVPlayer/src/QtAVPlayer/qavhwdevice_d3d11.cpp
Normal 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
|
44
QtAVPlayer/src/QtAVPlayer/qavhwdevice_d3d11_p.h
Normal file
44
QtAVPlayer/src/QtAVPlayer/qavhwdevice_d3d11_p.h
Normal 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
|
115
QtAVPlayer/src/QtAVPlayer/qavhwdevice_mediacodec.cpp
Normal file
115
QtAVPlayer/src/QtAVPlayer/qavhwdevice_mediacodec.cpp
Normal 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
|
47
QtAVPlayer/src/QtAVPlayer/qavhwdevice_mediacodec_p.h
Normal file
47
QtAVPlayer/src/QtAVPlayer/qavhwdevice_mediacodec_p.h
Normal 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
|
51
QtAVPlayer/src/QtAVPlayer/qavhwdevice_p.h
Normal file
51
QtAVPlayer/src/QtAVPlayer/qavhwdevice_p.h
Normal 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
|
163
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_drm_egl.cpp
Normal file
163
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_drm_egl.cpp
Normal 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
|
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_drm_egl_p.h
Normal file
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_drm_egl_p.h
Normal 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
|
180
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_x11_glx.cpp
Normal file
180
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_x11_glx.cpp
Normal 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
|
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_x11_glx_p.h
Normal file
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vaapi_x11_glx_p.h
Normal 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
|
235
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vdpau.cpp
Normal file
235
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vdpau.cpp
Normal 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
|
42
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vdpau_p.h
Normal file
42
QtAVPlayer/src/QtAVPlayer/qavhwdevice_vdpau_p.h
Normal 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
|
112
QtAVPlayer/src/QtAVPlayer/qavhwdevice_videotoolbox.mm
Normal file
112
QtAVPlayer/src/QtAVPlayer/qavhwdevice_videotoolbox.mm
Normal 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
|
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_videotoolbox_p.h
Normal file
46
QtAVPlayer/src/QtAVPlayer/qavhwdevice_videotoolbox_p.h
Normal 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
|
64
QtAVPlayer/src/QtAVPlayer/qavinoutfilter.cpp
Normal file
64
QtAVPlayer/src/QtAVPlayer/qavinoutfilter.cpp
Normal 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
|
50
QtAVPlayer/src/QtAVPlayer/qavinoutfilter_p.h
Normal file
50
QtAVPlayer/src/QtAVPlayer/qavinoutfilter_p.h
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavinoutfilter_p_p.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavinoutfilter_p_p.h
Normal 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
|
173
QtAVPlayer/src/QtAVPlayer/qaviodevice.cpp
Normal file
173
QtAVPlayer/src/QtAVPlayer/qaviodevice.cpp
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qaviodevice.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qaviodevice.h
Normal 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
|
108
QtAVPlayer/src/QtAVPlayer/qavpacket.cpp
Normal file
108
QtAVPlayer/src/QtAVPlayer/qavpacket.cpp
Normal 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
|
58
QtAVPlayer/src/QtAVPlayer/qavpacket_p.h
Normal file
58
QtAVPlayer/src/QtAVPlayer/qavpacket_p.h
Normal 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
|
275
QtAVPlayer/src/QtAVPlayer/qavpacketqueue_p.h
Normal file
275
QtAVPlayer/src/QtAVPlayer/qavpacketqueue_p.h
Normal 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
|
1399
QtAVPlayer/src/QtAVPlayer/qavplayer.cpp
Normal file
1399
QtAVPlayer/src/QtAVPlayer/qavplayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
162
QtAVPlayer/src/QtAVPlayer/qavplayer.h
Normal file
162
QtAVPlayer/src/QtAVPlayer/qavplayer.h
Normal 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
|
285
QtAVPlayer/src/QtAVPlayer/qavstream.cpp
Normal file
285
QtAVPlayer/src/QtAVPlayer/qavstream.cpp
Normal 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
|
83
QtAVPlayer/src/QtAVPlayer/qavstream.h
Normal file
83
QtAVPlayer/src/QtAVPlayer/qavstream.h
Normal 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
|
81
QtAVPlayer/src/QtAVPlayer/qavstreamframe.cpp
Normal file
81
QtAVPlayer/src/QtAVPlayer/qavstreamframe.cpp
Normal 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
|
45
QtAVPlayer/src/QtAVPlayer/qavstreamframe.h
Normal file
45
QtAVPlayer/src/QtAVPlayer/qavstreamframe.h
Normal 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
|
41
QtAVPlayer/src/QtAVPlayer/qavstreamframe_p.h
Normal file
41
QtAVPlayer/src/QtAVPlayer/qavstreamframe_p.h
Normal 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
|
60
QtAVPlayer/src/QtAVPlayer/qavsubtitlecodec.cpp
Normal file
60
QtAVPlayer/src/QtAVPlayer/qavsubtitlecodec.cpp
Normal 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
|
43
QtAVPlayer/src/QtAVPlayer/qavsubtitlecodec_p.h
Normal file
43
QtAVPlayer/src/QtAVPlayer/qavsubtitlecodec_p.h
Normal 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
|
83
QtAVPlayer/src/QtAVPlayer/qavsubtitleframe.cpp
Normal file
83
QtAVPlayer/src/QtAVPlayer/qavsubtitleframe.cpp
Normal 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
|
35
QtAVPlayer/src/QtAVPlayer/qavsubtitleframe.h
Normal file
35
QtAVPlayer/src/QtAVPlayer/qavsubtitleframe.h
Normal 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
|
38
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_cpu.cpp
Normal file
38
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_cpu.cpp
Normal 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
|
38
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_cpu_p.h
Normal file
38
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_cpu_p.h
Normal 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
|
34
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_gpu.cpp
Normal file
34
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_gpu.cpp
Normal 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
|
42
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_gpu_p.h
Normal file
42
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_gpu_p.h
Normal 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
|
45
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_p.h
Normal file
45
QtAVPlayer/src/QtAVPlayer/qavvideobuffer_p.h
Normal 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
|
147
QtAVPlayer/src/QtAVPlayer/qavvideocodec.cpp
Normal file
147
QtAVPlayer/src/QtAVPlayer/qavvideocodec.cpp
Normal 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
|
44
QtAVPlayer/src/QtAVPlayer/qavvideocodec_p.h
Normal file
44
QtAVPlayer/src/QtAVPlayer/qavvideocodec_p.h
Normal 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
|
149
QtAVPlayer/src/QtAVPlayer/qavvideofilter.cpp
Normal file
149
QtAVPlayer/src/QtAVPlayer/qavvideofilter.cpp
Normal 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
|
52
QtAVPlayer/src/QtAVPlayer/qavvideofilter_p.h
Normal file
52
QtAVPlayer/src/QtAVPlayer/qavvideofilter_p.h
Normal 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
|
475
QtAVPlayer/src/QtAVPlayer/qavvideoframe.cpp
Normal file
475
QtAVPlayer/src/QtAVPlayer/qavvideoframe.cpp
Normal 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
|
80
QtAVPlayer/src/QtAVPlayer/qavvideoframe.h
Normal file
80
QtAVPlayer/src/QtAVPlayer/qavvideoframe.h
Normal 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
|
116
QtAVPlayer/src/QtAVPlayer/qavvideoinputfilter.cpp
Normal file
116
QtAVPlayer/src/QtAVPlayer/qavvideoinputfilter.cpp
Normal 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
|
46
QtAVPlayer/src/QtAVPlayer/qavvideoinputfilter_p.h
Normal file
46
QtAVPlayer/src/QtAVPlayer/qavvideoinputfilter_p.h
Normal 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
|
42
QtAVPlayer/src/QtAVPlayer/qavvideooutputfilter.cpp
Normal file
42
QtAVPlayer/src/QtAVPlayer/qavvideooutputfilter.cpp
Normal 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
|
38
QtAVPlayer/src/QtAVPlayer/qavvideooutputfilter_p.h
Normal file
38
QtAVPlayer/src/QtAVPlayer/qavvideooutputfilter_p.h
Normal 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
|
13
QtAVPlayer/src/QtAVPlayer/qtavplayerglobal.h
Normal file
13
QtAVPlayer/src/QtAVPlayer/qtavplayerglobal.h
Normal 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
35
README.md
Normal 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
262
build.py
Executable 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
BIN
icons/check.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
icons/error.png
Normal file
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
Loading…
Reference in New Issue
Block a user