Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d18f7bb8cd | ||
|
|
0bb67b1055 | ||
|
|
0c2cb69597 | ||
|
|
3faf1fb0a4 | ||
|
|
71c25df5e6 | ||
|
|
8a4f2f0ac0 | ||
|
|
0fe4a492c4 | ||
|
|
1da53248ab | ||
|
|
d0c87dd579 | ||
|
|
b19b0869d6 | ||
|
|
9cb7f87b86 | ||
|
|
5771f7abf4 | ||
|
|
62f0677614 | ||
|
|
8cf5799ab1 | ||
|
|
f589a3bd78 | ||
|
|
9a28088e74 | ||
|
|
d280b3db93 | ||
|
|
181b071431 | ||
|
|
650d4e3401 | ||
|
|
7897f79217 | ||
|
|
8fe4aa94ee | ||
|
|
957e9a37db | ||
|
|
449d9725c9 | ||
|
|
9f0e358827 | ||
|
|
96462040ec | ||
|
|
93d40ac480 | ||
|
|
a9571fcc0f | ||
|
|
a46500a2c8 | ||
|
|
cc79d48d22 | ||
|
|
7baa18d5f8 | ||
|
|
1d47473a7b | ||
|
|
136cf2b770 | ||
|
|
8404a354e5 | ||
|
|
7684fda6ac | ||
|
|
45bae90f4d | ||
|
|
f40717a664 | ||
|
|
d355cb618c | ||
|
|
eacd97d536 | ||
|
|
9aa091615e | ||
|
|
68dc7d1695 | ||
|
|
ecef999d19 | ||
|
|
b62150bd6b | ||
|
|
60ba7eebd7 | ||
|
|
997e1fb621 | ||
|
|
f6c4e4ab76 | ||
|
|
8f2ccba71b | ||
|
|
0c88b661f9 | ||
|
|
ab39518719 | ||
|
|
1b2185657f | ||
|
|
03cf150ff3 | ||
|
|
d67aa6da6b | ||
|
|
d2bdb72b69 | ||
|
|
5361c0b5d8 | ||
|
|
09c501a40e | ||
|
|
371000108a | ||
|
|
0bfeba2d9a | ||
|
|
d431111b06 | ||
|
|
b21e0746f6 | ||
|
|
437469c103 | ||
|
|
028faacff5 | ||
|
|
82d9419f62 | ||
|
|
a82007dd1e | ||
|
|
2d78705b73 | ||
|
|
08ec444ba5 | ||
|
|
98f5579c02 | ||
|
|
344b75f52d | ||
|
|
e0a2d15216 | ||
|
|
48ab72b478 | ||
|
|
1f7fe1597d | ||
|
|
01058b16f9 | ||
|
|
be05c9eb58 | ||
|
|
abb7e88e3c | ||
|
|
46d86da630 | ||
|
|
9ca5a2a5d8 | ||
|
|
8209376468 | ||
|
|
1171f92beb | ||
|
|
b9430309ec | ||
|
|
79a39aeebd | ||
|
|
d7b893cf35 | ||
|
|
b74067bc74 | ||
|
|
158768513c | ||
|
|
1f593beeb4 | ||
|
|
16e0a39482 | ||
|
|
f24612db44 | ||
|
|
257dbdf06f | ||
|
|
6694048272 | ||
|
|
34ab0386cb | ||
|
|
935df644af | ||
|
|
b44e81d9c3 | ||
|
|
72a8120daa | ||
|
|
16d22ea02a | ||
|
|
63b1031ca2 | ||
|
|
817bb2bbbe | ||
|
|
df8cc84f47 | ||
|
|
68201f4591 | ||
|
|
b6d79deebb | ||
|
|
cacc6db2a3 | ||
|
|
c6628a5fb6 | ||
|
|
b531332835 | ||
|
|
f1d161d55f | ||
|
|
baead23ea0 | ||
|
|
c20df5b3e1 | ||
|
|
c391640645 | ||
|
|
8b1d4b978d | ||
|
|
1d0bf4073b | ||
|
|
e7a9bd6937 | ||
|
|
697c4389a9 | ||
|
|
de3eecce7f | ||
|
|
1a1f6e8581 | ||
|
|
fba4e8aa40 | ||
|
|
c6fafbffac | ||
|
|
b2fb3f1340 | ||
|
|
9c5dcf9398 | ||
|
|
de6d3f8ca1 | ||
|
|
7397e8ab42 | ||
|
|
6087f63003 | ||
|
|
7f9193b353 | ||
|
|
f7fb6e186a | ||
|
|
0e36b77aae | ||
|
|
2ad79785cc | ||
|
|
0e0b87b6f3 | ||
|
|
8ef50016be | ||
|
|
0b10a07b63 | ||
|
|
3f75b3c343 | ||
|
|
a434892208 | ||
|
|
c1bcd30b09 | ||
|
|
fd135cf73e | ||
|
|
12cafdc868 | ||
|
|
6f40c211a5 | ||
|
|
434d3d4976 | ||
|
|
15eb1e1ad3 | ||
|
|
bee602da82 | ||
|
|
61c8a147b5 | ||
|
|
bbb57694b0 | ||
|
|
168ffbaf23 | ||
|
|
d45fbecbb5 | ||
|
|
47ea771b7a | ||
|
|
1adf324d8b | ||
|
|
bed421d483 | ||
|
|
ecbb02148b | ||
|
|
01a40bb1ee | ||
|
|
346ac1e09f | ||
|
|
46f7a4352e | ||
|
|
5fb0d7dbef | ||
|
|
41ee66d566 | ||
|
|
9581fbe569 | ||
|
|
207458c7a9 | ||
|
|
f550fdaa91 | ||
|
|
2b83f7e391 | ||
|
|
494aa0f651 | ||
|
|
988d188d96 | ||
|
|
e5748e891f | ||
|
|
f2298f394e | ||
|
|
a680c643eb | ||
|
|
56742ecdc9 | ||
|
|
dddda6e4a5 | ||
|
|
333cae74ef | ||
|
|
f72a33d2cb | ||
|
|
dd096863fa | ||
|
|
8dd4ae13ff | ||
|
|
aac04c0d64 | ||
|
|
fa4a1c4675 | ||
|
|
0206efa94a | ||
|
|
a796745fd2 | ||
|
|
90b53fede6 | ||
|
|
0389852db0 | ||
|
|
1a0d92aeca | ||
|
|
69650851f8 | ||
|
|
037c7a9279 | ||
|
|
e406e81477 | ||
|
|
5ea390c07f | ||
|
|
779031ad8f | ||
|
|
fee6b287a5 | ||
|
|
1aab037601 | ||
|
|
0f956f0929 | ||
|
|
7625bcc716 | ||
|
|
2bf094cd55 | ||
|
|
5e89aed934 | ||
|
|
83f3910398 | ||
|
|
e7d31d12be | ||
|
|
1479c7bd0d | ||
|
|
ffe0526064 | ||
|
|
7ec8bd168e | ||
|
|
2bcf04a7bd | ||
|
|
8f484567fc | ||
|
|
5e1e7d32e8 | ||
|
|
6074cfdfd4 | ||
|
|
955cc50e1b | ||
|
|
12ee13101b | ||
|
|
67506f21b8 | ||
|
|
bda32d99d7 | ||
|
|
80758eda17 | ||
|
|
b726f1eb90 | ||
|
|
53188a14bf | ||
|
|
5429dbf2a2 | ||
|
|
ab1ffa1382 | ||
|
|
37b7b32d61 | ||
|
|
8494fdadae | ||
|
|
0f9fcf0ed4 | ||
|
|
6fcc09ff3d | ||
|
|
67cda60da0 | ||
|
|
28e3618e79 | ||
|
|
df12f76910 | ||
|
|
5453604606 | ||
|
|
345bb193fe | ||
|
|
f1af6e53f0 | ||
|
|
ea76f79e5d | ||
|
|
e1cc43182c | ||
|
|
81851febc4 | ||
|
|
5904eddb38 | ||
|
|
b31020b193 | ||
|
|
9875f14ad4 | ||
|
|
49a648447f | ||
|
|
7097b6f81c | ||
|
|
f2809e19f0 | ||
|
|
62de84cbe3 | ||
|
|
d099e037ef | ||
|
|
6c06e7e2a0 | ||
|
|
cd226bdfd8 | ||
|
|
241d5ebc46 | ||
|
|
13ef6bcf6f | ||
|
|
dbef5b737e | ||
|
|
0c84ee01d5 | ||
|
|
f338ec4591 | ||
|
|
b196cadf9f | ||
|
|
aad6ba018a | ||
|
|
eb54d2ad4d | ||
|
|
38b367c039 | ||
|
|
24dc91907a | ||
|
|
3d0f86145c | ||
|
|
64d56114ed | ||
|
|
9e7665b21b | ||
|
|
0ce3dce03a | ||
|
|
bbfd9b5a29 | ||
|
|
87e9cd13aa | ||
|
|
95885bbaa2 | ||
|
|
d3dfffdf02 | ||
|
|
a24df94fed | ||
|
|
1c81311673 | ||
|
|
2378acc3b3 | ||
|
|
533ff28574 | ||
|
|
a22ff44f7b | ||
|
|
4bc29c1730 | ||
|
|
7a51271d3d | ||
|
|
8c82555e41 | ||
|
|
f676f6d2b9 | ||
|
|
e0739378dd | ||
|
|
9e124933e5 |
No files matched your search
+1
-3
@@ -5,7 +5,6 @@ os:
|
||||
#- osx
|
||||
env:
|
||||
matrix:
|
||||
- LIBAV=libav-stable
|
||||
- LIBAV=libav-git
|
||||
- LIBAV=ffmpeg-stable
|
||||
- LIBAV=ffmpeg-git
|
||||
@@ -26,8 +25,6 @@ branches:
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-stable
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
@@ -44,6 +41,7 @@ script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
notifications:
|
||||
|
||||
@@ -283,7 +283,6 @@ x player/audio.c LGPL (dysfunctional due to libaf)
|
||||
video/filter/vf_crop.c will be deleted
|
||||
video/filter/vf_d3d11vpp.c LGPL
|
||||
video/filter/vf_dsize.c will be deleted
|
||||
video/filter/vf_eq.c will be deleted
|
||||
video/filter/vf_expand.c will be deleted
|
||||
video/filter/vf_flip.c will be deleted
|
||||
video/filter/vf_format.c will be deleted
|
||||
|
||||
@@ -32,7 +32,7 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.26.0 ---
|
||||
--- mpv 0.27.0 ---
|
||||
1.25 - remove setting "no-" options via mpv_set_option*(). (See corresponding
|
||||
deprecation in 0.23.0.)
|
||||
--- mpv 0.25.0 ---
|
||||
|
||||
@@ -140,6 +140,8 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
(If the if body is simple enough, this rule can be skipped.)
|
||||
- Remove any trailing whitespace.
|
||||
|
||||
General coding
|
||||
|
||||
@@ -19,6 +19,53 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.27.0 ---
|
||||
- drop previously deprecated --field-dominance option
|
||||
- drop previously deprecated "osd" command
|
||||
- remove client API compatibility handling for "script", "sub-file",
|
||||
"audio-file", "external-file" (these cases used to log a deprecation
|
||||
warning)
|
||||
- drop deprecated --video-aspect-method=hybrid option choice
|
||||
- rename --hdr-tone-mapping to --tone-mapping (and generalize it)
|
||||
- --opengl-fbo-format changes from a choice to a string. Also, its value
|
||||
will be checked only on renderer initialization, rather than when the
|
||||
option is set.
|
||||
- Using opengl-cb now always assumes 8 bit per component depth, and dithers
|
||||
to this size. Before, it tried to figure out the depth of the first
|
||||
framebuffer that was ever passed to the renderer. Having GL framebuffers
|
||||
with a size larger than 8 bit per component is quite rare. If you need
|
||||
it, set the --dither-depth option instead.
|
||||
- --lavfi-complex can now be set during runtime. If you set this in
|
||||
expectation it would be applied only after a reload, you might observe
|
||||
weird behavior.
|
||||
- add --track-auto-selection to help with scripts/applications that
|
||||
make exclusive use of --lavfi-complex.
|
||||
- undeprecate --loop, and map it from --loop-playlist to --loop-file (the
|
||||
deprecation was to make sure no API user gets broken by a sudden behavior
|
||||
change)
|
||||
- remove previously deprecated vf_eq
|
||||
- remove that hardware deinterlace filters (vavpp, d3d11vpp, vdpaupp)
|
||||
changed their deinterlacing-enabled setting depending on what the
|
||||
--deinterlace option or property was set to. Now, a filter always does
|
||||
what its filter options and defaults imply. The --deinterlace option and
|
||||
property strictly add/remove its own filters. For example, if you run
|
||||
"mpv --vf=vavpp --deinterlace=yes", this will insert another, redundant
|
||||
filter, which is probably not what you want. For toggling a deinterlace
|
||||
filter manually, use the "vf toggle" command, and do not set the
|
||||
deinterlace option/property. To customize the filter that will be
|
||||
inserted automatically, use --vf-defaults. Details how this works will
|
||||
probably change in the future.
|
||||
- remove deinterlace=auto (this was not deprecated, but had only a very
|
||||
obscure use that stopped working with the change above. It was also
|
||||
prone to be confused with a feature not implemented by it: auto did _not_
|
||||
mean that deinterlacing was enabled on demand.)
|
||||
- add shortened mnemonic names for mouse button bindings, eg. mbtn_left
|
||||
the old numeric names (mouse_btn0) are deprecated
|
||||
- remove mouse_btn3_dbl and up, since they are only generated for buttons
|
||||
0-2 (these now print an error when sent from the 'mouse' command)
|
||||
- rename the axis bindings to wheel_up/down/etc. axis scrolling and mouse
|
||||
wheel scrolling are now conceptually the same thing
|
||||
the old axis_up/down names remain as deprecated aliases
|
||||
--- mpv 0.26.0 ---
|
||||
- remove remaining deprecated audio device options, like --alsa-device
|
||||
Some of them were removed in earlier releases.
|
||||
|
||||
+2
-1
@@ -216,7 +216,8 @@ Available audio output drivers are:
|
||||
it will write a WAVE header every time the file is opened.
|
||||
|
||||
``rsound``
|
||||
Audio output to an RSound daemon
|
||||
Audio output to an RSound daemon. Use ``--audio-device=rsound/<hostname>``
|
||||
to set the host name (with ``<hostname>`` replaced, without the ``< >``).
|
||||
|
||||
.. note:: Completely useless, unless you intend to run RSound. Not to be
|
||||
confused with RoarAudio, which is something completely
|
||||
|
||||
@@ -346,10 +346,6 @@ List of Input Commands
|
||||
events that have already been displayed, or are within a short prefetch
|
||||
range.
|
||||
|
||||
``osd [<level>]``
|
||||
Toggle OSD level. If ``<level>`` is specified, set the OSD mode
|
||||
(see ``--osd-level`` for valid values).
|
||||
|
||||
``print-text "<string>"``
|
||||
Print text to stdout. The string can contain properties (see
|
||||
`Property Expansion`_).
|
||||
@@ -2171,22 +2167,12 @@ caveats with some properties (due to historical reasons):
|
||||
|
||||
Option changes at runtime are affected by this as well.
|
||||
|
||||
``deinterlace``
|
||||
While video is active, this behaves differently from the option. It will
|
||||
never return the ``auto`` value (but the state as observed by the video
|
||||
chain). If you set ``auto``, the property will set this as the option value,
|
||||
and will return the actual video chain state as observed instead of auto.
|
||||
|
||||
``video-aspect``
|
||||
While video is active, always returns the effective aspect ratio. Setting
|
||||
a special value (like ``no``, values ``<= 0``) will make the property
|
||||
set this as option, and return whatever actual aspect was derived from the
|
||||
option setting.
|
||||
|
||||
``brightness`` (and other color options)
|
||||
If ``--vo=xv`` is used, these properties may return the adapter's current
|
||||
values instead of the option values.
|
||||
|
||||
``display-fps``
|
||||
If a VO is created, this will return either the actual display FPS, or
|
||||
an invalid value, instead of the option value.
|
||||
|
||||
+6
-1
@@ -437,7 +437,7 @@ Suffix Meaning
|
||||
-set Set a list of items
|
||||
============= ===============================================
|
||||
|
||||
Although some operations allow specifiying multiple ``,``-separated items, using
|
||||
Although some operations allow specifying multiple ``,``-separated items, using
|
||||
this is strongly discouraged and deprecated, except for ``-set``.
|
||||
|
||||
Without suffix, the action taken is normally ``-set``.
|
||||
@@ -728,6 +728,11 @@ PROTOCOLS
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
|
||||
``title`` can be: ``longest`` or ``first`` (selects the default
|
||||
playlist); ``mpls/<number>`` (selects <number>.mpls playlist);
|
||||
``<number>`` (select playlist with the same index). You can list
|
||||
the available playlists with ``--msg-level=bd=v``.
|
||||
|
||||
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
|
||||
|
||||
Play a DVD. DVD menus are not supported. If no title is given, the longest
|
||||
|
||||
+202
-76
@@ -76,6 +76,18 @@ Track Selection
|
||||
to ``auto`` (the default), mpv will choose the first edition declared as a
|
||||
default, or if there is no default, the first edition defined.
|
||||
|
||||
``--track-auto-selection=<yes|no>``
|
||||
Enable the default track auto-selection (default: yes). Enabling this will
|
||||
make the player select streams according to ``--aid``, ``--alang``, and
|
||||
others. If it is disabled, no tracks are selected. In addition, the player
|
||||
will not exit if no tracks are selected, and wait instead (this wait mode
|
||||
is similar to pausing, but the pause option is not set).
|
||||
|
||||
This is useful with ``--lavfi-complex``: you can start playback in this
|
||||
mode, and then set select tracks at runtime by setting the filter graph.
|
||||
Note that if ``--lavfi-complex`` is set before playback is started, the
|
||||
referenced tracks are always selected.
|
||||
|
||||
|
||||
Playback Control
|
||||
----------------
|
||||
@@ -287,11 +299,7 @@ Playback Control
|
||||
time trying to loop a file that doesn't exist. But it might be useful for
|
||||
playing webradios under very bad network conditions.
|
||||
|
||||
``--loop``
|
||||
Currently a deprecated alias to ``--loop-playlist``. After a deprecation
|
||||
period, it will be undeprecated, but changed to alias ``--loop-file``.
|
||||
|
||||
``--loop-file=<N|inf|no>``
|
||||
``--loop-file=<N|inf|no>``, ``--loop=<N|inf|no>``
|
||||
Loop a single file N times. ``inf`` means forever, ``no`` means normal
|
||||
playback. For compatibility, ``--loop-file`` and ``--loop-file=yes`` are
|
||||
also accepted, and are the same as ``--loop-file=inf``.
|
||||
@@ -301,6 +309,8 @@ Playback Control
|
||||
difference between the two option is that this option performs a seek on
|
||||
loop, instead of reloading the file.
|
||||
|
||||
``--loop`` is an alias for this option.
|
||||
|
||||
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
|
||||
Set loop points. If playback passes the ``b`` timestamp, it will seek to
|
||||
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
|
||||
@@ -712,7 +722,8 @@ Video
|
||||
The ``...-copy`` modes (e.g. ``dxva2-copy``) allow you to use hardware
|
||||
decoding with any VO, backend or filter. Because these copy the decoded
|
||||
video back to system RAM, they're likely less efficient than the direct
|
||||
modes (like e.g. ``dxva2``).
|
||||
modes (like e.g. ``dxva2``), and probably not more efficient than software
|
||||
decoding except for some codecs (e.g. HEVC).
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -722,15 +733,19 @@ Video
|
||||
|
||||
.. admonition:: Quality reduction with hardware decoding
|
||||
|
||||
Normally, hardware decoding does not reduce video quality (at least for
|
||||
the codecs h264 and HEVC). However, due to restrictions in video output
|
||||
APIs, there can be some loss, or blatantly incorrect results.
|
||||
In theory, hardware decoding does not reduce video quality (at least
|
||||
for the codecs h264 and HEVC). However, due to restrictions in video
|
||||
output APIs, as well as bugs in the actual hardware decoders, there can
|
||||
be some loss, or even blatantly incorrect results.
|
||||
|
||||
In some cases, RGB conversion is forced, which means the RGB conversion
|
||||
is performed by the hardware decoding API, instead of the OpenGL code
|
||||
used by ``--vo=opengl``. This means certain obscure colorspaces may
|
||||
not display correctly, not certain filtering (such as debanding)
|
||||
cannot be applied in an ideal way.
|
||||
used by ``--vo=opengl``. This means certain colorspaces may not display
|
||||
correctly, and certain filtering (such as debanding) cannot be applied
|
||||
in an ideal way. This will also usually force the use of low quality
|
||||
chroma scalers instead of the one specified by ``--cscale``. In other
|
||||
cases, hardware decoding can also reduce the bit depth of the decoded
|
||||
image, which can introduce banding or precision loss for 10-bit files.
|
||||
|
||||
``vdpau`` is usually safe. If deinterlacing enabled (or the ``vdpaupp``
|
||||
video filter is active in general), it forces RGB conversion. The latter
|
||||
@@ -739,14 +754,15 @@ Video
|
||||
filter retrieves image data without RGB conversion and is safe (but
|
||||
precludes use of vdpau postprocessing).
|
||||
|
||||
``vaapi`` is safe if the ``vaapi-egl`` backend is indicated in the logs.
|
||||
If ``vaapi-glx`` is indicated, and the video colorspace is either BT.601
|
||||
or BT.709, a forced but correct RGB conversion is performed. Otherwise,
|
||||
the result will be incorrect.
|
||||
``vaapi`` is safe if the ``vaapi-egl`` backend is indicated in the
|
||||
logs. If ``vaapi-glx`` is indicated, and the video colorspace is either
|
||||
BT.601 or BT.709, a forced, low-quality but correct RGB conversion is
|
||||
performed. Otherwise, the result will be totally incorrect.
|
||||
|
||||
``d3d11va`` is usually safe (if used with ANGLE builds that support
|
||||
``EGL_KHR_stream path`` - otherwise, it converts to RGB), except that
|
||||
10 bit input (HEVC main 10 profiles) will be rounded down to 8 bits.
|
||||
10 bit input (HEVC main 10 profiles) will be rounded down to 8 bits,
|
||||
which results in reduced quality.
|
||||
|
||||
``dxva2`` is not safe. It appears to always use BT.601 for forced RGB
|
||||
conversion, but actual behavior depends on the GPU drivers. Some drivers
|
||||
@@ -758,17 +774,30 @@ Video
|
||||
``rpi`` always uses the hardware overlay renderer, even with
|
||||
``--vo=opengl``.
|
||||
|
||||
``cuda`` should be safe, but it has been reported to corrupt the
|
||||
timestamps causing glitched, flashing frames on some files. It can also
|
||||
sometimes cause massive framedrops for unknown reasons. Caution is
|
||||
advised.
|
||||
|
||||
``crystalhd`` is not safe. It always converts to 4:2:2 YUV, which
|
||||
may be lossy, depending on how chroma sub-sampling is done during
|
||||
conversion. It also discards the top left pixel of each frame for
|
||||
some reason.
|
||||
|
||||
All other methods, in particular the copy-back methods (like
|
||||
``dxva2-copy`` etc.) are either fully safe, or not worse than software
|
||||
decoding.
|
||||
``dxva2-copy`` etc.) should hopefully be safe, although they can still
|
||||
cause random decoding issues. At the very least, they shouldn't affect
|
||||
the colors of the image.
|
||||
|
||||
In particular, ``auto-copy`` will only select safe modes
|
||||
(although potentially slower than other methods).
|
||||
In particular, ``auto-copy`` will only select "safe" modes
|
||||
(although potentially slower than other methods), but there's still no
|
||||
guarantee the chosen hardware decoder will actually work correctly.
|
||||
|
||||
In general, it's very strongly advised to avoid hardware decoding
|
||||
unless **absolutely** necessary, i.e. if your CPU is insufficient to
|
||||
decode the file in questions. If you run into any weird decoding issues,
|
||||
frame glitches or discoloration, and you have ``--hwdec`` turned on,
|
||||
the first thing you should try is disabling it.
|
||||
|
||||
``--opengl-hwdec-interop=<name>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
@@ -835,13 +864,10 @@ Video
|
||||
- ``--video-aspect=16:9`` or ``--video-aspect=1.7777``
|
||||
- ``--no-video-aspect`` or ``--video-aspect=no``
|
||||
|
||||
``--video-aspect-method=<hybrid|bitstream|container>``
|
||||
``--video-aspect-method=<bitstream|container>``
|
||||
This sets the default video aspect determination method (if the aspect is
|
||||
_not_ overridden by the user with ``--video-aspect`` or others).
|
||||
|
||||
:hybrid: Prefer the container aspect ratio. If the bitstream aspect
|
||||
switches mid-stream, switch to preferring the bitstream aspect.
|
||||
This was the default in older mpv and mplayer2. Deprecated.
|
||||
:container: Strictly prefer the container aspect ratio. This is apparently
|
||||
the default behavior with VLC, at least with Matroska. Note that
|
||||
if the container has no aspect ratio set, the behavior is the
|
||||
@@ -940,8 +966,8 @@ Video
|
||||
|
||||
Works in ``--no-correct-pts`` mode only.
|
||||
|
||||
``--deinterlace=<yes|no|auto>``
|
||||
Enable or disable interlacing (default: auto, which usually means no).
|
||||
``--deinterlace=<yes|no>``
|
||||
Enable or disable interlacing (default: no).
|
||||
Interlaced video shows ugly comb-like artifacts, which are visible on
|
||||
fast movement. Enabling this typically inserts the yadif video filter in
|
||||
order to deinterlace the video, or lets the video output apply deinterlacing
|
||||
@@ -950,22 +976,11 @@ Video
|
||||
This behaves exactly like the ``deinterlace`` input property (usually
|
||||
mapped to ``d``).
|
||||
|
||||
``auto`` is a technicality. Strictly speaking, the default for this option
|
||||
is deinterlacing disabled, but the ``auto`` case is needed if ``yadif`` was
|
||||
added to the filter chain manually with ``--vf``. Then the core shouldn't
|
||||
disable deinterlacing just because the ``--deinterlace`` was not set.
|
||||
|
||||
``--field-dominance=<auto|top|bottom>``
|
||||
Set first field for interlaced content.
|
||||
|
||||
:auto: (default) If the decoder does not export the appropriate
|
||||
information, it falls back on ``top`` (top field first).
|
||||
:top: top field first
|
||||
:bottom: bottom field first
|
||||
|
||||
.. note::
|
||||
|
||||
Setting either ``top`` or ``bottom`` will flag all frames as interlaced.
|
||||
Keep in mind that this **will** conflict with manually inserted
|
||||
deinterlacing filters, unless you take care. (Since mpv 0.27.0, even the
|
||||
hardware deinterlace filters will conflict. Also since that version,
|
||||
``--deinterlace=auto`` was removed, which used to mean that the default
|
||||
interlacing option of possibly inserted video filters was used.)
|
||||
|
||||
``--frames=<number>``
|
||||
Play/convert only first ``<number>`` video frames, then quit.
|
||||
@@ -1028,6 +1043,23 @@ Video
|
||||
(default: 3). If this is a number, then fallback will be triggered if
|
||||
N frames fail to decode in a row. 1 is equivalent to ``yes``.
|
||||
|
||||
``--vd-lavc-dr=<yes|no>``
|
||||
Enable direct rendering (default: no). If this is set to ``yes``, the
|
||||
video will be decoded directly to GPU video memory (or staging buffers).
|
||||
This can speed up video upload, and may help with large resolutions or
|
||||
slow hardware. This works only with the following VOs:
|
||||
|
||||
- ``opengl``: requires at least OpenGL 4.4.
|
||||
|
||||
(In particular, this can't be made work with ``opengl-cb``.)
|
||||
|
||||
Using video filters of any kind that write to the image data (or output
|
||||
newly allocated frames) will silently disable the DR code path.
|
||||
|
||||
There are some corner cases that will result in undefined behavior (crashes
|
||||
and other strange behavior) if this option is enabled. These are pending
|
||||
towards being fixed properly at a later point.
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
@@ -1450,13 +1482,12 @@ Audio
|
||||
|
||||
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
|
||||
Load additional audio files matching the video filename. The parameter
|
||||
specifies how external audio files are matched. ``exact`` is enabled by
|
||||
default.
|
||||
specifies how external audio files are matched.
|
||||
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:no: Don't automatically load external audio files (default).
|
||||
:exact: Load the media filename with audio file extension.
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all audio files in the current and ``--audio-file-path``
|
||||
:all: Load all audio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
@@ -1811,7 +1842,7 @@ Subtitles
|
||||
:no: Don't automatically load external subtitle files.
|
||||
:exact: Load the media filename with subtitle file extension (default).
|
||||
:fuzzy: Load all subs containing media filename.
|
||||
:all: Load all subs in the current and ``--sub-file-path`` directories.
|
||||
:all: Load all subs in the current and ``--sub-file-paths`` directories.
|
||||
|
||||
``--sub-codepage=<codepage>``
|
||||
You can use this option to specify the subtitle codepage. uchardet will be
|
||||
@@ -2456,6 +2487,10 @@ Window
|
||||
as having the same size as on none-HiDPI resolutions. This is the default OS X
|
||||
behavior.
|
||||
|
||||
``--native-fs``, ``--no-native-fs``
|
||||
(OS X only)
|
||||
Uses the native fullscreen mechanism of the OS (default: yes).
|
||||
|
||||
``--monitorpixelaspect=<ratio>``
|
||||
Set the aspect of a single pixel of your monitor or TV screen (default:
|
||||
1). A value of 1 means square pixels (correct for (almost?) all LCDs). See
|
||||
@@ -2965,7 +3000,7 @@ Input
|
||||
Support depends on the VO in use.
|
||||
|
||||
``--input-media-keys=<yes|no>``
|
||||
(OS X only)
|
||||
(OS X and Windows only)
|
||||
Enable/disable media keys support. Enabled by default (except for libmpv).
|
||||
|
||||
``--input-right-alt-gr``, ``--no-input-right-alt-gr``
|
||||
@@ -4089,7 +4124,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
|
||||
``--linear-scaling``
|
||||
Scale in linear light. It should only be used with a
|
||||
``--opengl-fbo-format`` that has at least 16 bit precision.
|
||||
``--opengl-fbo-format`` that has at least 16 bit precision. This option
|
||||
has no effect on HDR content.
|
||||
|
||||
``--correct-downscaling``
|
||||
When using convolution based filters, extend the filter size when
|
||||
@@ -4112,7 +4148,8 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
the ``--tscale`` setting.
|
||||
|
||||
Note that this relies on vsync to work, see ``--opengl-swapinterval`` for
|
||||
more information.
|
||||
more information. It should also only be used with an ``--opengl-fbo-format``
|
||||
that has at least 16 bit precision.
|
||||
|
||||
``--interpolation-threshold=<0..1,-1>``
|
||||
Threshold below which frame ratio interpolation gets disabled (default:
|
||||
@@ -4216,24 +4253,61 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
|
||||
...
|
||||
|
||||
Each block of metadata, along with the non-metadata lines after it, defines
|
||||
a single pass. Each pass can set the following metadata:
|
||||
Each section of metadata, along with the non-metadata lines after it,
|
||||
defines a single block. There are currently two types of blocks, HOOKs and
|
||||
TEXTUREs.
|
||||
|
||||
DESC <title>
|
||||
User-friendly description of the pass. This is the name used when
|
||||
representing this shader in the list of passes for property
|
||||
`vo-passes`.
|
||||
A ``TEXTURE`` block can set the following options:
|
||||
|
||||
TEXTURE <name> (required)
|
||||
The name of this texture. Hooks can then bind the texture under this
|
||||
name using BIND. This must be the first option of the texture block.
|
||||
|
||||
SIZE <width> [<height>] [<depth>] (required)
|
||||
The dimensions of the texture. The height and depth are optional. The
|
||||
type of texture (1D, 2D or 3D) depends on the number of components
|
||||
specified.
|
||||
|
||||
FORMAT <name> (required)
|
||||
The texture format for the samples. Supported texture formats are listed
|
||||
in debug logging when the ``opengl`` VO is initialized (look for
|
||||
``Texture formats:``). Usually, this follows OpenGL naming conventions.
|
||||
For example, ``rgb16`` provides 3 channels with normalized 16 bit
|
||||
components. One oddity are float formats: for example, ``rgba16f`` has
|
||||
16 bit internal precision, but the texture data is provided as 32 bit
|
||||
floats, and the driver converts the data on texture upload.
|
||||
|
||||
Although format names follow a common naming convention, not all of them
|
||||
are available on all hardware, drivers, GL versions, and so on.
|
||||
|
||||
FILTER <LINEAR|NEAREST>
|
||||
The min/magnification filter used when sampling from this texture.
|
||||
|
||||
BORDER <CLAMP|REPEAT|MIRROR>
|
||||
The border wrapping mode used when sampling from this texture.
|
||||
|
||||
Following the metadata is a string of bytes in hexadecimal notation that
|
||||
define the raw texture data, corresponding to the format specified by
|
||||
`FORMAT`, on a single line with no extra whitespace.
|
||||
|
||||
A ``HOOK`` block can set the following options:
|
||||
|
||||
HOOK <name> (required)
|
||||
The texture which to hook into. May occur multiple times within a
|
||||
metadata block, up to a predetermined limit. See below for a list of
|
||||
hookable textures.
|
||||
|
||||
DESC <title>
|
||||
User-friendly description of the pass. This is the name used when
|
||||
representing this shader in the list of passes for property
|
||||
`vo-passes`.
|
||||
|
||||
BIND <name>
|
||||
Loads a texture and makes it available to the pass, and sets up macros
|
||||
to enable accessing it. See below for a list of set macros. By default,
|
||||
no textures are bound. The special name HOOKED can be used to refer to
|
||||
the texture that triggered this pass.
|
||||
Loads a texture (either coming from mpv or from a ``TEXTURE`` block)
|
||||
and makes it available to the pass. When binding textures from mpv,
|
||||
this will also set up macros to facilitate accessing it properly. See
|
||||
below for a list. By default, no textures are bound. The special name
|
||||
HOOKED can be used to refer to the texture that triggered this pass.
|
||||
|
||||
SAVE <name>
|
||||
Gives the name of the texture to save the result of this pass into. By
|
||||
@@ -4257,19 +4331,40 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
hook point can still cause that hook point to be saved, which has some
|
||||
minor overhead)
|
||||
|
||||
OFFSET ox oy
|
||||
OFFSET <ox> <oy>
|
||||
Indicates a pixel shift (offset) introduced by this pass. These pixel
|
||||
offsets will be accumulated and corrected during the next scaling pass
|
||||
(``cscale`` or ``scale``). The default values are 0 0 which correspond
|
||||
to no shift. Note that offsets are ignored when not overwriting the
|
||||
hooked texture.
|
||||
|
||||
COMPONENTS n
|
||||
COMPONENTS <n>
|
||||
Specifies how many components of this pass's output are relevant and
|
||||
should be stored in the texture, up to 4 (rgba). By default, this value
|
||||
is equal to the number of components in HOOKED.
|
||||
|
||||
Each bound texture (via ``BIND``) will make available the following
|
||||
COMPUTE <bw> <bh> [<tw> <th>]
|
||||
Specifies that this shader should be treated as a compute shader, with
|
||||
the block size bw and bh. The compute shader will be dispatched with
|
||||
however many blocks are necessary to completely tile over the output.
|
||||
Within each block, there will bw tw*th threads, forming a single work
|
||||
group. In other words: tw and th specify the work group size, which can
|
||||
be different from the block size. So for example, a compute shader with
|
||||
bw, bh = 32 and tw, th = 8 running on a 500x500 texture would dispatch
|
||||
16x16 blocks (rounded up), each with 8x8 threads.
|
||||
|
||||
Compute shaders in mpv are treated a bit different from fragment
|
||||
shaders. Instead of defining a ``vec4 hook`` that produces an output
|
||||
sample, you directly define ``void hook`` which writes to a fixed
|
||||
writeonly image unit named ``out_image`` (this is bound by mpv) using
|
||||
`imageStore`. To help translate texture coordinates in the absence of
|
||||
vertices, mpv provides a special function ``NAME_map(id)`` to map from
|
||||
the texel space of the output image to the texture coordinates for all
|
||||
bound textures. In particular, ``NAME_pos`` is equivalent to
|
||||
``NAME_map(gl_GlobalInvocationID)``, although using this only really
|
||||
makes sense if (tw,th) == (bw,bh).
|
||||
|
||||
Each bound mpv texture (via ``BIND``) will make available the following
|
||||
definitions to that shader pass, where NAME is the name of the bound
|
||||
texture:
|
||||
|
||||
@@ -4696,9 +4791,11 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
The user should independently guarantee this before using these signal
|
||||
formats for display.
|
||||
|
||||
``--hdr-tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping HDR images onto the target
|
||||
display. Valid values are:
|
||||
``--tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping images onto the target
|
||||
display. This is relevant for both HDR->SDR conversion as well as gamut
|
||||
reduction (e.g. playing back BT.2020 content on a standard gamut display).
|
||||
Valid values are:
|
||||
|
||||
clip
|
||||
Hard-clip any out-of-range values. Use this when you care about
|
||||
@@ -4717,10 +4814,10 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
results in flattening of details and degradation in color accuracy.
|
||||
hable
|
||||
Similar to ``reinhard`` but preserves both dark and bright details
|
||||
better (slightly sigmoidal), at the cost of slightly darkening
|
||||
everything. Developed by John Hable for use in video games. Use this
|
||||
when you care about detail preservation more than color/brightness
|
||||
accuracy. This is roughly equivalent to
|
||||
better (slightly sigmoidal), at the cost of slightly darkening /
|
||||
desaturating everything. Developed by John Hable for use in video
|
||||
games. Use this when you care about detail preservation more than
|
||||
color/brightness accuracy. This is roughly equivalent to
|
||||
``--hdr-tone-mapping=reinhard --tone-mapping-param=0.24``.
|
||||
gamma
|
||||
Fits a logarithmic transfer between the tone curves.
|
||||
@@ -4750,6 +4847,14 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
linear
|
||||
Specifies the scale factor to use while stretching. Defaults to 1.0.
|
||||
|
||||
``--hdr-compute-peak``
|
||||
Compute the HDR peak per-frame of relying on tagged metadata. These values
|
||||
are averaged over local regions as well as over several frames to prevent
|
||||
the value from jittering around too much. This option basically gives you
|
||||
dynamic, per-scene tone mapping. Requires compute shaders, which is a
|
||||
fairly recent OpenGL feature, and will probably also perform horribly on
|
||||
some drivers, so enable at your own risk.
|
||||
|
||||
``--tone-mapping-desaturate=<value>``
|
||||
Apply desaturation for highlights that exceed this level of brightness. The
|
||||
higher the parameter, the more color information will be preserved. This
|
||||
@@ -4760,6 +4865,24 @@ The following video options are currently all specific to ``--vo=opengl`` and
|
||||
The default of 2.0 is somewhat conservative and will mostly just apply to
|
||||
skies or directly sunlit surfaces. A setting of 0.0 disables this option.
|
||||
|
||||
``--gamut-warning``
|
||||
If enabled, mpv will mark all clipped/out-of-gamut pixels that exceed a
|
||||
given threshold (currently hard-coded to 101%). The affected pixels will be
|
||||
inverted to make them stand out. Note: This option applies after the
|
||||
effects of all of mpv's color space transformation / tone mapping options,
|
||||
so it's a good idea to combine this with ``--tone-mapping=clip`` and use
|
||||
``--target-gamut`` to set the gamut to simulate. For example,
|
||||
``--target-gamut=bt.709`` would make mpv highlight all pixels that exceed the
|
||||
gamut of a standard gamut (sRGB) display. This option also does not work
|
||||
well with ICC profiles, since the 3DLUTs are always generated against the
|
||||
source color space and have chromatically-accurate clipping built in.
|
||||
|
||||
``--use-embedded-icc-profile``
|
||||
Load the embedded ICC profile contained in media files such as PNG images.
|
||||
(Default: yes). Note that this option only works when also using a display
|
||||
ICC profile (``--icc-profile`` or ``--icc-profile-auto``), and also
|
||||
requires LittleCMS 2 support.
|
||||
|
||||
``--icc-profile=<file>``
|
||||
Load an ICC profile and use it to transform video RGB to screen output.
|
||||
Needs LittleCMS 2 support compiled in. This option overrides the
|
||||
@@ -5122,11 +5245,14 @@ Miscellaneous
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter cannot be changed yet during playback. It's also not
|
||||
possible to change the tracks connected to the filter at runtime. Other
|
||||
tracks, as long as they're not connected to the filter, and the
|
||||
It's not possible to change the tracks connected to the filter at runtime,
|
||||
unless you explicitly change the ``lavfi-complex`` property and set new
|
||||
track assignments. When the graph is changed, the track selection is changed
|
||||
according to the used labels as well.
|
||||
|
||||
Other tracks, as long as they're not connected to the filter, and the
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
changed.
|
||||
changed with the normal methods.
|
||||
|
||||
Note that the normal filter chains (``--af``, ``--vf``) are applied between
|
||||
the complex graphs (e.g. ``ao`` label) and the actual output.
|
||||
|
||||
+11
-28
@@ -414,6 +414,16 @@ Available mpv-only filters are:
|
||||
but values such as ``[16:9]`` can be passed too (``[...]`` for quoting
|
||||
to prevent the option parser from interpreting the ``:`` character).
|
||||
|
||||
``<spherical-type>``
|
||||
Type of the spherical projection:
|
||||
|
||||
:auto: As indicated by the file (default)
|
||||
:none: Normal video
|
||||
:equirect: Equirectangular
|
||||
:unknown: Unknown projection
|
||||
|
||||
``<spherical-yaw>``, ``<spherical-pitch>``, ``<spherical-roll>``
|
||||
Reference angle in degree, if spherical video is used.
|
||||
|
||||
``noformat[=fmt]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
@@ -474,32 +484,6 @@ Available mpv-only filters are:
|
||||
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
|
||||
forces a specific threading configuration.
|
||||
|
||||
``eq[=gamma:contrast:brightness:saturation:rg:gg:bg:weight]``
|
||||
Software equalizer that uses lookup tables (slow), allowing gamma correction
|
||||
in addition to simple brightness and contrast adjustment. The parameters are
|
||||
given as floating point values.
|
||||
|
||||
``<0.1-10>``
|
||||
initial gamma value (default: 1.0)
|
||||
``<-2-2>``
|
||||
initial contrast, where negative values result in a negative image
|
||||
(default: 1.0)
|
||||
``<-1-1>``
|
||||
initial brightness (default: 0.0)
|
||||
``<0-3>``
|
||||
initial saturation (default: 1.0)
|
||||
``<0.1-10>``
|
||||
gamma value for the red component (default: 1.0)
|
||||
``<0.1-10>``
|
||||
gamma value for the green component (default: 1.0)
|
||||
``<0.1-10>``
|
||||
gamma value for the blue component (default: 1.0)
|
||||
``<0-1>``
|
||||
The weight parameter can be used to reduce the effect of a high gamma
|
||||
value on bright image areas, e.g. keep them from getting overamplified
|
||||
and just plain white. A value of 0.0 turns the gamma correction all
|
||||
the way down while 1.0 leaves it at its full strength (default: 1.0).
|
||||
|
||||
``pullup[=jl:jr:jt:jb:sb:mp]``
|
||||
Pulldown reversal (inverse telecine) filter, capable of handling mixed
|
||||
hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
|
||||
@@ -787,7 +771,7 @@ Available mpv-only filters are:
|
||||
no
|
||||
Don't perform deinterlacing.
|
||||
first-field
|
||||
Show only first field (going by ``--field-dominance``).
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing (default).
|
||||
weave, motion-adaptive, motion-compensated
|
||||
@@ -828,7 +812,6 @@ Available mpv-only filters are:
|
||||
the mode selected with ``deint-mode``.
|
||||
``deint-mode=<first-field|bob|temporal|temporal-spatial>``
|
||||
Select deinterlacing mode (default: temporal).
|
||||
All modes respect ``--field-dominance``.
|
||||
|
||||
Note that there's currently a mechanism that allows the ``vdpau`` VO to
|
||||
change the ``deint-mode`` of auto-inserted ``vdpaupp`` filters. To avoid
|
||||
|
||||
+1
-1
@@ -356,7 +356,7 @@ Available video output drivers are:
|
||||
no
|
||||
Don't allow deinterlacing (default for newer libva).
|
||||
first-field
|
||||
Show only first field (going by ``--field-dominance``).
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing (default for older libva).
|
||||
|
||||
|
||||
+101
@@ -1,3 +1,104 @@
|
||||
Release 0.27.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Windows: handle media keys
|
||||
- libmpv: options: add a thread-safe way to notify option updates
|
||||
- vd_lavc/vo_opengl: support embedded ICC profiles
|
||||
- vo: rendering API abstraction for future non-GL video outputs
|
||||
- vo_opengl: add a gamut warning feature to highlight out-of-gamut colors (--gamut-warning)
|
||||
- vo_opengl: add direct rendering support (--vd-lavc-dr)
|
||||
- vo_opengl: implement (faster) compute shader based EWA kernel
|
||||
- vo_opengl: implement HLG OOTF inverse
|
||||
- vo_opengl: support HDR peak detection (--hdr-compute-peak)
|
||||
- vo_opengl: support float input pixel formats
|
||||
- vo_opengl: support loading custom user textures (#4586)
|
||||
- vo_opengl: support user compute shaders
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- Remove video equalizer handling from vo_direct3d, vo_sdl, vo_vaapi, and vo_xv (GPL, not worth the effort to support legacy VOs)
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- macOS: add --no-native-fs option to disable the native macOS fullscreen (#4014)
|
||||
- player: add --track-auto-selection option
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- input: use mnemonic names for mouse buttons, same as Qt: https://doc.qt.io/qt-5/qt.html#MouseButton-enum
|
||||
- options: change --loop semantics
|
||||
- player: make --lavfi-complex changeable at runtime
|
||||
- vf_eq: remove this filter (GPL; uses libavfilter’s eq filter now, with changed semantics)
|
||||
- video: change --deinterlace behavior
|
||||
- vo_opengl: generalize HDR tone mapping to gamut mapping, --hdr-tone-mapping → --tone-mapping
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- --field-dominance (GPL-only author, no chance of relicensing)
|
||||
- input: drop deprecated "osd" command
|
||||
- options: drop --video-aspect-method=hybrid (GPL-only)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/autocrop.lua: fix cropdetect black limit for 10-bit videos
|
||||
- TOOLS/lua/autodeint: update to lavfi-bridge
|
||||
- TOOLS/lua/status-line: improve and update
|
||||
- af_lavrresample: don't call swr_set_compensation() unless necessary (#4716)
|
||||
- ao_oss: fix period_size calculation (#4642)
|
||||
- ao_rsound: allow setting the host
|
||||
- audio: fix spdif mode
|
||||
- filter_kernels: correct spline64 kernel
|
||||
- macOS: fix media keys input when other Apps steal the priority (#4834)
|
||||
- macOS: fix the support of multiple renderers (GPU switch) (#2371)
|
||||
- macOS: remove the standard "Show Tab Bar" menu item
|
||||
- options: fix --include (#4673)
|
||||
- player: fix --end with large values (#4650)
|
||||
- player: fix confusion in audio resync code (#4688)
|
||||
- player: make refresh seeks slightly more robust (#4757)
|
||||
- player: readd smi subtitle extension (#4626)
|
||||
- vd_lavc: change auto-probe order to prefer cuda over vdpau-copy
|
||||
- vd_lavc: fix device leak with copy-mode hwaccels (#4735)
|
||||
- vd_lavc: fix hwdec compatibility with yuvj420p formats
|
||||
- vd_lavc: fix mid-stream hwdec fallback
|
||||
- vf_vapoursynth: fix inverted sign and restore 10 bit support (#4720)
|
||||
- video: increase --monitorpixelaspect range
|
||||
- vo_opengl: adjust the rules for linearization (#4631)
|
||||
- vo_opengl: scale deband-grain to the signal range
|
||||
- vo_opengl: tone map on the maximum signal component
|
||||
- x11: fix that window could be resized when using embedding (#4784)
|
||||
- ytdl_hook: resolve relative paths when joining segment urls (#4827)
|
||||
- ytdl_hook: support fragments with relative paths, fixes segmented DASH
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.26.0..v0.27.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.26.0...v0.27.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.26.0..v0.27.0
|
||||
|
||||
|
||||
Release 0.26.0
|
||||
==============
|
||||
|
||||
|
||||
+108
-79
@@ -3,48 +3,80 @@
|
||||
--
|
||||
-- See https://ffmpeg.org/ffmpeg-filters.html#acompressor for explanation
|
||||
-- of the parameteres.
|
||||
--
|
||||
-- Default key bindings:
|
||||
-- n: Toggle dynamic range compression on or off
|
||||
-- F1/Shift+F1: Increase/Decrease threshold parameter
|
||||
-- F2/Shift+F2: Increase/Decrease ratio parameter
|
||||
-- F3/Shift+F3: Increase/Decrease knee parameter
|
||||
-- F4/Shift+F4: Increase/Decrease makeup gain parameter
|
||||
-- F5/Shift+F5: Increase/Decrease attack parameter
|
||||
-- F6/Shift+F6: Increase/Decrease release parameter
|
||||
--
|
||||
-- To change key bindings in input.conf use:
|
||||
-- BINDING script-message-to acompressor toggle-acompressor
|
||||
-- BINDING script-message-to acompressor update-param PARAM INCREMENT
|
||||
-- BINDING is the key binding to use, PARAM is either 'attack', 'release',
|
||||
-- 'threshold', 'ratio', 'knee' or 'makeup' and INCREMENT is a signed floating
|
||||
-- point value to add to the current parameter value.
|
||||
--
|
||||
-- You may also just adjust default parameters to your liking in this table.
|
||||
|
||||
local mp = require 'mp'
|
||||
local options = require 'mp.options'
|
||||
|
||||
local o = {
|
||||
default_enable = false,
|
||||
show_osd = true,
|
||||
osd_timeout = 4000,
|
||||
filter_label = mp.get_script_name(),
|
||||
|
||||
key_toggle = 'n',
|
||||
key_increase_threshold = 'F1',
|
||||
key_decrease_threshold = 'Shift+F1',
|
||||
key_increase_ratio = 'F2',
|
||||
key_decrease_ratio = 'Shift+F2',
|
||||
key_increase_knee = 'F3',
|
||||
key_decrease_knee = 'Shift+F3',
|
||||
key_increase_makeup = 'F4',
|
||||
key_decrease_makeup = 'Shift+F4',
|
||||
key_increase_attack = 'F5',
|
||||
key_decrease_attack = 'Shift+F5',
|
||||
key_increase_release = 'F6',
|
||||
key_decrease_release = 'Shift+F6',
|
||||
|
||||
default_threshold = -25.0,
|
||||
default_ratio = 3.0,
|
||||
default_knee = 2.0,
|
||||
default_makeup = 8.0,
|
||||
default_attack = 20.0,
|
||||
default_release = 250.0,
|
||||
|
||||
step_threshold = -2.5,
|
||||
step_ratio = 1.0,
|
||||
step_knee = 1.0,
|
||||
step_makeup = 1.0,
|
||||
step_attack = 10.0,
|
||||
step_release = 10.0,
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
local params = {
|
||||
-- 'hide' defines wether the parameter should be hidden from OSD display (it will still be included in ffmpeg filter graph).
|
||||
-- 'input_format' is used to parse the value back from a obtained filter graph.
|
||||
-- 'output_format' defines how the value shall be formatted for creating the filter graph.
|
||||
{ name = 'Attack', value= 20, min=0.01, max=2000, hide= 20, input_format='attack=(%d+[.%d+]*)', output_format='%g' },
|
||||
{ name = 'Release', value=250, min=0.01, max=9000, hide=250, input_format='release=(%d+[.%d+]*)', output_format='%g' },
|
||||
{ name = 'Threshold', value=-25, min= -30, max= 0, hide=nil, input_format='threshold=(-%d+[.%d+]*)dB', output_format='%gdB' },
|
||||
{ name = 'Ratio', value= 3, min= 1, max= 20, hide=nil, input_format='ratio=(%d+[.%d+]*)', output_format='%g' },
|
||||
{ name = 'Knee', value= 2, min= 1, max= 10, hide= 2, input_format='knee=(%d+[.%d+]*)dB', output_format='%gdB' },
|
||||
{ name = 'Makeup', value= 8, min= 0, max= 24, hide=nil, input_format='makeup=(%d+[.%d+]*)dB', output_format='%gdB' }
|
||||
{ name = 'attack', min=0.01, max=2000, hide_default=true, dB='' },
|
||||
{ name = 'release', min=0.01, max=9000, hide_default=true, dB='' },
|
||||
{ name = 'threshold', min= -30, max= 0, hide_default=false, dB='dB' },
|
||||
{ name = 'ratio', min= 1, max= 20, hide_default=false, dB='' },
|
||||
{ name = 'knee', min= 1, max= 10, hide_default=true, dB='dB' },
|
||||
{ name = 'makeup', min= 0, max= 24, hide_default=false, dB='dB' },
|
||||
}
|
||||
|
||||
-- Defines the mpv filter label to be used. This allows us to easily add/replace/remove it, so
|
||||
-- it should be left so something meaningful and unique.
|
||||
local filter_label = 'acompressor'
|
||||
local function parse_value(value)
|
||||
-- Using nil here because tonumber differs between lua 5.1 and 5.2 when parsing fractions in combination with explicit base argument set to 10.
|
||||
-- And we can't omit it because gsub returns 2 values which would get unpacked and cause more problems. Gotta love scripting languages.
|
||||
return tonumber(value:gsub('dB$', ''), nil)
|
||||
end
|
||||
|
||||
local function format_value(value, dB)
|
||||
return string.format('%g%s', value, dB)
|
||||
end
|
||||
|
||||
local function show_osd(filter)
|
||||
if not o.show_osd then
|
||||
return
|
||||
end
|
||||
|
||||
if not filter.enabled then
|
||||
mp.commandv('show-text', 'Dynamic range compressor: disabled', o.osd_timeout)
|
||||
return
|
||||
end
|
||||
|
||||
local function update_filter()
|
||||
local graph = {}
|
||||
local pretty = {}
|
||||
|
||||
for _,param in pairs(params) do
|
||||
graph[#graph+1] = string.format('%s=' .. param.output_format, string.lower(param.name), param.value)
|
||||
if param.hide ~= param.value then
|
||||
pretty[#pretty+1] = string.format('%s: ' .. param.output_format, param.name, param.value)
|
||||
for _,param in ipairs(params) do
|
||||
local value = parse_value(filter.params[param.name])
|
||||
if not (param.hide_default and value == o['default_' .. param.name]) then
|
||||
pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper), value, param.dB)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,49 +86,49 @@ local function update_filter()
|
||||
pretty = '\n(' .. table.concat(pretty, ', ') .. ')'
|
||||
end
|
||||
|
||||
mp.command(string.format('no-osd af add @%s:lavfi=[acompressor=%s]; show-text "Dynamic range compressor: enabled%s" 4000', filter_label, table.concat(graph, ':'), pretty))
|
||||
mp.commandv('show-text', 'Dynamic range compressor: enabled' .. pretty, o.osd_timeout)
|
||||
end
|
||||
|
||||
local function read_filter()
|
||||
local graph = nil
|
||||
local function get_filter()
|
||||
local af = mp.get_property_native('af', {})
|
||||
|
||||
for i = 1, #af do
|
||||
if af[i]['name'] == 'lavfi' and af[i]['label'] == 'acompressor' then
|
||||
graph = af[i]['params']['graph']
|
||||
break
|
||||
if af[i].label == o.filter_label then
|
||||
return af, i
|
||||
end
|
||||
end
|
||||
|
||||
if graph == nil then
|
||||
return false
|
||||
end
|
||||
af[#af+1] = {
|
||||
name = 'acompressor',
|
||||
label = o.filter_label,
|
||||
enabled = false,
|
||||
params = {},
|
||||
}
|
||||
|
||||
for _,param in pairs(params) do
|
||||
local value = tonumber(string.match(graph, param.input_format))
|
||||
if value ~= nil and value >= param.min and value <= param.max then
|
||||
param.value = value
|
||||
end
|
||||
af[#af].params[param.name] = format_value(o['default_' .. param.name], param.dB)
|
||||
end
|
||||
return true
|
||||
|
||||
return af, #af
|
||||
end
|
||||
|
||||
local function toggle_acompressor()
|
||||
if read_filter() then
|
||||
mp.command(string.format('no-osd af del @%s; show-text "Dynamic range compressor: disabled"', filter_label))
|
||||
else
|
||||
update_filter()
|
||||
end
|
||||
local af, i = get_filter()
|
||||
af[i].enabled = not af[i].enabled
|
||||
mp.set_property_native('af', af)
|
||||
show_osd(af[i])
|
||||
end
|
||||
|
||||
local function update_param(name, increment)
|
||||
-- The current filter values could come from a watch_later config.
|
||||
-- read_filter() will parse them, so that we don't clobber those with our default values.
|
||||
read_filter()
|
||||
for _,param in pairs(params) do
|
||||
if string.lower(param.name) == string.lower(name) then
|
||||
param.value = math.max(param.min, math.min(param.value + increment, param.max))
|
||||
update_filter()
|
||||
if param.name == string.lower(name) then
|
||||
local af, i = get_filter()
|
||||
local value = parse_value(af[i].params[param.name])
|
||||
value = math.max(param.min, math.min(value + increment, param.max))
|
||||
af[i].params[param.name] = format_value(value, param.dB)
|
||||
af[i].enabled = true
|
||||
mp.set_property_native('af', af)
|
||||
show_osd(af[i])
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -104,23 +136,20 @@ local function update_param(name, increment)
|
||||
mp.msg.error('Unknown parameter "' .. name .. '"')
|
||||
end
|
||||
|
||||
mp.add_key_binding("n", "toggle-acompressor", toggle_acompressor)
|
||||
mp.add_key_binding(o.key_toggle, "toggle-acompressor", toggle_acompressor)
|
||||
mp.register_script_message('update-param', update_param)
|
||||
|
||||
mp.add_key_binding("F1", 'acompressor-inc-threshold', function() update_param('threshold', -5); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F1", 'acompressor-dec-threshold', function() update_param('Threshold', 5); end, { repeatable = true })
|
||||
for _,param in pairs(params) do
|
||||
for direction,step in pairs({increase=1, decrease=-1}) do
|
||||
mp.add_key_binding(o['key_' .. direction .. '_' .. param.name],
|
||||
'acompressor-' .. direction .. '-' .. param.name,
|
||||
function() update_param(param.name, step*o['step_' .. param.name]); end,
|
||||
{ repeatable = true })
|
||||
end
|
||||
end
|
||||
|
||||
mp.add_key_binding("F2", 'acompressor-inc-ratio', function() update_param('Ratio', 1); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F2", 'acompressor-dec-ratio', function() update_param('Ratio', -1); end, { repeatable = true })
|
||||
|
||||
mp.add_key_binding("F3", 'acompressor-inc-knee', function() update_param('Knee', 1); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F3", 'acompressor-dec-knee', function() update_param('Knee', -1); end, { repeatable = true })
|
||||
|
||||
mp.add_key_binding("F4", 'acompressor-inc-makeup', function() update_param('Makeup', 1); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F4", 'acompressor-dec-makeup', function() update_param('Makeup', -1); end, { repeatable = true })
|
||||
|
||||
mp.add_key_binding("F5", 'acompressor-inc-attack', function() update_param('Attack', 10); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F5", 'acompressor-dec-attack', function() update_param('Attack', -10); end, { repeatable = true })
|
||||
|
||||
mp.add_key_binding("F6", 'acompressor-inc-release', function() update_param('Release', 10); end, { repeatable = true })
|
||||
mp.add_key_binding("Shift+F6", 'acompressor-dec-release', function() update_param('Release', -10); end, { repeatable = true })
|
||||
if o.default_enable then
|
||||
local af, i = get_filter()
|
||||
af[i].enabled = true
|
||||
mp.set_property_native('af', af)
|
||||
end
|
||||
@@ -66,8 +66,8 @@ function autocrop_start()
|
||||
-- insert the cropdetect filter
|
||||
ret=mp.command(
|
||||
string.format(
|
||||
'vf add @%s:lavfi=graph="cropdetect=limit=24:round=2:reset=0"',
|
||||
cropdetect_label
|
||||
'vf add @%s:cropdetect=limit=%f:round=2:reset=0',
|
||||
cropdetect_label, 24/255
|
||||
)
|
||||
)
|
||||
-- wait to gather data
|
||||
@@ -79,6 +79,7 @@ function do_crop()
|
||||
local cropdetect_metadata = mp.get_property_native(
|
||||
string.format("vf-metadata/%s", cropdetect_label)
|
||||
)
|
||||
|
||||
-- use it to crop if its valid
|
||||
if cropdetect_metadata then
|
||||
if cropdetect_metadata["lavfi.cropdetect.w"]
|
||||
@@ -86,7 +87,7 @@ function do_crop()
|
||||
and cropdetect_metadata["lavfi.cropdetect.x"]
|
||||
and cropdetect_metadata["lavfi.cropdetect.y"]
|
||||
then
|
||||
mp.command(string.format("vf add @%s:crop=%s:%s:%s:%s",
|
||||
mp.command(string.format("vf add @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
|
||||
crop_label,
|
||||
cropdetect_metadata["lavfi.cropdetect.w"],
|
||||
cropdetect_metadata["lavfi.cropdetect.h"],
|
||||
|
||||
+38
-27
@@ -3,13 +3,13 @@
|
||||
-- currently playing video.
|
||||
--
|
||||
-- It registers the key-binding ctrl+d, which when pressed, inserts the filters
|
||||
-- ``vf=lavfi=idet,pullup,vf=lavfi=idet``. After 4 seconds, it removes these
|
||||
-- ``vf=idet,lavfi-pullup,idet``. After 4 seconds, it removes these
|
||||
-- filters and decides whether the content is progressive, interlaced, or
|
||||
-- telecined and the interlacing field dominance.
|
||||
--
|
||||
-- Based on this information, it may set mpv's ``deinterlace`` property (which
|
||||
-- usually inserts the yadif filter), or insert the ``pullup`` filter if the
|
||||
-- content is telecined. It also sets mpv's ``field-dominance`` property.
|
||||
-- content is telecined. It also sets field dominance with lavfi setfield.
|
||||
--
|
||||
-- OPTIONS:
|
||||
-- The default detection time may be overridden by adding
|
||||
@@ -22,16 +22,14 @@
|
||||
-- To see counts of the various types of frames for each detection phase,
|
||||
-- the verbosity can be increased with
|
||||
--
|
||||
-- --msg-level autodeint=v
|
||||
--
|
||||
-- This script requires a recent version of ffmpeg for which the idet
|
||||
-- filter adds the required metadata.
|
||||
-- --msg-level=autodeint=v
|
||||
|
||||
require "mp.msg"
|
||||
|
||||
script_name = mp.get_script_name()
|
||||
detect_label = string.format("%s-detect", script_name)
|
||||
pullup_label = string.format("%s", script_name)
|
||||
dominance_label = string.format("%s-dominance", script_name)
|
||||
ivtc_detect_label = string.format("%s-ivtc-detect", script_name)
|
||||
|
||||
-- number of seconds to gather cropdetect data
|
||||
@@ -55,6 +53,10 @@ function del_filter_if_present(label)
|
||||
return false
|
||||
end
|
||||
|
||||
local function add_vf(label, filter)
|
||||
return mp.command(('vf add @%s:%s'):format(label, filter))
|
||||
end
|
||||
|
||||
function start_detect()
|
||||
-- exit if detection is already in progress
|
||||
if timer then
|
||||
@@ -64,11 +66,13 @@ function start_detect()
|
||||
|
||||
mp.set_property("deinterlace","no")
|
||||
del_filter_if_present(pullup_label)
|
||||
del_filter_if_present(dominance_label)
|
||||
|
||||
-- insert the detection filter
|
||||
local cmd = string.format('vf add @%s:lavfi=graph="idet",@%s:pullup,@%s:lavfi=graph="idet"',
|
||||
detect_label, pullup_label, ivtc_detect_label)
|
||||
if not mp.command(cmd) then
|
||||
-- insert the detection filters
|
||||
if not (add_vf(detect_label, 'idet') and
|
||||
add_vf(dominance_label, 'setfield=mode=auto') and
|
||||
add_vf(pullup_label, 'lavfi-pullup') and
|
||||
add_vf(ivtc_detect_label, 'idet')) then
|
||||
mp.msg.error("failed to insert detection filters")
|
||||
return
|
||||
end
|
||||
@@ -88,12 +92,12 @@ progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
|
||||
function judge(label)
|
||||
-- get the metadata
|
||||
local result = mp.get_property_native(string.format("vf-metadata/%s", label))
|
||||
num_tff = tonumber(result["lavfi.idet.multiple.tff"])
|
||||
num_bff = tonumber(result["lavfi.idet.multiple.bff"])
|
||||
num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
|
||||
num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"])
|
||||
num_interlaced = num_tff + num_bff
|
||||
num_determined = num_interlaced + num_progressive
|
||||
local num_tff = tonumber(result["lavfi.idet.multiple.tff"])
|
||||
local num_bff = tonumber(result["lavfi.idet.multiple.bff"])
|
||||
local num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
|
||||
local num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"])
|
||||
local num_interlaced = num_tff + num_bff
|
||||
local num_determined = num_interlaced + num_progressive
|
||||
|
||||
mp.msg.verbose(label.." progressive = "..num_progressive)
|
||||
mp.msg.verbose(label.." interlaced-tff = "..num_tff)
|
||||
@@ -116,26 +120,33 @@ end
|
||||
|
||||
function select_filter()
|
||||
-- handle the first detection filter results
|
||||
verdict = judge(detect_label)
|
||||
local verdict = judge(detect_label)
|
||||
local ivtc_verdict = judge(ivtc_detect_label)
|
||||
local dominance = "auto"
|
||||
if verdict == progressive then
|
||||
mp.msg.info("progressive: doing nothing")
|
||||
stop_detect()
|
||||
del_filter_if_present(dominance_label)
|
||||
del_filter_if_present(pullup_label)
|
||||
return
|
||||
elseif verdict == interlaced_tff then
|
||||
mp.set_property("field-dominance", "top")
|
||||
elseif verdict == interlaced_bff then
|
||||
mp.set_property("field-dominance", "bottom")
|
||||
elseif verdict == interlaced then
|
||||
mp.set_property("field-dominance", "auto")
|
||||
else
|
||||
if verdict == interlaced_tff then
|
||||
dominance = "tff"
|
||||
add_vf(dominance_label, 'setfield=mode='..dominance)
|
||||
elseif verdict == interlaced_bff then
|
||||
dominance = "bff"
|
||||
add_vf(dominance_label, 'setfield=mode='..dominance)
|
||||
else
|
||||
del_filter_if_present(dominance_label)
|
||||
end
|
||||
end
|
||||
|
||||
-- handle the ivtc detection filter results
|
||||
verdict = judge(ivtc_detect_label)
|
||||
if verdict == progressive then
|
||||
mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance")))
|
||||
if ivtc_verdict == progressive then
|
||||
mp.msg.info(string.format("telecined with %s field dominance: using pullup", dominance))
|
||||
stop_detect()
|
||||
else
|
||||
mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", mp.get_property("field-dominance")))
|
||||
mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", dominance))
|
||||
del_filter_if_present(pullup_label)
|
||||
mp.set_property("deinterlace","yes")
|
||||
stop_detect()
|
||||
|
||||
+32
-9
@@ -53,6 +53,37 @@ table.filter = function(t, iter)
|
||||
end
|
||||
end
|
||||
|
||||
-- splitbynum and alnumcomp from alphanum.lua (C) Andre Bogus
|
||||
-- Released under the MIT License
|
||||
-- http://www.davekoelle.com/files/alphanum.lua
|
||||
|
||||
-- split a string into a table of number and string values
|
||||
function splitbynum(s)
|
||||
local result = {}
|
||||
for x, y in (s or ""):gmatch("(%d*)(%D*)") do
|
||||
if x ~= "" then table.insert(result, tonumber(x)) end
|
||||
if y ~= "" then table.insert(result, y) end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function clean_key(k)
|
||||
k = (' '..k..' '):gsub("%s+", " "):sub(2, -2):lower()
|
||||
return splitbynum(k)
|
||||
end
|
||||
|
||||
-- compare two strings
|
||||
function alnumcomp(x, y)
|
||||
local xt, yt = clean_key(x), clean_key(y)
|
||||
for i = 1, math.min(#xt, #yt) do
|
||||
local xe, ye = xt[i], yt[i]
|
||||
if type(xe) == "string" then ye = tostring(ye)
|
||||
elseif type(ye) == "string" then xe = tostring(xe) end
|
||||
if xe ~= ye then return xe < ye end
|
||||
end
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
@@ -81,15 +112,7 @@ function find_and_add_entries()
|
||||
end
|
||||
return EXTENSIONS[string.lower(ext)]
|
||||
end)
|
||||
table.sort(files, function (a, b)
|
||||
local len = string.len(a) - string.len(b)
|
||||
if len ~= 0 then -- case for ordering filename ending with such as X.Y.Z
|
||||
local ext = string.len(get_extension(a)) + 1
|
||||
a = string.sub(a, 1, -ext)
|
||||
b = string.sub(b, 1, -ext)
|
||||
end
|
||||
return string.lower(a) < string.lower(b)
|
||||
end)
|
||||
table.sort(files, alnumcomp)
|
||||
|
||||
if dir == "." then
|
||||
dir = ""
|
||||
|
||||
@@ -18,10 +18,10 @@ function update_status_line()
|
||||
atsl("(Buffering) ")
|
||||
end
|
||||
|
||||
if mp.get_property("vid") ~= "no" then
|
||||
if mp.get_property("aid") ~= "no" then
|
||||
atsl("A")
|
||||
end
|
||||
if mp.get_property("aid") ~= "no" then
|
||||
if mp.get_property("vid") ~= "no" then
|
||||
atsl("V")
|
||||
end
|
||||
|
||||
@@ -43,7 +43,7 @@ function update_status_line()
|
||||
|
||||
r = mp.get_property_number("avsync", nil)
|
||||
if r ~= nil then
|
||||
atsl(string.format(" A-V: %7.3f", r))
|
||||
atsl(string.format(" A-V: %f", r))
|
||||
end
|
||||
|
||||
r = mp.get_property("total-avsync-change", 0)
|
||||
@@ -51,12 +51,24 @@ function update_status_line()
|
||||
atsl(string.format(" ct:%7.3f", r))
|
||||
end
|
||||
|
||||
r = mp.get_property_number("drop-frame-count", -1)
|
||||
r = mp.get_property_number("decoder-drop-frame-count", -1)
|
||||
if r > 0 then
|
||||
atsl(" Late: ")
|
||||
atsl(r)
|
||||
end
|
||||
|
||||
r = mp.get_property_osd("video-bitrate")
|
||||
if r ~= nil and r ~= "" then
|
||||
atsl(" Vb: ")
|
||||
atsl(r)
|
||||
end
|
||||
|
||||
r = mp.get_property_osd("audio-bitrate")
|
||||
if r ~= nil and r ~= "" then
|
||||
atsl(" Ab: ")
|
||||
atsl(r)
|
||||
end
|
||||
|
||||
r = mp.get_property_number("cache", 0)
|
||||
if r > 0 then
|
||||
atsl(string.format(" Cache: %d%% ", r))
|
||||
@@ -66,6 +78,15 @@ function update_status_line()
|
||||
mp.set_property("options/term-status-msg", newStatus)
|
||||
end
|
||||
|
||||
-- Register the event
|
||||
mp.register_event("tick", update_status_line)
|
||||
timer = mp.add_periodic_timer(1, update_status_line)
|
||||
|
||||
function on_pause_change(name, value)
|
||||
if value == false then
|
||||
timer:resume()
|
||||
else
|
||||
timer:stop()
|
||||
end
|
||||
mp.add_timeout(0.1, update_status_line)
|
||||
end
|
||||
mp.observe_property("pause", "bool", on_pause_change)
|
||||
mp.register_event("seek", update_status_line)
|
||||
@@ -137,6 +137,13 @@ elements_matroska = (
|
||||
'LuminanceMin, 55DA, float',
|
||||
),
|
||||
),
|
||||
'Projection, 7670, sub', (
|
||||
'ProjectionType, 7671, uint',
|
||||
'ProjectionPrivate, 7672, binary',
|
||||
'ProjectionPoseYaw, 7673, float',
|
||||
'ProjectionPosePitch, 7674, float',
|
||||
'ProjectionPoseRoll, 7675, float',
|
||||
),
|
||||
),
|
||||
'Audio, e1, sub', (
|
||||
'SamplingFrequency, b5, float',
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
#!/bin/bash -l
|
||||
#!/bin/sh
|
||||
export MPVBUNDLE="true"
|
||||
$SHELL -c "$(dirname "$0")/mpv --player-operation-mode=pseudo-gui"
|
||||
|
||||
# set the right args for the user specified standard shell
|
||||
# to load the expected profiles and configs
|
||||
args="-c"
|
||||
case "$SHELL" in
|
||||
*bash) args="-l $args";;
|
||||
*zsh) args="-l -i $args";;
|
||||
esac
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
$SHELL $args "./mpv --player-operation-mode=pseudo-gui"
|
||||
+1
-5
@@ -87,10 +87,6 @@ end
|
||||
class Libav < TravisDepsBuilder
|
||||
def build_map
|
||||
{
|
||||
"libav-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://libav.org/releases/libav-12.tar.gz'
|
||||
},
|
||||
"libav-git" => {
|
||||
:action => :git,
|
||||
:url => "git://git.libav.org/libav.git"
|
||||
@@ -133,7 +129,7 @@ end
|
||||
class Dependencies < TravisDepsBuilder
|
||||
def deps
|
||||
packages = {
|
||||
'linux' => 'pkg-config fontconfig libfribidi-dev yasm',
|
||||
'linux' => 'pkg-config fontconfig libfribidi-dev yasm libxinerama-dev libxrandr-dev',
|
||||
'osx' => 'pkg-config fontconfig freetype fribidi yasm'
|
||||
}
|
||||
package_manager_update
|
||||
|
||||
@@ -11,7 +11,7 @@ if [ "x$TRAVIS_BRANCH" != "xmaster" ]; then
|
||||
fi
|
||||
|
||||
TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE:-HEAD^..}
|
||||
if git diff --quiet --name-only --exit-code "$TRAVIS_COMMIT_RANGE" DOCS/; then
|
||||
if git diff --quiet --name-only --exit-code "$TRAVIS_COMMIT_RANGE" DOCS/ VERSION; then
|
||||
exit;
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ branches:
|
||||
- appveyor
|
||||
|
||||
environment:
|
||||
global:
|
||||
PATH: C:\msys64\usr\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem
|
||||
matrix:
|
||||
- MSYSTEM: MINGW64
|
||||
- MSYSTEM: MINGW32
|
||||
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "chmap.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "format.h"
|
||||
#include "aframe.h"
|
||||
|
||||
struct mp_aframe {
|
||||
AVFrame *av_frame;
|
||||
// We support channel layouts different from AVFrame channel masks
|
||||
struct mp_chmap chmap;
|
||||
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
|
||||
int format;
|
||||
double pts;
|
||||
};
|
||||
|
||||
static void free_frame(void *ptr)
|
||||
{
|
||||
struct mp_aframe *frame = ptr;
|
||||
av_frame_free(&frame->av_frame);
|
||||
}
|
||||
|
||||
struct mp_aframe *mp_aframe_create(void)
|
||||
{
|
||||
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->av_frame = av_frame_alloc();
|
||||
if (!frame->av_frame)
|
||||
abort();
|
||||
talloc_set_destructor(frame, free_frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
|
||||
{
|
||||
if (!frame)
|
||||
return NULL;
|
||||
|
||||
struct mp_aframe *dst = mp_aframe_create();
|
||||
|
||||
dst->chmap = frame->chmap;
|
||||
dst->format = frame->format;
|
||||
dst->pts = frame->pts;
|
||||
|
||||
if (mp_aframe_is_allocated(frame)) {
|
||||
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
|
||||
abort();
|
||||
} else {
|
||||
// av_frame_ref() would fail.
|
||||
mp_aframe_config_copy(dst, frame);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Revert to state after mp_aframe_create().
|
||||
void mp_aframe_reset(struct mp_aframe *frame)
|
||||
{
|
||||
av_frame_unref(frame->av_frame);
|
||||
frame->chmap.num = 0;
|
||||
frame->format = 0;
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
// Remove all actual audio data and leave only the metadata.
|
||||
void mp_aframe_unref_data(struct mp_aframe *frame)
|
||||
{
|
||||
// In a fucked up way, this is less complex than just unreffing the data.
|
||||
struct mp_aframe *tmp = mp_aframe_create();
|
||||
MPSWAP(struct mp_aframe, *tmp, *frame);
|
||||
mp_aframe_reset(frame);
|
||||
mp_aframe_config_copy(frame, tmp);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
// Return a new reference to the data in av_frame. av_frame itself is not
|
||||
// touched. Returns NULL if not representable, or if input is NULL.
|
||||
// Does not copy the timestamps.
|
||||
struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
{
|
||||
if (!av_frame || av_frame->width > 0 || av_frame->height > 0)
|
||||
return NULL;
|
||||
|
||||
int format = af_from_avformat(av_frame->format);
|
||||
if (!format && av_frame->format != AV_SAMPLE_FMT_NONE)
|
||||
return NULL;
|
||||
|
||||
struct mp_aframe *frame = mp_aframe_create();
|
||||
|
||||
// This also takes care of forcing refcounting.
|
||||
if (av_frame_ref(frame->av_frame, av_frame) < 0)
|
||||
abort();
|
||||
|
||||
frame->format = format;
|
||||
mp_chmap_from_lavc(&frame->chmap, frame->av_frame->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
if (frame->chmap.num != frame->av_frame->channels)
|
||||
mp_chmap_from_channels(&frame->chmap, av_frame->channels);
|
||||
#endif
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Return a new reference to the data in frame. Returns NULL is not
|
||||
// representable (), or if input is NULL.
|
||||
// Does not copy the timestamps.
|
||||
struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame)
|
||||
{
|
||||
if (!frame)
|
||||
return NULL;
|
||||
|
||||
if (af_to_avformat(frame->format) != frame->av_frame->format)
|
||||
return NULL;
|
||||
|
||||
if (!mp_chmap_is_lavc(&frame->chmap))
|
||||
return NULL;
|
||||
|
||||
return av_frame_clone(frame->av_frame);
|
||||
}
|
||||
|
||||
struct AVFrame *mp_aframe_to_avframe_and_unref(struct mp_aframe *frame)
|
||||
{
|
||||
AVFrame *av = mp_aframe_to_avframe(frame);
|
||||
talloc_free(frame);
|
||||
return av;
|
||||
}
|
||||
|
||||
// You must not use this.
|
||||
struct AVFrame *mp_aframe_get_raw_avframe(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame;
|
||||
}
|
||||
|
||||
// Return whether it has associated audio data. (If not, metadata only.)
|
||||
bool mp_aframe_is_allocated(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame->buf[0] || frame->av_frame->extended_data[0];
|
||||
}
|
||||
|
||||
// Clear dst, and then copy the configuration to it.
|
||||
void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
{
|
||||
mp_aframe_reset(dst);
|
||||
|
||||
dst->chmap = src->chmap;
|
||||
dst->format = src->format;
|
||||
dst->pts = src->pts;
|
||||
|
||||
if (av_frame_copy_props(dst->av_frame, src->av_frame) < 0)
|
||||
abort();
|
||||
dst->av_frame->format = src->av_frame->format;
|
||||
dst->av_frame->channel_layout = src->av_frame->channel_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
dst->av_frame->channels = src->av_frame->channels;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return whether a and b use the same physical audio format. Extra metadata
|
||||
// such as PTS, per-frame signalling, and AVFrame side data is not compared.
|
||||
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b)
|
||||
{
|
||||
struct mp_chmap ca = {0}, cb = {0};
|
||||
mp_aframe_get_chmap(a, &ca);
|
||||
mp_aframe_get_chmap(b, &cb);
|
||||
return mp_chmap_equals(&ca, &cb) &&
|
||||
mp_aframe_get_rate(a) == mp_aframe_get_rate(b) &&
|
||||
mp_aframe_get_format(a) == mp_aframe_get_format(b);
|
||||
}
|
||||
|
||||
// Return whether all required format fields have been set.
|
||||
bool mp_aframe_config_is_valid(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->format && frame->chmap.num && frame->av_frame->sample_rate;
|
||||
}
|
||||
|
||||
// Return the pointer to the first sample for each plane. The pointers stay
|
||||
// valid until the next call that mutates frame somehow. You must not write to
|
||||
// the audio data. Returns NULL if no frame allocated.
|
||||
uint8_t **mp_aframe_get_data_ro(struct mp_aframe *frame)
|
||||
{
|
||||
return mp_aframe_is_allocated(frame) ? frame->av_frame->extended_data : NULL;
|
||||
}
|
||||
|
||||
// Like mp_aframe_get_data_ro(), but you can write to the audio data.
|
||||
// Additionally, it will return NULL if copy-on-write fails.
|
||||
uint8_t **mp_aframe_get_data_rw(struct mp_aframe *frame)
|
||||
{
|
||||
if (!mp_aframe_is_allocated(frame))
|
||||
return NULL;
|
||||
if (av_frame_make_writable(frame->av_frame) < 0)
|
||||
return NULL;
|
||||
return frame->av_frame->extended_data;
|
||||
}
|
||||
|
||||
int mp_aframe_get_format(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->format;
|
||||
}
|
||||
|
||||
bool mp_aframe_get_chmap(struct mp_aframe *frame, struct mp_chmap *out)
|
||||
{
|
||||
if (!mp_chmap_is_valid(&frame->chmap))
|
||||
return false;
|
||||
*out = frame->chmap;
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_aframe_get_channels(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->chmap.num;
|
||||
}
|
||||
|
||||
int mp_aframe_get_rate(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame->sample_rate;
|
||||
}
|
||||
|
||||
int mp_aframe_get_size(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->av_frame->nb_samples;
|
||||
}
|
||||
|
||||
double mp_aframe_get_pts(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->pts;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_format(struct mp_aframe *frame, int format)
|
||||
{
|
||||
if (mp_aframe_is_allocated(frame))
|
||||
return false;
|
||||
enum AVSampleFormat av_format = af_to_avformat(format);
|
||||
if (av_format == AV_SAMPLE_FMT_NONE && format) {
|
||||
if (!af_fmt_is_spdif(format))
|
||||
return false;
|
||||
av_format = AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
frame->format = format;
|
||||
frame->av_frame->format = av_format;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in)
|
||||
{
|
||||
if (!mp_chmap_is_valid(in) && !mp_chmap_is_empty(in))
|
||||
return false;
|
||||
if (mp_aframe_is_allocated(frame) && in->num != frame->chmap.num)
|
||||
return false;
|
||||
uint64_t lavc_layout = mp_chmap_to_lavc_unchecked(in);
|
||||
if (!lavc_layout && in->num)
|
||||
return false;
|
||||
frame->chmap = *in;
|
||||
frame->av_frame->channel_layout = lavc_layout;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
frame->av_frame->channels = frame->chmap.num;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate)
|
||||
{
|
||||
if (rate < 1 && rate > 10000000)
|
||||
return false;
|
||||
frame->av_frame->sample_rate = rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_size(struct mp_aframe *frame, int samples)
|
||||
{
|
||||
if (!mp_aframe_is_allocated(frame) || mp_aframe_get_size(frame) < samples)
|
||||
return false;
|
||||
frame->av_frame->nb_samples = MPMAX(samples, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mp_aframe_set_pts(struct mp_aframe *frame, double pts)
|
||||
{
|
||||
frame->pts = pts;
|
||||
}
|
||||
|
||||
// Return number of data pointers.
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame)
|
||||
{
|
||||
return af_fmt_is_planar(mp_aframe_get_format(frame))
|
||||
? mp_aframe_get_channels(frame) : 1;
|
||||
}
|
||||
|
||||
// Return number of bytes between 2 consecutive samples on the same plane.
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame)
|
||||
{
|
||||
int format = mp_aframe_get_format(frame);
|
||||
return af_fmt_to_bytes(format) *
|
||||
(af_fmt_is_planar(format) ? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_aframe_get_size(f));
|
||||
|
||||
int num_planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
f->av_frame->extended_data[n] += samples * sstride;
|
||||
|
||||
f->av_frame->nb_samples -= samples;
|
||||
|
||||
if (f->pts != MP_NOPTS_VALUE)
|
||||
f->pts += samples / (double)mp_aframe_get_rate(f);
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_aframe_end_pts(struct mp_aframe *f)
|
||||
{
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Return the duration in seconds of the frame (0 if invalid).
|
||||
double mp_aframe_duration(struct mp_aframe *f)
|
||||
{
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (rate < 1)
|
||||
return 0;
|
||||
return f->av_frame->nb_samples / (double)rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_aframe_end_pts(f);
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
f->av_frame->nb_samples = 0;
|
||||
} else {
|
||||
int new = (end - f->pts) * rate;
|
||||
f->av_frame->nb_samples = MPCLAMP(new, 0, f->av_frame->nb_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != MP_NOPTS_VALUE) {
|
||||
if (f->pts < start) {
|
||||
if (f_end <= start) {
|
||||
f->av_frame->nb_samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
int skip = (start - f->pts) * rate;
|
||||
skip = MPCLAMP(skip, 0, f->av_frame->nb_samples);
|
||||
mp_aframe_skip_samples(f, skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
};
|
||||
|
||||
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent)
|
||||
{
|
||||
return talloc_zero(ta_parent, struct mp_aframe_pool);
|
||||
}
|
||||
|
||||
static void mp_aframe_pool_destructor(void *p)
|
||||
{
|
||||
struct mp_aframe_pool *pool = p;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
}
|
||||
|
||||
// Like mp_aframe_allocate(), but use the pool to allocate data.
|
||||
int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame,
|
||||
int samples)
|
||||
{
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
int size = plane_size * planes;
|
||||
|
||||
if (size <= 0 || mp_aframe_is_allocated(frame))
|
||||
return -1;
|
||||
|
||||
if (!pool->avpool || size > pool->element_size) {
|
||||
size_t alloc = ta_calc_prealloc_elems(size);
|
||||
if (alloc >= INT_MAX)
|
||||
return -1;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
pool->element_size = alloc;
|
||||
pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
|
||||
if (!pool->avpool)
|
||||
return -1;
|
||||
talloc_set_destructor(pool, mp_aframe_pool_destructor);
|
||||
}
|
||||
|
||||
// Yes, you have to do all this shit manually.
|
||||
// At least it's less stupid than av_frame_get_buffer(), which just wipes
|
||||
// the entire frame struct on error for no reason.
|
||||
AVFrame *av_frame = frame->av_frame;
|
||||
if (av_frame->extended_data != av_frame->data)
|
||||
av_freep(&av_frame->extended_data); // sigh
|
||||
av_frame->extended_data =
|
||||
av_mallocz_array(planes, sizeof(av_frame->extended_data[0]));
|
||||
if (!av_frame->extended_data)
|
||||
abort();
|
||||
av_frame->buf[0] = av_buffer_pool_get(pool->avpool);
|
||||
if (!av_frame->buf[0])
|
||||
return -1;
|
||||
av_frame->linesize[0] = samples * sstride;
|
||||
for (int n = 0; n < planes; n++)
|
||||
av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size;
|
||||
av_frame->nb_samples = samples;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
struct mp_chmap;
|
||||
|
||||
struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame);
|
||||
struct mp_aframe *mp_aframe_create(void);
|
||||
struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame);
|
||||
|
||||
void mp_aframe_reset(struct mp_aframe *frame);
|
||||
void mp_aframe_unref_data(struct mp_aframe *frame);
|
||||
|
||||
struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame);
|
||||
struct AVFrame *mp_aframe_to_avframe_and_unref(struct mp_aframe *frame);
|
||||
struct AVFrame *mp_aframe_get_raw_avframe(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_is_allocated(struct mp_aframe *frame);
|
||||
|
||||
void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src);
|
||||
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b);
|
||||
bool mp_aframe_config_is_valid(struct mp_aframe *frame);
|
||||
|
||||
uint8_t **mp_aframe_get_data_ro(struct mp_aframe *frame);
|
||||
uint8_t **mp_aframe_get_data_rw(struct mp_aframe *frame);
|
||||
|
||||
int mp_aframe_get_format(struct mp_aframe *frame);
|
||||
bool mp_aframe_get_chmap(struct mp_aframe *frame, struct mp_chmap *out);
|
||||
int mp_aframe_get_channels(struct mp_aframe *frame);
|
||||
int mp_aframe_get_rate(struct mp_aframe *frame);
|
||||
int mp_aframe_get_size(struct mp_aframe *frame);
|
||||
double mp_aframe_get_pts(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_set_format(struct mp_aframe *frame, int format);
|
||||
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in);
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate);
|
||||
bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
|
||||
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
|
||||
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame);
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
|
||||
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
|
||||
double mp_aframe_end_pts(struct mp_aframe *f);
|
||||
double mp_aframe_duration(struct mp_aframe *f);
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end);
|
||||
|
||||
struct mp_aframe_pool;
|
||||
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent);
|
||||
int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame,
|
||||
int samples);
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
#include "aframe.h"
|
||||
|
||||
static void update_redundant_info(struct mp_audio *mpa)
|
||||
{
|
||||
@@ -403,6 +404,30 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
|
||||
{
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
struct mp_audio *res = mp_audio_from_avframe(av);
|
||||
if (!res)
|
||||
return NULL;
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(aframe, &chmap);
|
||||
mp_audio_set_channels(res, &chmap);
|
||||
mp_audio_set_format(res, mp_aframe_get_format(aframe));
|
||||
res->pts = mp_aframe_get_pts(aframe);
|
||||
return res;
|
||||
}
|
||||
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
|
||||
{
|
||||
*dst = (struct mp_audio){0};
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(src, &chmap);
|
||||
mp_audio_set_channels(dst, &chmap);
|
||||
mp_audio_set_format(dst, mp_aframe_get_format(src));
|
||||
dst->rate = mp_aframe_get_rate(src);
|
||||
}
|
||||
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
|
||||
{
|
||||
av_frame_unref(avframe);
|
||||
|
||||
@@ -83,6 +83,10 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
|
||||
|
||||
struct mp_aframe;
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
@@ -39,7 +39,7 @@ struct ad_functions {
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_audio **out);
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+12
-21
@@ -40,7 +40,6 @@
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
struct mp_audio frame;
|
||||
bool force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
@@ -191,7 +190,7 @@ static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -217,25 +216,18 @@ static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
return true;
|
||||
|
||||
struct mp_chmap lavc_chmap = mpframe->channels;
|
||||
if (lavc_chmap.num != avctx->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avctx->channels);
|
||||
if (priv->force_channel_map) {
|
||||
if (lavc_chmap.num == da->codec->channels.num)
|
||||
lavc_chmap = da->codec->channels;
|
||||
}
|
||||
mp_audio_set_channels(mpframe, &lavc_chmap);
|
||||
if (priv->force_channel_map)
|
||||
mp_aframe_set_chmap(mpframe, &da->codec->channels);
|
||||
|
||||
mpframe->pts = out_pts;
|
||||
if (out_pts == MP_NOPTS_VALUE)
|
||||
out_pts = priv->next_pts;
|
||||
mp_aframe_set_pts(mpframe, out_pts);
|
||||
|
||||
if (mpframe->pts == MP_NOPTS_VALUE)
|
||||
mpframe->pts = priv->next_pts;
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpframe->pts + mpframe->samples / (double)mpframe->rate;
|
||||
priv->next_pts = mp_aframe_end_pts(mpframe);
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
AVFrameSideData *sd =
|
||||
@@ -254,14 +246,14 @@ static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
priv->preroll_done = true;
|
||||
}
|
||||
|
||||
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
|
||||
uint32_t skip = MPMIN(priv->skip_samples, mp_aframe_get_size(mpframe));
|
||||
if (skip) {
|
||||
mp_audio_skip_samples(mpframe, skip);
|
||||
mp_aframe_skip_samples(mpframe, skip);
|
||||
priv->skip_samples -= skip;
|
||||
}
|
||||
uint32_t trim = MPMIN(priv->trim_samples, mpframe->samples);
|
||||
uint32_t trim = MPMIN(priv->trim_samples, mp_aframe_get_size(mpframe));
|
||||
if (trim) {
|
||||
mpframe->samples -= trim;
|
||||
mp_aframe_set_size(mpframe, mp_aframe_get_size(mpframe) - trim);
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
@@ -269,7 +261,6 @@ static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
MP_DBG(da, "Decoded %d samples\n", mpframe->samples);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+29
-12
@@ -40,8 +40,9 @@ struct spdifContext {
|
||||
uint8_t out_buffer[OUTBUF_SIZE];
|
||||
bool need_close;
|
||||
bool use_dts_hd;
|
||||
struct mp_audio fmt;
|
||||
struct mp_audio_pool *pool;
|
||||
struct mp_aframe *fmt;
|
||||
int sstride;
|
||||
struct mp_aframe_pool *pool;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
};
|
||||
@@ -84,7 +85,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_audio_pool_create(spdif_ctx);
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
@@ -198,6 +199,9 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
spdif_ctx->fmt = mp_aframe_create();
|
||||
talloc_steal(spdif_ctx, spdif_ctx->fmt);
|
||||
|
||||
int num_channels = 0;
|
||||
int sample_format = 0;
|
||||
int samplerate = 0;
|
||||
@@ -246,9 +250,14 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
mp_audio_set_num_channels(&spdif_ctx->fmt, num_channels);
|
||||
mp_audio_set_format(&spdif_ctx->fmt, sample_format);
|
||||
spdif_ctx->fmt.rate = samplerate;
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, num_channels);
|
||||
mp_aframe_set_chmap(spdif_ctx->fmt, &chmap);
|
||||
mp_aframe_set_format(spdif_ctx->fmt, sample_format);
|
||||
mp_aframe_set_rate(spdif_ctx->fmt, samplerate);
|
||||
|
||||
spdif_ctx->sstride = mp_aframe_get_sstride(spdif_ctx->fmt);
|
||||
|
||||
if (avformat_write_header(lavf_ctx, &format_opts) < 0) {
|
||||
MP_FATAL(da, "libavformat spdif initialization failed.\n");
|
||||
@@ -279,7 +288,7 @@ static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
@@ -308,13 +317,21 @@ static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
|
||||
goto done;
|
||||
}
|
||||
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
|
||||
*out = mp_audio_pool_get(spdif_ctx->pool, &spdif_ctx->fmt, samples);
|
||||
if (!*out)
|
||||
*out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, *out, samples) < 0) {
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
(*out)->pts = pts;
|
||||
uint8_t **data = mp_aframe_get_data_rw(*out);
|
||||
if (!data) {
|
||||
TA_FREEP(out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(data[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
mp_aframe_set_pts(*out, pts);
|
||||
|
||||
done:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
|
||||
+13
-20
@@ -38,8 +38,6 @@
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
@@ -179,25 +177,24 @@ static void fix_audio_pts(struct dec_audio *da)
|
||||
if (!da->current_frame)
|
||||
return;
|
||||
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE) {
|
||||
double newpts = da->current_frame->pts;
|
||||
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - newpts);
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - frame_pts);
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - newpts) > 0.001)
|
||||
da->pts = newpts;
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - frame_pts) > 0.001)
|
||||
da->pts = frame_pts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
da->current_frame->pts = da->pts;
|
||||
mp_aframe_set_pts(da->current_frame, da->pts);
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
da->pts += da->current_frame->samples / (double)da->current_frame->rate;
|
||||
da->pts += mp_aframe_duration(da->current_frame);
|
||||
}
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
@@ -228,11 +225,6 @@ void audio_work(struct dec_audio *da)
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
if (da->current_frame && !mp_audio_config_valid(da->current_frame)) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
@@ -242,10 +234,11 @@ void audio_work(struct dec_audio *da)
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = da->current_frame->pts >= da->end;
|
||||
if (da->current_frame->samples == 0) {
|
||||
mp_aframe_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = frame_pts >= da->end;
|
||||
if (mp_aframe_get_size(da->current_frame) == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
@@ -280,7 +273,7 @@ void audio_work(struct dec_audio *da)
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_audio **out_frame)
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
|
||||
@@ -19,11 +19,10 @@
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_audio_buffer;
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
@@ -48,7 +47,7 @@ struct dec_audio {
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_audio *current_frame;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
@@ -57,7 +56,7 @@ int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_audio **out_frame);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
double playback_speed;
|
||||
bool is_resampling;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_audio avrctx_fmt; // output format of avrctx
|
||||
struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
|
||||
@@ -332,6 +333,8 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
|
||||
s->is_resampling = false;
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
|
||||
MP_ERR(af, "Cannot open Libavresample Context. \n");
|
||||
goto error;
|
||||
@@ -518,9 +521,7 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
|
||||
if (s->avrctx) {
|
||||
if (s->avrctx && !(!s->is_resampling && new_rate == s->in_rate)) {
|
||||
AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate,
|
||||
INT_MAX / 2);
|
||||
// Essentially, swr/avresample_set_compensation() does 2 things:
|
||||
@@ -531,10 +532,13 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
// feeding it, until the next filter() call.
|
||||
int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1);
|
||||
r = (AVRational){ r.num * mult, r.den * mult };
|
||||
if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) < 0)
|
||||
need_reinit = true;
|
||||
if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) >= 0) {
|
||||
new_rate = s->in_rate;
|
||||
s->is_resampling = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01;
|
||||
if (need_reinit && new_rate != s->in_rate) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
|
||||
+5
-6
@@ -26,7 +26,6 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
@@ -480,12 +479,12 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
|
||||
// --- The following functions just return immutable information.
|
||||
|
||||
void ao_get_format(struct ao *ao, struct mp_audio *format)
|
||||
void ao_get_format(struct ao *ao,
|
||||
int *samplerate, int *format, struct mp_chmap *channels)
|
||||
{
|
||||
*format = (struct mp_audio){0};
|
||||
mp_audio_set_format(format, ao->format);
|
||||
mp_audio_set_channels(format, &ao->channels);
|
||||
format->rate = ao->samplerate;
|
||||
*samplerate = ao->samplerate;
|
||||
*format = ao->format;
|
||||
*channels = ao->channels;
|
||||
}
|
||||
|
||||
const char *ao_get_name(struct ao *ao)
|
||||
|
||||
+2
-2
@@ -81,7 +81,6 @@ struct ao;
|
||||
struct mpv_global;
|
||||
struct input_ctx;
|
||||
struct encode_lavc_context;
|
||||
struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
int init_flags,
|
||||
@@ -89,7 +88,8 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
void ao_uninit(struct ao *ao);
|
||||
void ao_get_format(struct ao *ao, struct mp_audio *format);
|
||||
void ao_get_format(struct ao *ao,
|
||||
int *samplerate, int *format, struct mp_chmap *channels);
|
||||
const char *ao_get_name(struct ao *ao);
|
||||
const char *ao_get_description(struct ao *ao);
|
||||
bool ao_untimed(struct ao *ao);
|
||||
|
||||
+3
-2
@@ -387,8 +387,9 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
}
|
||||
}
|
||||
|
||||
ao->period_size = channels.num * af_fmt_to_bytes(format);
|
||||
p->outburst -= p->outburst % ao->period_size; // round down
|
||||
int sstride = channels.num * af_fmt_to_bytes(format);
|
||||
p->outburst -= p->outburst % sstride; // round down
|
||||
ao->period_size = p->outburst / sstride;
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ static int init(struct ao *ao)
|
||||
if (rsd_init(&priv->rd) < 0)
|
||||
return -1;
|
||||
|
||||
if (ao->device)
|
||||
rsd_set_param(priv->rd, RSD_HOST, ao->device);
|
||||
|
||||
// Actual channel layout unknown.
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext_def(&sel);
|
||||
|
||||
@@ -201,9 +201,9 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
mpthread_set_name("wasapi event");
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
state->init_ret = wasapi_thread_init(ao);
|
||||
state->init_ok = wasapi_thread_init(ao);
|
||||
SetEvent(state->hInitDone);
|
||||
if (FAILED(state->init_ret))
|
||||
if (!state->init_ok)
|
||||
goto exit_label;
|
||||
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
@@ -302,7 +302,7 @@ static int init(struct ao *ao)
|
||||
state->dispatch = mp_dispatch_create(state);
|
||||
mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao);
|
||||
|
||||
state->init_ret = E_FAIL;
|
||||
state->init_ok = false;
|
||||
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
|
||||
if (!state->hAudioThread) {
|
||||
MP_FATAL(ao, "Failed to create audio thread\n");
|
||||
@@ -312,7 +312,7 @@ static int init(struct ao *ao)
|
||||
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
|
||||
SAFE_DESTROY(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
if (FAILED(state->init_ret)) {
|
||||
if (!state->init_ok) {
|
||||
if (!ao->probing)
|
||||
MP_FATAL(ao, "Received failure from audio thread\n");
|
||||
uninit(ao);
|
||||
|
||||
+6
-14
@@ -45,15 +45,6 @@ typedef struct change_notify {
|
||||
HRESULT wasapi_change_init(struct ao* ao, bool is_hotplug);
|
||||
void wasapi_change_uninit(struct ao* ao);
|
||||
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_DESTROY(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#define mp_format_res_str(hres) \
|
||||
(SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
|
||||
? "unsupported" : mp_HRESULT_to_str(hres))
|
||||
|
||||
enum wasapi_thread_state {
|
||||
WASAPI_THREAD_FEED = 0,
|
||||
WASAPI_THREAD_RESUME,
|
||||
@@ -64,8 +55,8 @@ enum wasapi_thread_state {
|
||||
typedef struct wasapi_state {
|
||||
struct mp_log *log;
|
||||
|
||||
bool init_ok; // status of init phase
|
||||
// Thread handles
|
||||
HRESULT init_ret; // status of init phase
|
||||
HANDLE hInitDone; // set when init is complete in audio thread
|
||||
HANDLE hAudioThread; // the audio thread itself
|
||||
HANDLE hWake; // thread wakeup event
|
||||
@@ -113,11 +104,12 @@ void wasapi_list_devs(struct ao *ao, struct ao_device_list *list);
|
||||
bstr wasapi_get_specified_device_string(struct ao *ao);
|
||||
LPWSTR wasapi_find_deviceID(struct ao *ao);
|
||||
|
||||
void wasapi_dispatch(struct ao *ao);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
bool wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
void wasapi_receive_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
#define EXIT_ON_ERROR(hres) \
|
||||
do { if (FAILED(hres)) { goto exit_label; } } while(0)
|
||||
#define SAFE_DESTROY(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#endif
|
||||
+130
-155
@@ -81,7 +81,7 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
|
||||
{AF_FORMAT_S32, 32, 32, &KSDATAFORMAT_SUBTYPE_PCM},
|
||||
// compatible, assume LSBs are ignored
|
||||
{AF_FORMAT_S32, 32, 24, &KSDATAFORMAT_SUBTYPE_PCM},
|
||||
// aka S24 (with conversion) - untested, thus commented.
|
||||
// aka S24 (with conversion on output)
|
||||
{AF_FORMAT_S32, 24, 24, &KSDATAFORMAT_SUBTYPE_PCM},
|
||||
{AF_FORMAT_FLOAT, 32, 32, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT},
|
||||
{AF_FORMAT_S_AC3, 16, 16, &mp_KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL},
|
||||
@@ -94,6 +94,20 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
|
||||
{0},
|
||||
};
|
||||
|
||||
static void wasapi_get_best_sample_formats(
|
||||
int src_format, struct wasapi_sample_fmt *out_formats)
|
||||
{
|
||||
int mp_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(src_format, mp_formats);
|
||||
for (int n = 0; mp_formats[n]; n++) {
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
if (wasapi_formats[i].mp_format == mp_formats[n])
|
||||
*out_formats++ = wasapi_formats[i];
|
||||
}
|
||||
}
|
||||
*out_formats = (struct wasapi_sample_fmt) {0};
|
||||
}
|
||||
|
||||
static const GUID *format_to_subtype(int format)
|
||||
{
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
@@ -103,18 +117,6 @@ static const GUID *format_to_subtype(int format)
|
||||
return &KSDATAFORMAT_SPECIFIER_NONE;
|
||||
}
|
||||
|
||||
// "solve" the under-determined inverse of format_to_subtype by assuming the
|
||||
// input subtype is "special" (i.e. IEC61937)
|
||||
static int special_subtype_to_format(const GUID *subtype)
|
||||
{
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
if (IsEqualGUID(subtype, wasapi_formats[i].subtype) &&
|
||||
af_fmt_is_spdif(wasapi_formats[i].mp_format))
|
||||
return wasapi_formats[i].mp_format;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey)
|
||||
{
|
||||
buf = mp_GUID_to_str_buf(buf, buf_size, &pkey->fmtid);
|
||||
@@ -132,54 +134,21 @@ static void update_waveformat_datarate(WAVEFORMATEXTENSIBLE *wformat)
|
||||
}
|
||||
|
||||
static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat,
|
||||
struct wasapi_sample_fmt format,
|
||||
struct wasapi_sample_fmt *format,
|
||||
DWORD samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
wformat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wformat->Format.nChannels = channels->num;
|
||||
wformat->Format.nSamplesPerSec = samplerate;
|
||||
wformat->Format.wBitsPerSample = format.bits;
|
||||
wformat->Format.wBitsPerSample = format->bits;
|
||||
wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
|
||||
wformat->SubFormat = *format_to_subtype(format.mp_format);
|
||||
wformat->Samples.wValidBitsPerSample = format.used_msb;
|
||||
wformat->SubFormat = *format_to_subtype(format->mp_format);
|
||||
wformat->Samples.wValidBitsPerSample = format->used_msb;
|
||||
wformat->dwChannelMask = mp_chmap_to_waveext(channels);
|
||||
update_waveformat_datarate(wformat);
|
||||
}
|
||||
|
||||
// This implicitly transforms all pcm formats to: interleaved / signed (except
|
||||
// 8-bit is unsigned) / waveext channel order. "Special" formats should be
|
||||
// exempt as they should already satisfy these properties.
|
||||
static void set_waveformat_with_ao(WAVEFORMATEXTENSIBLE *wformat, struct ao *ao)
|
||||
{
|
||||
struct mp_chmap channels = ao->channels;
|
||||
|
||||
if (mp_chmap_is_unknown(&channels))
|
||||
mp_chmap_from_channels(&channels, channels.num);
|
||||
mp_chmap_reorder_to_waveext(&channels);
|
||||
if (!mp_chmap_is_valid(&channels))
|
||||
mp_chmap_from_channels(&channels, 2);
|
||||
|
||||
// First find a format that is actually representable.
|
||||
// (Notably excludes AF_FORMAT_DOUBLE.)
|
||||
struct wasapi_sample_fmt format =
|
||||
{AF_FORMAT_S16, 16, 16, &KSDATAFORMAT_SUBTYPE_PCM};
|
||||
|
||||
int alt_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, alt_formats);
|
||||
for (int n = 0; alt_formats[n]; n++) {
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
if (wasapi_formats[i].mp_format == alt_formats[n]) {
|
||||
format = wasapi_formats[i];
|
||||
goto found_format;
|
||||
}
|
||||
}
|
||||
}
|
||||
found_format:
|
||||
|
||||
set_waveformat(wformat, format, ao->samplerate, &channels);
|
||||
}
|
||||
|
||||
// other wformat parameters must already be set with set_waveformat
|
||||
static void change_waveformat_samplerate(WAVEFORMATEXTENSIBLE *wformat,
|
||||
DWORD samplerate)
|
||||
@@ -319,9 +288,20 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
|
||||
}
|
||||
waveformat_copy(&state->format, wf);
|
||||
state->share_mode = share_mode;
|
||||
|
||||
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz -> %s (%s)\n",
|
||||
mp_chmap_to_str(&ao->channels),
|
||||
af_fmt_to_str(ao->format), ao->samplerate,
|
||||
waveformat_to_str(wf),
|
||||
state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE
|
||||
? "exclusive" : "shared");
|
||||
return true;
|
||||
}
|
||||
|
||||
#define mp_format_res_str(hres) \
|
||||
(SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
|
||||
? "unsupported" : mp_HRESULT_to_str(hres))
|
||||
|
||||
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
@@ -333,36 +313,15 @@ static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
// This works like try_format_exclusive(), but will try to fallback to the AC3
|
||||
// format if the format is a non-AC3 passthrough format. *wformat will be
|
||||
// adjusted accordingly.
|
||||
static bool try_format_exclusive_with_spdif_fallback(struct ao *ao,
|
||||
WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
int special_format = special_subtype_to_format(&wformat->SubFormat);
|
||||
if (special_format && special_format != AF_FORMAT_S_AC3) {
|
||||
MP_VERBOSE(ao, "Retrying as AC3.\n");
|
||||
wformat->SubFormat = *format_to_subtype(AF_FORMAT_S_AC3);
|
||||
return try_format_exclusive(ao, wformat);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool search_sample_formats(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat,
|
||||
int samplerate, struct mp_chmap *channels)
|
||||
{
|
||||
int alt_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, alt_formats);
|
||||
for (int n = 0; alt_formats[n]; n++) {
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
if (wasapi_formats[i].mp_format == alt_formats[n]) {
|
||||
set_waveformat(wformat, wasapi_formats[i], samplerate, channels);
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
struct wasapi_sample_fmt alt_formats[MP_ARRAY_SIZE(wasapi_formats)];
|
||||
wasapi_get_best_sample_formats(ao->format, alt_formats);
|
||||
for (int n = 0; alt_formats[n].mp_format; n++) {
|
||||
set_waveformat(wformat, &alt_formats[n], samplerate, channels);
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
}
|
||||
|
||||
wformat->Format.wBitsPerSample = 0;
|
||||
@@ -441,40 +400,39 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool find_formats_exclusive(struct ao *ao, bool do_search)
|
||||
static bool find_formats_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_waveformat_with_ao(&wformat, ao);
|
||||
// Try the specified format as is
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
|
||||
// Try the requested format as is. If that doesn't work, and the do_search
|
||||
// argument is set, do the pcm format search.
|
||||
if (!try_format_exclusive_with_spdif_fallback(ao, &wformat) &&
|
||||
(!do_search || !search_channels(ao, &wformat)))
|
||||
if (af_fmt_is_spdif(ao->format)) {
|
||||
if (ao->format != AF_FORMAT_S_AC3) {
|
||||
// If the requested format failed and it is passthrough, but not
|
||||
// AC3, try lying and saying it is.
|
||||
MP_VERBOSE(ao, "Retrying as AC3.\n");
|
||||
wformat->SubFormat = *format_to_subtype(AF_FORMAT_S_AC3);
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_EXCLUSIVE))
|
||||
return false;
|
||||
|
||||
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (exclusive) -> %s\n",
|
||||
mp_chmap_to_str(&ao->channels),
|
||||
af_fmt_to_str(ao->format), ao->samplerate,
|
||||
waveformat_to_str(&wformat.Format));
|
||||
return true;
|
||||
// Fallback on the PCM format search
|
||||
return search_channels(ao, wformat);
|
||||
}
|
||||
|
||||
static bool find_formats_shared(struct ao *ao)
|
||||
static bool find_formats_shared(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_waveformat_with_ao(&wformat, ao);
|
||||
|
||||
WAVEFORMATEX *closestMatch;
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
&wformat.Format, &closestMatch);
|
||||
&wformat->Format,
|
||||
&closestMatch);
|
||||
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
|
||||
waveformat_to_str(&wformat.Format), mp_format_res_str(hr));
|
||||
waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
|
||||
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -482,27 +440,20 @@ static bool find_formats_shared(struct ao *ao)
|
||||
case S_OK:
|
||||
break;
|
||||
case S_FALSE:
|
||||
waveformat_copy(&wformat, closestMatch);
|
||||
waveformat_copy(wformat, closestMatch);
|
||||
CoTaskMemFree(closestMatch);
|
||||
MP_VERBOSE(ao, "Closest match is %s\n",
|
||||
waveformat_to_str(&wformat.Format));
|
||||
waveformat_to_str(&wformat->Format));
|
||||
break;
|
||||
default:
|
||||
hr = IAudioClient_GetMixFormat(state->pAudioClient, &closestMatch);
|
||||
EXIT_ON_ERROR(hr);
|
||||
waveformat_copy(&wformat, closestMatch);
|
||||
waveformat_copy(wformat, closestMatch);
|
||||
MP_VERBOSE(ao, "Fallback to mix format %s\n",
|
||||
waveformat_to_str(&wformat.Format));
|
||||
waveformat_to_str(&wformat->Format));
|
||||
CoTaskMemFree(closestMatch);
|
||||
}
|
||||
|
||||
if (!set_ao_format(ao, &wformat.Format, AUDCLNT_SHAREMODE_SHARED))
|
||||
return false;
|
||||
|
||||
MP_VERBOSE(ao, "Accepted as %s %s @ %dhz (shared) -> %s\n",
|
||||
mp_chmap_to_str(&ao->channels),
|
||||
af_fmt_to_str(ao->format), ao->samplerate,
|
||||
waveformat_to_str(&wformat.Format));
|
||||
return true;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error finding shared mode format: %s\n",
|
||||
@@ -513,22 +464,36 @@ exit_label:
|
||||
static bool find_formats(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
struct mp_chmap channels = ao->channels;
|
||||
|
||||
if (state->opt_exclusive) {
|
||||
// If exclusive is requested, try the requested format (which
|
||||
// might be passthrough). If that fails, do a pcm format
|
||||
// search.
|
||||
return find_formats_exclusive(ao, true);
|
||||
} else if (af_fmt_is_spdif(ao->format)) {
|
||||
// If a passthrough format is requested, but exclusive mode
|
||||
// was not explicitly set, try only the requested passthrough
|
||||
// format in exclusive mode. Fall back on shared mode if that
|
||||
// fails without doing the exclusive pcm format search.
|
||||
if (find_formats_exclusive(ao, false))
|
||||
return true;
|
||||
if (mp_chmap_is_unknown(&channels))
|
||||
mp_chmap_from_channels(&channels, channels.num);
|
||||
mp_chmap_reorder_to_waveext(&channels);
|
||||
if (!mp_chmap_is_valid(&channels))
|
||||
mp_chmap_from_channels(&channels, 2);
|
||||
|
||||
struct wasapi_sample_fmt alt_formats[MP_ARRAY_SIZE(wasapi_formats)];
|
||||
wasapi_get_best_sample_formats(ao->format, alt_formats);
|
||||
struct wasapi_sample_fmt wasapi_format =
|
||||
{AF_FORMAT_S16, 16, 16, &KSDATAFORMAT_SUBTYPE_PCM};;
|
||||
if (alt_formats[0].mp_format)
|
||||
wasapi_format = alt_formats[0];
|
||||
|
||||
AUDCLNT_SHAREMODE share_mode;
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_waveformat(&wformat, &wasapi_format, ao->samplerate, &channels);
|
||||
|
||||
if (state->opt_exclusive || af_fmt_is_spdif(ao->format)) {
|
||||
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
|
||||
if(!find_formats_exclusive(ao, &wformat))
|
||||
return false;
|
||||
} else {
|
||||
share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||
if(!find_formats_shared(ao, &wformat))
|
||||
return false;
|
||||
}
|
||||
// Default is to use shared mode
|
||||
return find_formats_shared(ao);
|
||||
|
||||
return set_ao_format(ao, &wformat.Format, share_mode);
|
||||
}
|
||||
|
||||
static HRESULT init_clock(struct wasapi_state *state) {
|
||||
@@ -833,7 +798,7 @@ exit_label:
|
||||
destroy_enumerator(enumerator);
|
||||
}
|
||||
|
||||
static HRESULT load_device(struct mp_log *l,
|
||||
static bool load_device(struct mp_log *l,
|
||||
IMMDevice **ppDevice, LPWSTR deviceID)
|
||||
{
|
||||
IMMDeviceEnumerator *pEnumerator = NULL;
|
||||
@@ -849,7 +814,7 @@ exit_label:
|
||||
if (FAILED(hr))
|
||||
mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr));
|
||||
SAFE_RELEASE(pEnumerator);
|
||||
return hr;
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
static LPWSTR select_device(struct mp_log *l, struct device_desc *d)
|
||||
@@ -934,7 +899,7 @@ exit_label:
|
||||
return deviceID;
|
||||
}
|
||||
|
||||
HRESULT wasapi_thread_init(struct ao *ao)
|
||||
bool wasapi_thread_init(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
MP_DBG(ao, "Init wasapi thread\n");
|
||||
@@ -946,14 +911,18 @@ HRESULT wasapi_thread_init(struct ao *ao)
|
||||
|
||||
retry:
|
||||
if (state->deviceID) {
|
||||
hr = load_device(ao->log, &state->pDevice, state->deviceID);
|
||||
EXIT_ON_ERROR(hr);
|
||||
if (!load_device(ao->log, &state->pDevice, state->deviceID))
|
||||
return false;
|
||||
|
||||
MP_DBG(ao, "Activating pAudioClient interface\n");
|
||||
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
|
||||
CLSCTX_ALL, NULL,
|
||||
(void **)&state->pAudioClient);
|
||||
EXIT_ON_ERROR(hr);
|
||||
if (FAILED(hr)) {
|
||||
MP_FATAL(ao, "Error activating device: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(ao, "Trying UWP wrapper.\n");
|
||||
|
||||
@@ -961,67 +930,73 @@ retry:
|
||||
HANDLE lib = LoadLibraryW(L"wasapiuwp2.dll");
|
||||
if (!lib) {
|
||||
MP_ERR(ao, "Wrapper not found: %d\n", (int)GetLastError());
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
}
|
||||
if (lib) {
|
||||
wuCreateDefaultAudioRenderer =
|
||||
(void*)GetProcAddress(lib, "wuCreateDefaultAudioRenderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
wuCreateDefaultAudioRenderer =
|
||||
(void*)GetProcAddress(lib, "wuCreateDefaultAudioRenderer");
|
||||
if (!wuCreateDefaultAudioRenderer) {
|
||||
MP_ERR(ao, "Function not found.\n");
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
return false;
|
||||
}
|
||||
IUnknown *res = NULL;
|
||||
hr = wuCreateDefaultAudioRenderer(&res);
|
||||
MP_VERBOSE(ao, "Device: %s %p\n", mp_HRESULT_to_str(hr), res);
|
||||
EXIT_ON_ERROR(hr);
|
||||
if (FAILED(hr)) {
|
||||
MP_FATAL(ao, "Error activating device: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
hr = IUnknown_QueryInterface(res, &IID_IAudioClient,
|
||||
(void **)&state->pAudioClient);
|
||||
IUnknown_Release(res);
|
||||
EXIT_ON_ERROR(hr);
|
||||
if (FAILED(hr)) {
|
||||
MP_FATAL(ao, "Failed to get UWP audio client: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In the event of an align hack, we've already done this.
|
||||
if (!align_hack) {
|
||||
MP_DBG(ao, "Probing formats\n");
|
||||
if (!find_formats(ao)) {
|
||||
hr = E_FAIL;
|
||||
EXIT_ON_ERROR(hr);
|
||||
}
|
||||
if (!find_formats(ao))
|
||||
return false;
|
||||
}
|
||||
|
||||
MP_DBG(ao, "Fixing format\n");
|
||||
hr = fix_format(ao, align_hack);
|
||||
switch (hr) {
|
||||
case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
|
||||
if (align_hack)
|
||||
if (align_hack) {
|
||||
MP_FATAL(ao, "Align hack failed\n");
|
||||
break;
|
||||
}
|
||||
// According to MSDN, we must use this as base after the failure.
|
||||
IAudioClient_GetBufferSize(state->pAudioClient,
|
||||
&state->bufferFrameCount);
|
||||
hr = IAudioClient_GetBufferSize(state->pAudioClient,
|
||||
&state->bufferFrameCount);
|
||||
if (FAILED(hr)) {
|
||||
MP_FATAL(ao, "Error getting buffer size for align hack: %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
wasapi_thread_uninit(ao);
|
||||
align_hack = true;
|
||||
MP_WARN(ao, "This appears to require a weird Windows 7 hack. Retrying.\n");
|
||||
goto retry;
|
||||
case AUDCLNT_E_DEVICE_IN_USE:
|
||||
case AUDCLNT_E_DEVICE_INVALIDATED:
|
||||
if (retry_wait > 8)
|
||||
break;
|
||||
if (retry_wait > 8) {
|
||||
MP_FATAL(ao, "Bad device retry failed\n");
|
||||
return false;
|
||||
}
|
||||
wasapi_thread_uninit(ao);
|
||||
MP_WARN(ao, "Retrying in %"PRId64" us\n", retry_wait);
|
||||
mp_sleep_us(retry_wait);
|
||||
retry_wait *= 2;
|
||||
goto retry;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_DBG(ao, "Init wasapi thread done\n");
|
||||
return S_OK;
|
||||
exit_label:
|
||||
MP_FATAL(state, "Error setting up audio thread: %s\n", mp_HRESULT_to_str(hr));
|
||||
return hr;
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
void wasapi_thread_uninit(struct ao *ao)
|
||||
|
||||
@@ -115,6 +115,12 @@ bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2)
|
||||
return rc->x1 > rc->x0 && rc->y1 > rc->y0;
|
||||
}
|
||||
|
||||
bool mp_rect_equals(struct mp_rect *rc1, struct mp_rect *rc2)
|
||||
{
|
||||
return rc1->x0 == rc2->x0 && rc1->y0 == rc2->y0 &&
|
||||
rc1->x1 == rc2->x1 && rc1->y1 == rc2->y1;
|
||||
}
|
||||
|
||||
// This works like snprintf(), except that it starts writing the first output
|
||||
// character to str[strlen(str)]. This returns the number of characters the
|
||||
// string would have *appended* assuming a large enough buffer, will make sure
|
||||
@@ -287,3 +293,12 @@ char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag)
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vsnprintf(buf, buf_size, format, ap);
|
||||
va_end(ap);
|
||||
return buf;
|
||||
}
|
||||
+14
-4
@@ -59,10 +59,11 @@ enum stream_type {
|
||||
};
|
||||
|
||||
enum {
|
||||
DATA_OK = 1,
|
||||
DATA_WAIT = 0,
|
||||
DATA_AGAIN = -1,
|
||||
DATA_EOF = -2,
|
||||
DATA_OK = 1, // data is actually being returned
|
||||
DATA_WAIT = 0, // async wait: check state again after next wakeup
|
||||
DATA_AGAIN = -2, // repeat request (internal progress was made)
|
||||
DATA_STARVE = -1, // need input (might require to drain other outputs)
|
||||
DATA_EOF = -3, // no more data available
|
||||
};
|
||||
|
||||
extern const char mpv_version[];
|
||||
@@ -82,6 +83,7 @@ struct mp_rect {
|
||||
void mp_rect_union(struct mp_rect *rc, const struct mp_rect *src);
|
||||
bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2);
|
||||
bool mp_rect_contains(struct mp_rect *rc, int x, int y);
|
||||
bool mp_rect_equals(struct mp_rect *rc1, struct mp_rect *rc2);
|
||||
|
||||
int mp_snprintf_cat(char *str, size_t size, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
@@ -101,4 +103,12 @@ char *mp_strerror_buf(char *buf, size_t buf_size, int errnum);
|
||||
char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag);
|
||||
#define mp_tag_str(t) mp_tag_str_buf((char[22]){0}, 22, t)
|
||||
|
||||
// Return a printf(format, ...) formatted string of the given SIZE. SIZE must
|
||||
// be a compile time constant. The result is allocated on the stack and valid
|
||||
// only within the current block scope.
|
||||
#define mp_tprintf(SIZE, format, ...) \
|
||||
mp_tprintf_buf((char[SIZE]){0}, (SIZE), (format), __VA_ARGS__)
|
||||
char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
|
||||
#endif /* MPLAYER_MPCOMMON_H */
|
||||
+18
-3
@@ -38,6 +38,10 @@
|
||||
#include <libavutil/display.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#if HAVE_AVUTIL_SPHERICAL
|
||||
#include <libavutil/spherical.h>
|
||||
#endif
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "common/tags.h"
|
||||
#include "common/av_common.h"
|
||||
@@ -234,7 +238,7 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
memcpy(buf, priv->init_fragment.start + priv->stream_pos, ret);
|
||||
priv->stream_pos += ret;
|
||||
} else {
|
||||
ret = stream_read(stream, buf, size);
|
||||
ret = stream_read_partial(stream, buf, size);
|
||||
priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
|
||||
}
|
||||
|
||||
@@ -641,6 +645,19 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->codec->rotate = (((int)(-r) % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
#if HAVE_AVUTIL_SPHERICAL
|
||||
sd = av_stream_get_side_data(st, AV_PKT_DATA_SPHERICAL, NULL);
|
||||
if (sd) {
|
||||
AVSphericalMapping *sp = (void *)sd;
|
||||
struct mp_spherical_params *mpsp = &sh->codec->spherical;
|
||||
mpsp->type = sp->projection == AV_SPHERICAL_EQUIRECTANGULAR ?
|
||||
MP_SPHERICAL_EQUIRECTANGULAR : MP_SPHERICAL_UNKNOWN;
|
||||
mpsp->ref_angles[0] = sp->yaw / (float)(1 << 16);
|
||||
mpsp->ref_angles[1] = sp->pitch / (float)(1 << 16);
|
||||
mpsp->ref_angles[2] = sp->roll / (float)(1 << 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
// This also applies to vfw-muxed mkv, but we can't detect these easily.
|
||||
sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
|
||||
|
||||
@@ -1146,8 +1163,6 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
if (priv) {
|
||||
if (priv->avfc)
|
||||
av_freep(&priv->avfc->key);
|
||||
avformat_close_input(&priv->avfc);
|
||||
if (priv->pb)
|
||||
av_freep(&priv->pb->buffer);
|
||||
|
||||
@@ -115,6 +115,7 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct mp_log *log, char *filenam
|
||||
|
||||
char *fname = talloc_size(mf, strlen(filename) + 32);
|
||||
|
||||
#if HAVE_GLOB
|
||||
if (!strchr(filename, '%')) {
|
||||
strcpy(fname, filename);
|
||||
if (!strchr(filename, '*'))
|
||||
@@ -137,6 +138,7 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct mp_log *log, char *filenam
|
||||
globfree(&gg);
|
||||
goto exit_mf;
|
||||
}
|
||||
#endif
|
||||
|
||||
mp_info(log, "search expr: %s\n", filename);
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ typedef struct mkv_track {
|
||||
uint32_t colorspace;
|
||||
int stereo_mode;
|
||||
struct mp_colorspace color;
|
||||
struct mp_spherical_params spherical;
|
||||
|
||||
uint32_t a_channels, a_bps;
|
||||
float a_sfreq;
|
||||
@@ -584,6 +585,49 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackprojection(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_projection *projection)
|
||||
{
|
||||
if (projection->n_projection_type) {
|
||||
const char *name;
|
||||
switch (projection->projection_type) {
|
||||
case 0:
|
||||
name = "rectangular";
|
||||
track->spherical.type = MP_SPHERICAL_NONE;
|
||||
break;
|
||||
case 1:
|
||||
name = "equirectangular";
|
||||
track->spherical.type = MP_SPHERICAL_EQUIRECTANGULAR;
|
||||
break;
|
||||
default:
|
||||
name = "unknown";
|
||||
track->spherical.type = MP_SPHERICAL_UNKNOWN;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "| + ProjectionType: %s (%"PRIu64")\n", name,
|
||||
projection->projection_type);
|
||||
}
|
||||
if (projection->n_projection_private) {
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPrivate: %zd bytes\n",
|
||||
projection->projection_private.len);
|
||||
MP_WARN(demuxer, "Unknown ProjectionPrivate element.\n");
|
||||
}
|
||||
if (projection->n_projection_pose_yaw) {
|
||||
track->spherical.ref_angles[0] = projection->projection_pose_yaw;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPoseYaw: %f\n",
|
||||
projection->projection_pose_yaw);
|
||||
}
|
||||
if (projection->n_projection_pose_pitch) {
|
||||
track->spherical.ref_angles[1] = projection->projection_pose_pitch;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPosePitch: %f\n",
|
||||
projection->projection_pose_pitch);
|
||||
}
|
||||
if (projection->n_projection_pose_roll) {
|
||||
track->spherical.ref_angles[2] = projection->projection_pose_roll;
|
||||
MP_VERBOSE(demuxer, "| + ProjectionPoseRoll: %f\n",
|
||||
projection->projection_pose_roll);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_video *video)
|
||||
{
|
||||
@@ -628,6 +672,8 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
if (video->n_colour)
|
||||
parse_trackcolour(demuxer, track, &video->colour);
|
||||
if (video->n_projection)
|
||||
parse_trackprojection(demuxer, track, &video->projection);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1454,6 +1500,7 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
|
||||
sh_v->stereo_mode = track->stereo_mode;
|
||||
sh_v->color = track->color;
|
||||
sh_v->spherical = track->spherical;
|
||||
|
||||
done:
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
@@ -1965,6 +2012,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
}
|
||||
if (id == MATROSKA_ID_CLUSTER) {
|
||||
MP_VERBOSE(demuxer, "|+ found cluster\n");
|
||||
mkv_d->cluster_start = start_pos;
|
||||
break;
|
||||
}
|
||||
int res = read_header_element(demuxer, id, start_pos);
|
||||
|
||||
+2
-1
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "video/csputils.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
struct MPOpts;
|
||||
struct demuxer;
|
||||
@@ -93,6 +93,7 @@ struct mp_codec_params {
|
||||
int rotate; // intended display rotation, in degrees, [0, 359]
|
||||
int stereo_mode; // mp_stereo3d_mode (0 if none/unknown)
|
||||
struct mp_colorspace color; // colorspace info where available
|
||||
struct mp_spherical_params spherical;
|
||||
|
||||
// STREAM_VIDEO + STREAM_AUDIO
|
||||
int bits_per_coded_sample;
|
||||
|
||||
+7
-11
@@ -28,21 +28,17 @@
|
||||
# If this is enabled, treat all the following bindings as default.
|
||||
#default-bindings start
|
||||
|
||||
#MOUSE_BTN0 ignore # don't do anything
|
||||
#MOUSE_BTN0_DBL cycle fullscreen # toggle fullscreen on/off
|
||||
#MOUSE_BTN2 cycle pause # toggle pause on/off
|
||||
#MOUSE_BTN3 seek 10
|
||||
#MOUSE_BTN4 seek -10
|
||||
#MOUSE_BTN5 add volume -2
|
||||
#MOUSE_BTN6 add volume 2
|
||||
#MBTN_LEFT ignore # don't do anything
|
||||
#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen on/off
|
||||
#MBTN_RIGHT cycle pause # toggle pause on/off
|
||||
|
||||
# Mouse wheels, touchpad or other input devices that have axes
|
||||
# if the input devices supports precise scrolling it will also scale the
|
||||
# numeric value accordingly
|
||||
#AXIS_UP seek 10
|
||||
#AXIS_DOWN seek -10
|
||||
#AXIS_LEFT seek 5
|
||||
#AXIS_RIGHT seek -5
|
||||
#WHEEL_UP seek 10
|
||||
#WHEEL_DOWN seek -10
|
||||
#WHEEL_LEFT add volume -2
|
||||
#WHEEL_RIGHT add volume 2
|
||||
|
||||
## Seek units are in seconds, but note that these are limited by keyframes
|
||||
#RIGHT seek 5
|
||||
|
||||
@@ -98,9 +98,6 @@ const struct mp_cmd_def mp_cmds[] = {
|
||||
{ MP_CMD_PLAYLIST_SHUFFLE, "playlist-shuffle", },
|
||||
{ MP_CMD_SUB_STEP, "sub-step", { ARG_INT }, .allow_auto_repeat = true },
|
||||
{ MP_CMD_SUB_SEEK, "sub-seek", { ARG_INT }, .allow_auto_repeat = true },
|
||||
#if HAVE_GPL
|
||||
{ MP_CMD_OSD, "osd", { OARG_INT(-1) } },
|
||||
#endif
|
||||
{ MP_CMD_PRINT_TEXT, "print-text", { ARG_STRING }, .allow_auto_repeat = true },
|
||||
{ MP_CMD_SHOW_TEXT, "show-text", { ARG_STRING, OARG_INT(-1), OARG_INT(0) },
|
||||
.allow_auto_repeat = true},
|
||||
|
||||
@@ -46,7 +46,6 @@ enum mp_command_type {
|
||||
MP_CMD_QUIT_WATCH_LATER,
|
||||
MP_CMD_PLAYLIST_NEXT,
|
||||
MP_CMD_PLAYLIST_PREV,
|
||||
MP_CMD_OSD,
|
||||
MP_CMD_SCREENSHOT,
|
||||
MP_CMD_SCREENSHOT_TO_FILE,
|
||||
MP_CMD_SCREENSHOT_RAW,
|
||||
|
||||
+46
-38
@@ -95,7 +95,7 @@ struct cmd_queue {
|
||||
struct mp_cmd *first;
|
||||
};
|
||||
|
||||
struct axis_state {
|
||||
struct wheel_state {
|
||||
double dead_zone_accum;
|
||||
double unit_accum;
|
||||
};
|
||||
@@ -135,11 +135,11 @@ struct input_ctx {
|
||||
bool mouse_mangle, mouse_src_mangle;
|
||||
struct mp_rect mouse_src, mouse_dst;
|
||||
|
||||
// Axis state (MP_AXIS_*)
|
||||
struct axis_state axis_state_y; // MP_AXIS_UP/MP_AXIS_DOWN
|
||||
struct axis_state axis_state_x; // MP_AXIS_LEFT/MP_AXIS_RIGHT
|
||||
struct axis_state *axis_current; // Points to axis currently being scrolled
|
||||
double last_axis_time; // mp_time_sec() of the last axis event
|
||||
// Wheel state (MP_WHEEL_*)
|
||||
struct wheel_state wheel_state_y; // MP_WHEEL_UP/MP_WHEEL_DOWN
|
||||
struct wheel_state wheel_state_x; // MP_WHEEL_LEFT/MP_WHEEL_RIGHT
|
||||
struct wheel_state *wheel_current; // The direction currently being scrolled
|
||||
double last_wheel_time; // mp_time_sec() of the last wheel event
|
||||
|
||||
// List of command binding sections
|
||||
struct cmd_bind_section *cmd_bind_sections;
|
||||
@@ -200,9 +200,9 @@ const struct m_sub_options input_config = {
|
||||
OPT_INTRANGE("input-key-fifo-size", key_fifo_size, 0, 2, 65000),
|
||||
OPT_FLAG("input-cursor", enable_mouse_movements, 0),
|
||||
OPT_FLAG("input-vo-keyboard", vo_key_input, 0),
|
||||
OPT_FLAG("input-media-keys", use_media_keys, 0),
|
||||
#if HAVE_COCOA
|
||||
OPT_FLAG("input-appleremote", use_appleremote, 0),
|
||||
OPT_FLAG("input-media-keys", use_media_keys, 0),
|
||||
#endif
|
||||
OPT_FLAG("window-dragging", allow_win_drag, 0),
|
||||
OPT_REPLACED("input-x11-keyboard", "input-vo-keyboard"),
|
||||
@@ -216,9 +216,9 @@ const struct m_sub_options input_config = {
|
||||
.ar_rate = 40,
|
||||
.use_alt_gr = 1,
|
||||
.enable_mouse_movements = 1,
|
||||
.use_media_keys = 1,
|
||||
#if HAVE_COCOA
|
||||
.use_appleremote = 1,
|
||||
.use_media_keys = 1,
|
||||
#endif
|
||||
.default_bindings = 1,
|
||||
.vo_key_input = 1,
|
||||
@@ -634,10 +634,10 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale,
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-processing for MP_AXIS_* events. If this returns false, the caller
|
||||
// Pre-processing for MP_WHEEL_* events. If this returns false, the caller
|
||||
// should discard the event.
|
||||
static bool process_axis(struct input_ctx *ictx, int code, double *scale,
|
||||
int *scale_units)
|
||||
static bool process_wheel(struct input_ctx *ictx, int code, double *scale,
|
||||
int *scale_units)
|
||||
{
|
||||
// Size of the deadzone in scroll units. The user must scroll at least this
|
||||
// much in any direction before their scroll is registered.
|
||||
@@ -650,45 +650,45 @@ static bool process_axis(struct input_ctx *ictx, int code, double *scale,
|
||||
// sent when the user scrolls slowly.
|
||||
static const double UNIT_SCROLL_TIME = 0.5;
|
||||
|
||||
// Determine which axis is being scrolled
|
||||
// Determine which direction is being scrolled
|
||||
double dir;
|
||||
struct axis_state *state;
|
||||
struct wheel_state *state;
|
||||
switch (code) {
|
||||
case MP_AXIS_UP: dir = -1; state = &ictx->axis_state_y; break;
|
||||
case MP_AXIS_DOWN: dir = +1; state = &ictx->axis_state_y; break;
|
||||
case MP_AXIS_LEFT: dir = -1; state = &ictx->axis_state_x; break;
|
||||
case MP_AXIS_RIGHT: dir = +1; state = &ictx->axis_state_x; break;
|
||||
case MP_WHEEL_UP: dir = -1; state = &ictx->wheel_state_y; break;
|
||||
case MP_WHEEL_DOWN: dir = +1; state = &ictx->wheel_state_y; break;
|
||||
case MP_WHEEL_LEFT: dir = -1; state = &ictx->wheel_state_x; break;
|
||||
case MP_WHEEL_RIGHT: dir = +1; state = &ictx->wheel_state_x; break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reset accumulators if it's determined that the user finished scrolling
|
||||
double now = mp_time_sec();
|
||||
if (now > ictx->last_axis_time + DEADZONE_SCROLL_TIME) {
|
||||
ictx->axis_current = NULL;
|
||||
ictx->axis_state_y.dead_zone_accum = 0;
|
||||
ictx->axis_state_x.dead_zone_accum = 0;
|
||||
if (now > ictx->last_wheel_time + DEADZONE_SCROLL_TIME) {
|
||||
ictx->wheel_current = NULL;
|
||||
ictx->wheel_state_y.dead_zone_accum = 0;
|
||||
ictx->wheel_state_x.dead_zone_accum = 0;
|
||||
}
|
||||
if (now > ictx->last_axis_time + UNIT_SCROLL_TIME) {
|
||||
ictx->axis_state_y.unit_accum = 0;
|
||||
ictx->axis_state_x.unit_accum = 0;
|
||||
if (now > ictx->last_wheel_time + UNIT_SCROLL_TIME) {
|
||||
ictx->wheel_state_y.unit_accum = 0;
|
||||
ictx->wheel_state_x.unit_accum = 0;
|
||||
}
|
||||
ictx->last_axis_time = now;
|
||||
ictx->last_wheel_time = now;
|
||||
|
||||
// Process axis deadzone. A lot of touchpad drivers don't filter scroll
|
||||
// input, which makes it difficult for the user to send AXIS_UP/DOWN
|
||||
// without accidentally triggering AXIS_LEFT/RIGHT. We try to fix this by
|
||||
// implementing a deadzone. When the value of either axis breaks out of the
|
||||
// deadzone, events from the other axis will be ignored until the user
|
||||
// finishes scrolling.
|
||||
if (ictx->axis_current == NULL) {
|
||||
// Process wheel deadzone. A lot of touchpad drivers don't filter scroll
|
||||
// input, which makes it difficult for the user to send WHEEL_UP/DOWN
|
||||
// without accidentally triggering WHEEL_LEFT/RIGHT. We try to fix this by
|
||||
// implementing a deadzone. When the value of either direction breaks out
|
||||
// of the deadzone, events from the other direction will be ignored until
|
||||
// the user finishes scrolling.
|
||||
if (ictx->wheel_current == NULL) {
|
||||
state->dead_zone_accum += *scale * dir;
|
||||
if (state->dead_zone_accum * dir > DEADZONE_DIST) {
|
||||
ictx->axis_current = state;
|
||||
ictx->wheel_current = state;
|
||||
*scale = state->dead_zone_accum * dir;
|
||||
}
|
||||
}
|
||||
if (ictx->axis_current != state)
|
||||
if (ictx->wheel_current != state)
|
||||
return false;
|
||||
|
||||
// Determine scale_units. This is incremented every time the accumulated
|
||||
@@ -724,7 +724,7 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
|
||||
if (!force_mouse && opts->doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
|
||||
return;
|
||||
int units = 1;
|
||||
if (MP_KEY_IS_AXIS(unmod) && !process_axis(ictx, unmod, &scale, &units))
|
||||
if (MP_KEY_IS_WHEEL(unmod) && !process_wheel(ictx, unmod, &scale, &units))
|
||||
return;
|
||||
interpret_key(ictx, code, scale, units);
|
||||
if (code & MP_KEY_STATE_DOWN) {
|
||||
@@ -732,8 +732,8 @@ static void mp_input_feed_key(struct input_ctx *ictx, int code, double scale,
|
||||
if (ictx->last_doubleclick_key_down == code &&
|
||||
now - ictx->last_doubleclick_time < opts->doubleclick_time / 1000.0)
|
||||
{
|
||||
if (code >= MP_MOUSE_BTN0 && code <= MP_MOUSE_BTN2) {
|
||||
interpret_key(ictx, code - MP_MOUSE_BTN0 + MP_MOUSE_BTN0_DBL,
|
||||
if (code >= MP_MBTN_LEFT && code <= MP_MBTN_RIGHT) {
|
||||
interpret_key(ictx, code - MP_MBTN_BASE + MP_MBTN_DBL_BASE,
|
||||
1, 1);
|
||||
}
|
||||
}
|
||||
@@ -766,7 +766,7 @@ void mp_input_put_key_utf8(struct input_ctx *ictx, int mods, struct bstr t)
|
||||
}
|
||||
}
|
||||
|
||||
void mp_input_put_axis(struct input_ctx *ictx, int direction, double value)
|
||||
void mp_input_put_wheel(struct input_ctx *ictx, int direction, double value)
|
||||
{
|
||||
if (value == 0.0)
|
||||
return;
|
||||
@@ -1441,6 +1441,14 @@ bool mp_input_use_alt_gr(struct input_ctx *ictx)
|
||||
return r;
|
||||
}
|
||||
|
||||
bool mp_input_use_media_keys(struct input_ctx *ictx)
|
||||
{
|
||||
input_lock(ictx);
|
||||
bool r = ictx->opts->use_media_keys;
|
||||
input_unlock(ictx);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct mp_cmd *mp_input_parse_cmd(struct input_ctx *ictx, bstr str,
|
||||
const char *location)
|
||||
{
|
||||
|
||||
+4
-1
@@ -143,7 +143,7 @@ void mp_input_put_key_utf8(struct input_ctx *ictx, int mods, struct bstr t);
|
||||
|
||||
// Process scrolling input. Support for precise scrolling. Scales the given
|
||||
// scroll amount add multiplies it with the command (seeking, sub-delay, etc)
|
||||
void mp_input_put_axis(struct input_ctx *ictx, int direction, double value);
|
||||
void mp_input_put_wheel(struct input_ctx *ictx, int direction, double value);
|
||||
|
||||
// Update mouse position (in window coordinates).
|
||||
void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y);
|
||||
@@ -250,6 +250,9 @@ void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c);
|
||||
// characters. If false, count Right Alt as the modifier Alt key.
|
||||
bool mp_input_use_alt_gr(struct input_ctx *ictx);
|
||||
|
||||
// Return true if mpv should intercept keyboard media keys
|
||||
bool mp_input_use_media_keys(struct input_ctx *ictx);
|
||||
|
||||
// Like mp_input_parse_cmd_strv, but also run the command.
|
||||
void mp_input_run_cmd(struct input_ctx *ictx, const char **cmd);
|
||||
|
||||
|
||||
+55
-45
@@ -78,46 +78,29 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_KPDEC, "KP_DEC" },
|
||||
{ MP_KEY_KPINS, "KP_INS" },
|
||||
{ MP_KEY_KPENTER, "KP_ENTER" },
|
||||
{ MP_MOUSE_BTN0, "MOUSE_BTN0" },
|
||||
{ MP_MOUSE_BTN1, "MOUSE_BTN1" },
|
||||
{ MP_MOUSE_BTN2, "MOUSE_BTN2" },
|
||||
{ MP_MOUSE_BTN3, "MOUSE_BTN3" },
|
||||
{ MP_MOUSE_BTN4, "MOUSE_BTN4" },
|
||||
{ MP_MOUSE_BTN5, "MOUSE_BTN5" },
|
||||
{ MP_MOUSE_BTN6, "MOUSE_BTN6" },
|
||||
{ MP_MOUSE_BTN7, "MOUSE_BTN7" },
|
||||
{ MP_MOUSE_BTN8, "MOUSE_BTN8" },
|
||||
{ MP_MOUSE_BTN9, "MOUSE_BTN9" },
|
||||
{ MP_MOUSE_BTN10, "MOUSE_BTN10" },
|
||||
{ MP_MOUSE_BTN11, "MOUSE_BTN11" },
|
||||
{ MP_MOUSE_BTN12, "MOUSE_BTN12" },
|
||||
{ MP_MOUSE_BTN13, "MOUSE_BTN13" },
|
||||
{ MP_MOUSE_BTN14, "MOUSE_BTN14" },
|
||||
{ MP_MOUSE_BTN15, "MOUSE_BTN15" },
|
||||
{ MP_MOUSE_BTN16, "MOUSE_BTN16" },
|
||||
{ MP_MOUSE_BTN17, "MOUSE_BTN17" },
|
||||
{ MP_MOUSE_BTN18, "MOUSE_BTN18" },
|
||||
{ MP_MOUSE_BTN19, "MOUSE_BTN19" },
|
||||
{ MP_MOUSE_BTN0_DBL, "MOUSE_BTN0_DBL" },
|
||||
{ MP_MOUSE_BTN1_DBL, "MOUSE_BTN1_DBL" },
|
||||
{ MP_MOUSE_BTN2_DBL, "MOUSE_BTN2_DBL" },
|
||||
{ MP_MOUSE_BTN3_DBL, "MOUSE_BTN3_DBL" },
|
||||
{ MP_MOUSE_BTN4_DBL, "MOUSE_BTN4_DBL" },
|
||||
{ MP_MOUSE_BTN5_DBL, "MOUSE_BTN5_DBL" },
|
||||
{ MP_MOUSE_BTN6_DBL, "MOUSE_BTN6_DBL" },
|
||||
{ MP_MOUSE_BTN7_DBL, "MOUSE_BTN7_DBL" },
|
||||
{ MP_MOUSE_BTN8_DBL, "MOUSE_BTN8_DBL" },
|
||||
{ MP_MOUSE_BTN9_DBL, "MOUSE_BTN9_DBL" },
|
||||
{ MP_MOUSE_BTN10_DBL, "MOUSE_BTN10_DBL" },
|
||||
{ MP_MOUSE_BTN11_DBL, "MOUSE_BTN11_DBL" },
|
||||
{ MP_MOUSE_BTN12_DBL, "MOUSE_BTN12_DBL" },
|
||||
{ MP_MOUSE_BTN13_DBL, "MOUSE_BTN13_DBL" },
|
||||
{ MP_MOUSE_BTN14_DBL, "MOUSE_BTN14_DBL" },
|
||||
{ MP_MOUSE_BTN15_DBL, "MOUSE_BTN15_DBL" },
|
||||
{ MP_MOUSE_BTN16_DBL, "MOUSE_BTN16_DBL" },
|
||||
{ MP_MOUSE_BTN17_DBL, "MOUSE_BTN17_DBL" },
|
||||
{ MP_MOUSE_BTN18_DBL, "MOUSE_BTN18_DBL" },
|
||||
{ MP_MOUSE_BTN19_DBL, "MOUSE_BTN19_DBL" },
|
||||
{ MP_MBTN_LEFT, "MBTN_LEFT" },
|
||||
{ MP_MBTN_MID, "MBTN_MID" },
|
||||
{ MP_MBTN_RIGHT, "MBTN_RIGHT" },
|
||||
{ MP_WHEEL_UP, "WHEEL_UP" },
|
||||
{ MP_WHEEL_DOWN, "WHEEL_DOWN" },
|
||||
{ MP_WHEEL_LEFT, "WHEEL_LEFT" },
|
||||
{ MP_WHEEL_RIGHT, "WHEEL_RIGHT" },
|
||||
{ MP_MBTN_BACK, "MBTN_BACK" },
|
||||
{ MP_MBTN_FORWARD, "MBTN_FORWARD" },
|
||||
{ MP_MBTN9, "MBTN9" },
|
||||
{ MP_MBTN10, "MBTN10" },
|
||||
{ MP_MBTN11, "MBTN11" },
|
||||
{ MP_MBTN12, "MBTN12" },
|
||||
{ MP_MBTN13, "MBTN13" },
|
||||
{ MP_MBTN14, "MBTN14" },
|
||||
{ MP_MBTN15, "MBTN15" },
|
||||
{ MP_MBTN16, "MBTN16" },
|
||||
{ MP_MBTN17, "MBTN17" },
|
||||
{ MP_MBTN18, "MBTN18" },
|
||||
{ MP_MBTN19, "MBTN19" },
|
||||
{ MP_MBTN_LEFT_DBL, "MBTN_LEFT_DBL" },
|
||||
{ MP_MBTN_MID_DBL, "MBTN_MID_DBL" },
|
||||
{ MP_MBTN_RIGHT_DBL, "MBTN_RIGHT_DBL" },
|
||||
|
||||
{ MP_AR_PLAY, "AR_PLAY" },
|
||||
{ MP_AR_PLAY_HOLD, "AR_PLAY_HOLD" },
|
||||
@@ -134,11 +117,6 @@ static const struct key_name key_names[] = {
|
||||
{ MP_AR_VDOWN, "AR_VDOWN" },
|
||||
{ MP_AR_VDOWN_HOLD, "AR_VDOWN_HOLD" },
|
||||
|
||||
{ MP_AXIS_UP, "AXIS_UP" },
|
||||
{ MP_AXIS_DOWN, "AXIS_DOWN" },
|
||||
{ MP_AXIS_LEFT, "AXIS_LEFT" },
|
||||
{ MP_AXIS_RIGHT, "AXIS_RIGHT" },
|
||||
|
||||
{ MP_KEY_POWER, "POWER" },
|
||||
{ MP_KEY_MENU, "MENU" },
|
||||
{ MP_KEY_PLAY, "PLAY" },
|
||||
@@ -159,6 +137,9 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_SEARCH, "SEARCH" },
|
||||
{ MP_KEY_SLEEP, "SLEEP" },
|
||||
{ MP_KEY_CANCEL, "CANCEL" },
|
||||
{ MP_KEY_RECORD, "RECORD" },
|
||||
{ MP_KEY_CHANNEL_UP, "CHANNEL_UP" },
|
||||
{ MP_KEY_CHANNEL_DOWN,"CHANNEL_DOWN" },
|
||||
|
||||
// These are kept for backward compatibility
|
||||
{ MP_KEY_PAUSE, "XF86_PAUSE" },
|
||||
@@ -166,6 +147,35 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_PREV, "XF86_PREV" },
|
||||
{ MP_KEY_NEXT, "XF86_NEXT" },
|
||||
|
||||
// Deprecated numeric aliases for the mouse buttons
|
||||
{ MP_MBTN_LEFT, "MOUSE_BTN0" },
|
||||
{ MP_MBTN_MID, "MOUSE_BTN1" },
|
||||
{ MP_MBTN_RIGHT, "MOUSE_BTN2" },
|
||||
{ MP_WHEEL_UP, "MOUSE_BTN3" },
|
||||
{ MP_WHEEL_DOWN, "MOUSE_BTN4" },
|
||||
{ MP_WHEEL_LEFT, "MOUSE_BTN5" },
|
||||
{ MP_WHEEL_RIGHT, "MOUSE_BTN6" },
|
||||
{ MP_MBTN_BACK, "MOUSE_BTN7" },
|
||||
{ MP_MBTN_FORWARD, "MOUSE_BTN8" },
|
||||
{ MP_MBTN9, "MOUSE_BTN9" },
|
||||
{ MP_MBTN10, "MOUSE_BTN10" },
|
||||
{ MP_MBTN11, "MOUSE_BTN11" },
|
||||
{ MP_MBTN12, "MOUSE_BTN12" },
|
||||
{ MP_MBTN13, "MOUSE_BTN13" },
|
||||
{ MP_MBTN14, "MOUSE_BTN14" },
|
||||
{ MP_MBTN15, "MOUSE_BTN15" },
|
||||
{ MP_MBTN16, "MOUSE_BTN16" },
|
||||
{ MP_MBTN17, "MOUSE_BTN17" },
|
||||
{ MP_MBTN18, "MOUSE_BTN18" },
|
||||
{ MP_MBTN19, "MOUSE_BTN19" },
|
||||
{ MP_MBTN_LEFT_DBL, "MOUSE_BTN0_DBL" },
|
||||
{ MP_MBTN_MID_DBL, "MOUSE_BTN1_DBL" },
|
||||
{ MP_MBTN_RIGHT_DBL, "MOUSE_BTN2_DBL" },
|
||||
{ MP_WHEEL_UP, "AXIS_UP" },
|
||||
{ MP_WHEEL_DOWN, "AXIS_DOWN" },
|
||||
{ MP_WHEEL_LEFT, "AXIS_LEFT" },
|
||||
{ MP_WHEEL_RIGHT, "AXIS_RIGHT" },
|
||||
|
||||
{ MP_KEY_CLOSE_WIN, "CLOSE_WIN" },
|
||||
{ MP_KEY_MOUSE_MOVE, "MOUSE_MOVE" },
|
||||
{ MP_KEY_MOUSE_LEAVE, "MOUSE_LEAVE" },
|
||||
|
||||
+36
-61
@@ -73,6 +73,9 @@
|
||||
#define MP_KEY_SEARCH (MP_KEY_MM_BASE+17)
|
||||
#define MP_KEY_SLEEP (MP_KEY_MM_BASE+18)
|
||||
#define MP_KEY_CANCEL (MP_KEY_MM_BASE+19)
|
||||
#define MP_KEY_RECORD (MP_KEY_MM_BASE+20)
|
||||
#define MP_KEY_CHANNEL_UP (MP_KEY_MM_BASE+21)
|
||||
#define MP_KEY_CHANNEL_DOWN (MP_KEY_MM_BASE+22)
|
||||
|
||||
/* Function keys */
|
||||
#define MP_KEY_F (MP_KEY_BASE+0x40)
|
||||
@@ -95,57 +98,42 @@
|
||||
#define MP_KEY_KPENTER (MP_KEY_KEYPAD+13)
|
||||
|
||||
// Mouse events from VOs
|
||||
#define MP_MOUSE_BASE ((MP_KEY_BASE+0xA0)|MP_NO_REPEAT_KEY|MP_KEY_EMIT_ON_UP)
|
||||
#define MP_MOUSE_BTN0 (MP_MOUSE_BASE+0)
|
||||
#define MP_MOUSE_BTN1 (MP_MOUSE_BASE+1)
|
||||
#define MP_MOUSE_BTN2 (MP_MOUSE_BASE+2)
|
||||
#define MP_MOUSE_BTN3 (MP_MOUSE_BASE+3)
|
||||
#define MP_MOUSE_BTN4 (MP_MOUSE_BASE+4)
|
||||
#define MP_MOUSE_BTN5 (MP_MOUSE_BASE+5)
|
||||
#define MP_MOUSE_BTN6 (MP_MOUSE_BASE+6)
|
||||
#define MP_MOUSE_BTN7 (MP_MOUSE_BASE+7)
|
||||
#define MP_MOUSE_BTN8 (MP_MOUSE_BASE+8)
|
||||
#define MP_MOUSE_BTN9 (MP_MOUSE_BASE+9)
|
||||
#define MP_MOUSE_BTN10 (MP_MOUSE_BASE+10)
|
||||
#define MP_MOUSE_BTN11 (MP_MOUSE_BASE+11)
|
||||
#define MP_MOUSE_BTN12 (MP_MOUSE_BASE+12)
|
||||
#define MP_MOUSE_BTN13 (MP_MOUSE_BASE+13)
|
||||
#define MP_MOUSE_BTN14 (MP_MOUSE_BASE+14)
|
||||
#define MP_MOUSE_BTN15 (MP_MOUSE_BASE+15)
|
||||
#define MP_MOUSE_BTN16 (MP_MOUSE_BASE+16)
|
||||
#define MP_MOUSE_BTN17 (MP_MOUSE_BASE+17)
|
||||
#define MP_MOUSE_BTN18 (MP_MOUSE_BASE+18)
|
||||
#define MP_MOUSE_BTN19 (MP_MOUSE_BASE+19)
|
||||
#define MP_MOUSE_BTN_END (MP_MOUSE_BASE+20)
|
||||
#define MP_MBTN_BASE ((MP_KEY_BASE+0xA0)|MP_NO_REPEAT_KEY|MP_KEY_EMIT_ON_UP)
|
||||
#define MP_MBTN_LEFT (MP_MBTN_BASE+0)
|
||||
#define MP_MBTN_MID (MP_MBTN_BASE+1)
|
||||
#define MP_MBTN_RIGHT (MP_MBTN_BASE+2)
|
||||
#define MP_WHEEL_UP (MP_MBTN_BASE+3)
|
||||
#define MP_WHEEL_DOWN (MP_MBTN_BASE+4)
|
||||
#define MP_WHEEL_LEFT (MP_MBTN_BASE+5)
|
||||
#define MP_WHEEL_RIGHT (MP_MBTN_BASE+6)
|
||||
#define MP_MBTN_BACK (MP_MBTN_BASE+7)
|
||||
#define MP_MBTN_FORWARD (MP_MBTN_BASE+8)
|
||||
#define MP_MBTN9 (MP_MBTN_BASE+9)
|
||||
#define MP_MBTN10 (MP_MBTN_BASE+10)
|
||||
#define MP_MBTN11 (MP_MBTN_BASE+11)
|
||||
#define MP_MBTN12 (MP_MBTN_BASE+12)
|
||||
#define MP_MBTN13 (MP_MBTN_BASE+13)
|
||||
#define MP_MBTN14 (MP_MBTN_BASE+14)
|
||||
#define MP_MBTN15 (MP_MBTN_BASE+15)
|
||||
#define MP_MBTN16 (MP_MBTN_BASE+16)
|
||||
#define MP_MBTN17 (MP_MBTN_BASE+17)
|
||||
#define MP_MBTN18 (MP_MBTN_BASE+18)
|
||||
#define MP_MBTN19 (MP_MBTN_BASE+19)
|
||||
#define MP_MBTN_END (MP_MBTN_BASE+20)
|
||||
|
||||
#define MP_KEY_IS_MOUSE_BTN_SINGLE(code) \
|
||||
((code) >= MP_MOUSE_BASE && (code) < MP_MOUSE_BTN_END)
|
||||
((code) >= MP_MBTN_BASE && (code) < MP_MBTN_END)
|
||||
#define MP_KEY_IS_WHEEL(code) \
|
||||
((code) >= MP_WHEEL_UP && (code) <= MP_WHEEL_RIGHT)
|
||||
|
||||
#define MP_MOUSE_BASE_DBL ((MP_KEY_BASE+0xC0)|MP_NO_REPEAT_KEY)
|
||||
#define MP_MOUSE_BTN0_DBL (MP_MOUSE_BASE_DBL+0)
|
||||
#define MP_MOUSE_BTN1_DBL (MP_MOUSE_BASE_DBL+1)
|
||||
#define MP_MOUSE_BTN2_DBL (MP_MOUSE_BASE_DBL+2)
|
||||
#define MP_MOUSE_BTN3_DBL (MP_MOUSE_BASE_DBL+3)
|
||||
#define MP_MOUSE_BTN4_DBL (MP_MOUSE_BASE_DBL+4)
|
||||
#define MP_MOUSE_BTN5_DBL (MP_MOUSE_BASE_DBL+5)
|
||||
#define MP_MOUSE_BTN6_DBL (MP_MOUSE_BASE_DBL+6)
|
||||
#define MP_MOUSE_BTN7_DBL (MP_MOUSE_BASE_DBL+7)
|
||||
#define MP_MOUSE_BTN8_DBL (MP_MOUSE_BASE_DBL+8)
|
||||
#define MP_MOUSE_BTN9_DBL (MP_MOUSE_BASE_DBL+9)
|
||||
#define MP_MOUSE_BTN10_DBL (MP_MOUSE_BASE_DBL+10)
|
||||
#define MP_MOUSE_BTN11_DBL (MP_MOUSE_BASE_DBL+11)
|
||||
#define MP_MOUSE_BTN12_DBL (MP_MOUSE_BASE_DBL+12)
|
||||
#define MP_MOUSE_BTN13_DBL (MP_MOUSE_BASE_DBL+13)
|
||||
#define MP_MOUSE_BTN14_DBL (MP_MOUSE_BASE_DBL+14)
|
||||
#define MP_MOUSE_BTN15_DBL (MP_MOUSE_BASE_DBL+15)
|
||||
#define MP_MOUSE_BTN16_DBL (MP_MOUSE_BASE_DBL+16)
|
||||
#define MP_MOUSE_BTN17_DBL (MP_MOUSE_BASE_DBL+17)
|
||||
#define MP_MOUSE_BTN18_DBL (MP_MOUSE_BASE_DBL+18)
|
||||
#define MP_MOUSE_BTN19_DBL (MP_MOUSE_BASE_DBL+19)
|
||||
#define MP_MOUSE_BTN_DBL_END (MP_MOUSE_BASE_DBL+20)
|
||||
#define MP_MBTN_DBL_BASE ((MP_KEY_BASE+0xC0)|MP_NO_REPEAT_KEY)
|
||||
#define MP_MBTN_LEFT_DBL (MP_MBTN_DBL_BASE+0)
|
||||
#define MP_MBTN_MID_DBL (MP_MBTN_DBL_BASE+1)
|
||||
#define MP_MBTN_RIGHT_DBL (MP_MBTN_DBL_BASE+2)
|
||||
#define MP_MBTN_DBL_END (MP_MBTN_DBL_BASE+20)
|
||||
|
||||
#define MP_KEY_IS_MOUSE_BTN_DBL(code) \
|
||||
((code) >= MP_MOUSE_BTN0_DBL && (code) < MP_MOUSE_BTN_DBL_END)
|
||||
((code) >= MP_MBTN_DBL_BASE && (code) < MP_MBTN_DBL_END)
|
||||
|
||||
// Apple Remote input module
|
||||
#define MP_AR_BASE (MP_KEY_BASE+0xE0)
|
||||
@@ -164,17 +152,6 @@
|
||||
#define MP_AR_VDOWN (MP_AR_BASE + 12)
|
||||
#define MP_AR_VDOWN_HOLD (MP_AR_BASE + 13)
|
||||
|
||||
// Mouse wheels or touchpad input
|
||||
#define MP_AXIS_BASE (MP_KEY_BASE+0x100)
|
||||
#define MP_AXIS_UP (MP_AXIS_BASE+0)
|
||||
#define MP_AXIS_DOWN (MP_AXIS_BASE+1)
|
||||
#define MP_AXIS_LEFT (MP_AXIS_BASE+2)
|
||||
#define MP_AXIS_RIGHT (MP_AXIS_BASE+3)
|
||||
#define MP_AXIS_END (MP_AXIS_BASE+4)
|
||||
|
||||
#define MP_KEY_IS_AXIS(code) \
|
||||
((code) >= MP_AXIS_BASE && (code) < MP_AXIS_END)
|
||||
|
||||
// Reserved area. Can be used for keys that have no explicit names assigned,
|
||||
// but should be mappable by the user anyway.
|
||||
#define MP_KEY_UNKNOWN_RESERVED_START (MP_KEY_BASE+0x10000)
|
||||
@@ -197,12 +174,10 @@
|
||||
|
||||
// Whether to dispatch the key binding by current mouse position.
|
||||
#define MP_KEY_DEPENDS_ON_MOUSE_POS(code) \
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || MP_KEY_IS_AXIS(code) || \
|
||||
(code) == MP_KEY_MOUSE_MOVE)
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || (code) == MP_KEY_MOUSE_MOVE)
|
||||
|
||||
#define MP_KEY_IS_MOUSE(code) \
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || MP_KEY_IS_AXIS(code) || \
|
||||
MP_KEY_IS_MOUSE_MOVE(code))
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || MP_KEY_IS_MOUSE_MOVE(code))
|
||||
|
||||
// No input source should generate this.
|
||||
#define MP_KEY_UNMAPPED (MP_KEY_INTERN+4)
|
||||
|
||||
+8
-2
@@ -68,14 +68,20 @@ extern "C" {
|
||||
* defaults. Likewise, mpv will attempt to leave the OpenGL context with
|
||||
* standard defaults. The following state is excluded from this:
|
||||
*
|
||||
* - the current viewport (can have/is set to an arbitrary value)
|
||||
* - the glViewport state
|
||||
* - the glScissor state (but GL_SCISSOR_TEST is in its default value)
|
||||
* - glBlendFuncSeparate() state (but GL_BLEND is in its default value)
|
||||
* - glClearColor() state
|
||||
* - mpv may overwrite the callback set with glDebugMessageCallback()
|
||||
* - mpv always disables GL_DITHER at init
|
||||
*
|
||||
* Messing with the state could be avoided by creating shared OpenGL contexts,
|
||||
* but this is avoided for the sake of compatibility and interoperability.
|
||||
*
|
||||
* On OpenGL 2.1, mpv will strictly call functions like glGenTextures() to
|
||||
* create OpenGL objects. You will have to do the same. This ensures that
|
||||
* objects created by mpv and the API users don't clash.
|
||||
* objects created by mpv and the API users don't clash. Also, legacy state
|
||||
* must be either in its defaults, or not interfere with core state.
|
||||
*
|
||||
* Threading
|
||||
* ---------
|
||||
|
||||
+37
@@ -454,3 +454,40 @@ struct bstr bstr_get_ext(struct bstr s)
|
||||
return (struct bstr){NULL, 0};
|
||||
return bstr_splice(s, dotpos + 1, s.len);
|
||||
}
|
||||
|
||||
static int h_to_i(unsigned char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
|
||||
return -1; // invalid char
|
||||
}
|
||||
|
||||
bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out)
|
||||
{
|
||||
if (!out)
|
||||
return false;
|
||||
|
||||
char *arr = talloc_array(talloc_ctx, char, hex.len / 2);
|
||||
int len = 0;
|
||||
|
||||
while (hex.len >= 2) {
|
||||
int a = h_to_i(hex.start[0]);
|
||||
int b = h_to_i(hex.start[1]);
|
||||
hex = bstr_splice(hex, 2, hex.len);
|
||||
|
||||
if (a < 0 || b < 0) {
|
||||
talloc_free(arr);
|
||||
return false;
|
||||
}
|
||||
|
||||
arr[len++] = (a << 4) | b;
|
||||
}
|
||||
|
||||
*out = (struct bstr){ .start = arr, .len = len };
|
||||
return true;
|
||||
}
|
||||
+8
-1
@@ -80,6 +80,10 @@ double bstrtod(struct bstr str, struct bstr *rest);
|
||||
void bstr_lower(struct bstr str);
|
||||
int bstr_sscanf(struct bstr str, const char *format, ...);
|
||||
|
||||
// Decode a string containing hexadecimal data. All whitespace will be silently
|
||||
// ignored. When successful, this allocates a new array to store the output.
|
||||
bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out);
|
||||
|
||||
// Decode the UTF-8 code point at the start of the string, and return the
|
||||
// character.
|
||||
// After calling this function, *out_next will point to the next character.
|
||||
@@ -189,7 +193,10 @@ static inline int bstrcmp0(struct bstr str1, const char *str2)
|
||||
|
||||
static inline bool bstr_equals(struct bstr str1, struct bstr str2)
|
||||
{
|
||||
return str1.len == str2.len && bstrcmp(str1, str2) == 0;
|
||||
if (str1.len != str2.len)
|
||||
return false;
|
||||
|
||||
return str1.start == str2.start || bstrcmp(str1, str2) == 0;
|
||||
}
|
||||
|
||||
static inline bool bstr_equals0(struct bstr str1, const char *str2)
|
||||
|
||||
@@ -59,6 +59,7 @@ struct mp_dispatch_item {
|
||||
mp_dispatch_fn fn;
|
||||
void *fn_data;
|
||||
bool asynchronous;
|
||||
bool mergeable;
|
||||
bool completed;
|
||||
struct mp_dispatch_item *next;
|
||||
};
|
||||
@@ -113,12 +114,25 @@ static void mp_dispatch_append(struct mp_dispatch_queue *queue,
|
||||
struct mp_dispatch_item *item)
|
||||
{
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
if (item->mergeable) {
|
||||
for (struct mp_dispatch_item *cur = queue->head; cur; cur = cur->next) {
|
||||
if (cur->mergeable && cur->fn == item->fn &&
|
||||
cur->fn_data == item->fn_data)
|
||||
{
|
||||
talloc_free(item);
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queue->tail) {
|
||||
queue->tail->next = item;
|
||||
} else {
|
||||
queue->head = item;
|
||||
}
|
||||
queue->tail = item;
|
||||
|
||||
// Wake up the main thread; note that other threads might wait on this
|
||||
// condition for reasons, so broadcast the condition.
|
||||
pthread_cond_broadcast(&queue->cond);
|
||||
@@ -127,6 +141,7 @@ static void mp_dispatch_append(struct mp_dispatch_queue *queue,
|
||||
if (!queue->wakeup_fn)
|
||||
queue->interrupted = true;
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
|
||||
if (queue->wakeup_fn)
|
||||
queue->wakeup_fn(queue->wakeup_ctx);
|
||||
}
|
||||
@@ -165,6 +180,47 @@ void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_append(queue, item);
|
||||
}
|
||||
|
||||
// Like mp_dispatch_enqueue(), but
|
||||
void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data)
|
||||
{
|
||||
struct mp_dispatch_item *item = talloc_ptrtype(NULL, item);
|
||||
*item = (struct mp_dispatch_item){
|
||||
.fn = fn,
|
||||
.fn_data = fn_data,
|
||||
.mergeable = true,
|
||||
.asynchronous = true,
|
||||
};
|
||||
mp_dispatch_append(queue, item);
|
||||
}
|
||||
|
||||
// Remove already queued item. Only items enqueued with the following functions
|
||||
// can be canceled:
|
||||
// - mp_dispatch_enqueue()
|
||||
// - mp_dispatch_enqueue_notify()
|
||||
// Items which were enqueued, and which are currently executing, can not be
|
||||
// canceled anymore. This function is mostly for being called from the same
|
||||
// context as mp_dispatch_queue_process(), where the "currently executing" case
|
||||
// can be excluded.
|
||||
void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data)
|
||||
{
|
||||
pthread_mutex_lock(&queue->lock);
|
||||
struct mp_dispatch_item **pcur = &queue->head;
|
||||
queue->tail = NULL;
|
||||
while (*pcur) {
|
||||
struct mp_dispatch_item *cur = *pcur;
|
||||
if (cur->fn == fn && cur->fn_data == fn_data) {
|
||||
*pcur = cur->next;
|
||||
talloc_free(cur);
|
||||
} else {
|
||||
queue->tail = cur;
|
||||
pcur = &cur->next;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&queue->lock);
|
||||
}
|
||||
|
||||
// Run fn(fn_data) on the target thread synchronously. This function enqueues
|
||||
// the callback and waits until the target thread is done doing this.
|
||||
// This is redundant to calling the function inside mp_dispatch_[un]lock(),
|
||||
|
||||
@@ -12,6 +12,10 @@ void mp_dispatch_enqueue(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data);
|
||||
void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data);
|
||||
void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data);
|
||||
void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data);
|
||||
void mp_dispatch_run(struct mp_dispatch_queue *queue,
|
||||
mp_dispatch_fn fn, void *fn_data);
|
||||
void mp_dispatch_queue_process(struct mp_dispatch_queue *queue, double timeout);
|
||||
|
||||
+94
-14
@@ -39,6 +39,7 @@
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
#include "misc/dispatch.h"
|
||||
#include "misc/node.h"
|
||||
#include "osdep/atomic.h"
|
||||
|
||||
@@ -57,6 +58,8 @@ struct m_config_shadow {
|
||||
pthread_mutex_t lock;
|
||||
struct m_config *root;
|
||||
char *data;
|
||||
struct m_config_cache **listeners;
|
||||
int num_listeners;
|
||||
};
|
||||
|
||||
// Represents a sub-struct (OPT_SUBSTRUCT()).
|
||||
@@ -156,8 +159,11 @@ static void config_destroy(void *p)
|
||||
m_option_free(co->opt, config->shadow->data + co->shadow_offset);
|
||||
}
|
||||
|
||||
if (config->shadow)
|
||||
if (config->shadow) {
|
||||
// must all have been unregistered
|
||||
assert(config->shadow->num_listeners == 0);
|
||||
pthread_mutex_destroy(&config->shadow->lock);
|
||||
}
|
||||
}
|
||||
|
||||
struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
|
||||
@@ -703,6 +709,8 @@ static int m_config_handle_special_options(struct m_config *config,
|
||||
config->recursion_depth += 1;
|
||||
config->includefunc(config->includefunc_ctx, param, flags);
|
||||
config->recursion_depth -= 1;
|
||||
if (config->recursion_depth == 0 && config->profile_depth == 0)
|
||||
m_config_finish_default_profile(config, flags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -910,19 +918,8 @@ int m_config_set_option_node(struct m_config *config, bstr name,
|
||||
int r;
|
||||
|
||||
struct m_config_option *co = m_config_get_co(config, name);
|
||||
if (!co) {
|
||||
co = m_config_get_co_raw(config, name);
|
||||
if (co && co->opt->type == &m_option_type_cli_alias) {
|
||||
/*bstr old_name = name;
|
||||
co = m_config_mogrify_cli_opt(config, &name, &(bool){0}, &(int){0});
|
||||
*/
|
||||
name = bstr0((char *)co->opt->priv);
|
||||
MP_WARN(config, "Setting %.*s via API is deprecated, set %s instead.\n",
|
||||
BSTR_P(name), co->opt->name);
|
||||
} else {
|
||||
return M_OPT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
if (!co)
|
||||
return M_OPT_UNKNOWN;
|
||||
|
||||
// Do this on an "empty" type to make setting the option strictly overwrite
|
||||
// the old value, as opposed to e.g. appending to lists.
|
||||
@@ -1199,6 +1196,16 @@ static bool is_group_included(struct m_config *config, int group, int parent)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void cache_destroy(void *p)
|
||||
{
|
||||
struct m_config_cache *cache = p;
|
||||
|
||||
// (technically speaking, being able to call them both without anything
|
||||
// breaking is a feature provided by these functions)
|
||||
m_config_cache_set_wakeup_cb(cache, NULL, NULL);
|
||||
m_config_cache_set_dispatch_change_cb(cache, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
struct m_config_cache *m_config_cache_alloc(void *ta_parent,
|
||||
struct mpv_global *global,
|
||||
const struct m_sub_options *group)
|
||||
@@ -1207,6 +1214,7 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
|
||||
struct m_config *root = shadow->root;
|
||||
|
||||
struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache);
|
||||
talloc_set_destructor(cache, cache_destroy);
|
||||
cache->shadow = shadow;
|
||||
cache->shadow_config = m_config_new(cache, mp_null_log, root->size,
|
||||
root->defaults, root->options);
|
||||
@@ -1301,12 +1309,84 @@ void m_config_notify_change_co(struct m_config *config,
|
||||
group = g->parent_group;
|
||||
}
|
||||
|
||||
if (shadow) {
|
||||
pthread_mutex_lock(&shadow->lock);
|
||||
for (int n = 0; n < shadow->num_listeners; n++) {
|
||||
struct m_config_cache *cache = shadow->listeners[n];
|
||||
if (cache->wakeup_cb)
|
||||
cache->wakeup_cb(cache->wakeup_cb_ctx);
|
||||
}
|
||||
pthread_mutex_unlock(&shadow->lock);
|
||||
}
|
||||
|
||||
if (config->option_change_callback) {
|
||||
config->option_change_callback(config->option_change_callback_ctx, co,
|
||||
changed);
|
||||
}
|
||||
}
|
||||
|
||||
void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
|
||||
void (*cb)(void *ctx), void *cb_ctx)
|
||||
{
|
||||
struct m_config_shadow *shadow = cache->shadow;
|
||||
|
||||
pthread_mutex_lock(&shadow->lock);
|
||||
if (cache->in_list) {
|
||||
for (int n = 0; n < shadow->num_listeners; n++) {
|
||||
if (shadow->listeners[n] == cache)
|
||||
MP_TARRAY_REMOVE_AT(shadow->listeners, shadow->num_listeners, n);
|
||||
}
|
||||
if (!shadow->num_listeners) {
|
||||
talloc_free(shadow->listeners);
|
||||
shadow->listeners = NULL;
|
||||
}
|
||||
}
|
||||
if (cb) {
|
||||
MP_TARRAY_APPEND(NULL, shadow->listeners, shadow->num_listeners, cache);
|
||||
cache->in_list = true;
|
||||
cache->wakeup_cb = cb;
|
||||
cache->wakeup_cb_ctx = cb_ctx;
|
||||
}
|
||||
pthread_mutex_unlock(&shadow->lock);
|
||||
}
|
||||
|
||||
static void dispatch_notify(void *p)
|
||||
{
|
||||
struct m_config_cache *cache = p;
|
||||
|
||||
assert(cache->wakeup_dispatch_queue);
|
||||
mp_dispatch_enqueue_notify(cache->wakeup_dispatch_queue,
|
||||
cache->wakeup_dispatch_cb,
|
||||
cache->wakeup_dispatch_cb_ctx);
|
||||
}
|
||||
|
||||
void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
|
||||
struct mp_dispatch_queue *dispatch,
|
||||
void (*cb)(void *ctx), void *cb_ctx)
|
||||
{
|
||||
// Remove the old one is tricky. Firts make sure no new notifications will
|
||||
// come.
|
||||
m_config_cache_set_wakeup_cb(cache, NULL, NULL);
|
||||
// Remove any pending notifications (assume we're on the same thread as
|
||||
// any potential mp_dispatch_queue_process() callers).
|
||||
if (cache->wakeup_dispatch_queue) {
|
||||
mp_dispatch_cancel_fn(cache->wakeup_dispatch_queue,
|
||||
cache->wakeup_dispatch_cb,
|
||||
cache->wakeup_dispatch_cb_ctx);
|
||||
}
|
||||
|
||||
cache->wakeup_dispatch_queue = NULL;
|
||||
cache->wakeup_dispatch_cb = NULL;
|
||||
cache->wakeup_dispatch_cb_ctx = NULL;
|
||||
|
||||
if (cb) {
|
||||
cache->wakeup_dispatch_queue = dispatch;
|
||||
cache->wakeup_dispatch_cb = cb;
|
||||
cache->wakeup_dispatch_cb_ctx = cb_ctx;
|
||||
m_config_cache_set_wakeup_cb(cache, dispatch_notify, cache);
|
||||
}
|
||||
}
|
||||
|
||||
bool m_config_is_in_group(struct m_config *config,
|
||||
const struct m_sub_options *group,
|
||||
struct m_config_option *co)
|
||||
|
||||
+29
-2
@@ -275,9 +275,20 @@ struct m_config_cache {
|
||||
struct m_config *shadow_config;
|
||||
long long ts;
|
||||
int group;
|
||||
bool in_list;
|
||||
// --- Implicitly synchronized by setting/unsetting wakeup_cb.
|
||||
struct mp_dispatch_queue *wakeup_dispatch_queue;
|
||||
void (*wakeup_dispatch_cb)(void *ctx);
|
||||
void *wakeup_dispatch_cb_ctx;
|
||||
// --- Protected by shadow->lock
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_cb_ctx;
|
||||
};
|
||||
|
||||
// Create a mirror copy from the global options.
|
||||
// Keep in mind that a m_config_cache object is not thread-safe; it merely
|
||||
// provides thread-safe access to the global options. All API functions for
|
||||
// the same m_config_cache object must synchronized, unless otherwise noted.
|
||||
// ta_parent: parent for the returned allocation
|
||||
// global: option data source
|
||||
// group: the option group to return. This can be NULL for the global option
|
||||
@@ -287,6 +298,22 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
|
||||
struct mpv_global *global,
|
||||
const struct m_sub_options *group);
|
||||
|
||||
// If any of the options in the group possibly changes, call this callback. The
|
||||
// callback must not actually access the cache or anything option related.
|
||||
// Instead, it must wake up the thread that normally accesses the cache.
|
||||
void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
|
||||
void (*cb)(void *ctx), void *cb_ctx);
|
||||
|
||||
// If any of the options in the group change, call this callback on the given
|
||||
// dispatch queue. This is higher level than m_config_cache_set_wakeup_cb(),
|
||||
// and you can do anything you want in the callback (assuming the dispatch
|
||||
// queue is processed in the same thread that accesses m_config_cache API).
|
||||
// To ensure clean shutdown, you must destroy the m_config_cache (or unset the
|
||||
// callback) before the dispatch queue is destroyed.
|
||||
void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
|
||||
struct mp_dispatch_queue *dispatch,
|
||||
void (*cb)(void *ctx), void *cb_ctx);
|
||||
|
||||
// Update the options in cache->opts to current global values. Return whether
|
||||
// there was an update notification at all (which may or may not indicate that
|
||||
// some options have changed).
|
||||
@@ -295,13 +322,13 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
|
||||
bool m_config_cache_update(struct m_config_cache *cache);
|
||||
|
||||
// Like m_config_cache_alloc(), but return the struct (m_config_cache->opts)
|
||||
// directly, with no way to update the config.
|
||||
// directly, with no way to update the config. Basically this returns a copy
|
||||
// with a snapshot of the current option values.
|
||||
// Warning: does currently not set the child as its own talloc root, which
|
||||
// means the only way to free the struct is by freeing ta_parent.
|
||||
void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
|
||||
const struct m_sub_options *group);
|
||||
|
||||
|
||||
// Read a single global option in a thread-safe way. For multiple options,
|
||||
// use m_config_cache. The option must exist and match the provided type (the
|
||||
// type is used as a sanity check only). Performs semi-expensive lookup.
|
||||
|
||||
+4
-4
@@ -397,9 +397,8 @@ struct m_option {
|
||||
// The following are also part of the M_OPT_* flags, and are used to update
|
||||
// certain groups of options.
|
||||
#define UPDATE_OPT_FIRST (1 << 7)
|
||||
#define UPDATE_TERM (1 << 7) // terminal options
|
||||
#define UPDATE_RENDERER (1 << 8) // mainly vo_opengl options
|
||||
#define UPDATE_VIDEOPOS (1 << 9) // video position (panscan etc.)
|
||||
#define UPDATE_TERM (1 << 7) // terminal options
|
||||
#define UPDATE_DEINT (1 << 8) // --deinterlace
|
||||
#define UPDATE_OSD (1 << 10) // related to OSD rendering
|
||||
#define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl
|
||||
#define UPDATE_IMGPAR (1 << 12) // video image params overrides
|
||||
@@ -408,7 +407,8 @@ struct m_option {
|
||||
#define UPDATE_PRIORITY (1 << 15) // --priority (Windows-only)
|
||||
#define UPDATE_SCREENSAVER (1 << 16) // --stop-screensaver
|
||||
#define UPDATE_VOL (1 << 17) // softvol related options
|
||||
#define UPDATE_OPT_LAST (1 << 17)
|
||||
#define UPDATE_LAVFI_COMPLEX (1 << 18) // --lavfi-complex
|
||||
#define UPDATE_OPT_LAST (1 << 18)
|
||||
|
||||
// All bits between _FIRST and _LAST (inclusive)
|
||||
#define UPDATE_OPTS_MASK \
|
||||
|
||||
@@ -82,11 +82,6 @@ enum mp_property_action {
|
||||
// Pass down an action to a sub-property.
|
||||
// arg: struct m_property_action_arg*
|
||||
M_PROPERTY_KEY_ACTION,
|
||||
|
||||
// Get the (usually constant) value that indicates no change. Obscure
|
||||
// special functionality for things like the volume property.
|
||||
// Otherwise works like M_PROPERTY_GET.
|
||||
M_PROPERTY_GET_NEUTRAL,
|
||||
};
|
||||
|
||||
// Argument for M_PROPERTY_SWITCH
|
||||
|
||||
+21
-42
@@ -142,26 +142,27 @@ static const m_option_t mp_vo_opt_list[] = {
|
||||
OPT_FLAG("force-window-position", force_window_position, 0),
|
||||
OPT_STRING("x11-name", winname, 0),
|
||||
OPT_FLOATRANGE("monitoraspect", force_monitor_aspect, 0, 0.0, 9.0),
|
||||
OPT_FLOATRANGE("monitorpixelaspect", monitor_pixel_aspect, 0, 0.2, 9.0),
|
||||
OPT_FLOATRANGE("monitorpixelaspect", monitor_pixel_aspect, 0, 1.0/32.0, 32.0),
|
||||
OPT_FLAG("fullscreen", fullscreen, 0),
|
||||
OPT_ALIAS("fs", "fullscreen"),
|
||||
OPT_FLAG("native-keyrepeat", native_keyrepeat, 0),
|
||||
OPT_FLOATRANGE("panscan", panscan, UPDATE_VIDEOPOS, 0.0, 1.0),
|
||||
OPT_FLOATRANGE("video-zoom", zoom, UPDATE_VIDEOPOS, -20.0, 20.0),
|
||||
OPT_FLOATRANGE("video-pan-x", pan_x, UPDATE_VIDEOPOS, -3.0, 3.0),
|
||||
OPT_FLOATRANGE("video-pan-y", pan_y, UPDATE_VIDEOPOS, -3.0, 3.0),
|
||||
OPT_FLOATRANGE("video-align-x", align_x, UPDATE_VIDEOPOS, -1.0, 1.0),
|
||||
OPT_FLOATRANGE("video-align-y", align_y, UPDATE_VIDEOPOS, -1.0, 1.0),
|
||||
OPT_CHOICE("video-unscaled", unscaled, UPDATE_VIDEOPOS,
|
||||
OPT_FLOATRANGE("panscan", panscan, 0, 0.0, 1.0),
|
||||
OPT_FLOATRANGE("video-zoom", zoom, 0, -20.0, 20.0),
|
||||
OPT_FLOATRANGE("video-pan-x", pan_x, 0, -3.0, 3.0),
|
||||
OPT_FLOATRANGE("video-pan-y", pan_y, 0, -3.0, 3.0),
|
||||
OPT_FLOATRANGE("video-align-x", align_x, 0, -1.0, 1.0),
|
||||
OPT_FLOATRANGE("video-align-y", align_y, 0, -1.0, 1.0),
|
||||
OPT_CHOICE("video-unscaled", unscaled, 0,
|
||||
({"no", 0}, {"yes", 1}, {"downscale-big", 2})),
|
||||
OPT_INT64("wid", WinID, 0),
|
||||
OPT_CHOICE_OR_INT("screen", screen_id, 0, 0, 32,
|
||||
({"default", -1})),
|
||||
OPT_CHOICE_OR_INT("fs-screen", fsscreen_id, 0, 0, 32,
|
||||
({"all", -2}, {"current", -1})),
|
||||
OPT_FLAG("keepaspect", keepaspect, UPDATE_VIDEOPOS),
|
||||
OPT_FLAG("keepaspect", keepaspect, 0),
|
||||
OPT_FLAG("keepaspect-window", keepaspect_window, 0),
|
||||
OPT_FLAG("hidpi-window-scale", hidpi_window_scale, 0),
|
||||
OPT_FLAG("native-fs", native_fs, 0),
|
||||
#if HAVE_X11
|
||||
OPT_CHOICE("x11-netwm", x11_netwm, 0,
|
||||
({"auto", 0}, {"no", -1}, {"yes", 1})),
|
||||
@@ -178,7 +179,7 @@ static const m_option_t mp_vo_opt_list[] = {
|
||||
#endif
|
||||
#if HAVE_GL
|
||||
OPT_STRING_VALIDATE("opengl-hwdec-interop", gl_hwdec_interop, 0,
|
||||
gl_hwdec_validate_opt),
|
||||
ra_hwdec_validate_opt),
|
||||
OPT_REPLACED("hwdec-preload", "opengl-hwdec-interop"),
|
||||
#endif
|
||||
{0}
|
||||
@@ -196,6 +197,7 @@ const struct m_sub_options vo_sub_opts = {
|
||||
.keepaspect = 1,
|
||||
.keepaspect_window = 1,
|
||||
.hidpi_window_scale = 1,
|
||||
.native_fs = 1,
|
||||
.taskbar_progress = 1,
|
||||
.snap_window = 0,
|
||||
.border = 1,
|
||||
@@ -273,7 +275,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("log-file", log_file, CONF_PRE_PARSE | M_OPT_FILE | UPDATE_TERM),
|
||||
OPT_FLAG("msg-module", msg_module, UPDATE_TERM),
|
||||
OPT_FLAG("msg-time", msg_time, UPDATE_TERM),
|
||||
#if HAVE_WIN32_DESKTOP && HAVE_GPL
|
||||
#if HAVE_WIN32_DESKTOP
|
||||
OPT_CHOICE("priority", w32_priority, UPDATE_PRIORITY,
|
||||
({"no", 0},
|
||||
{"realtime", REALTIME_PRIORITY_CLASS},
|
||||
@@ -359,8 +361,9 @@ const m_option_t mp_opts[] = {
|
||||
OPT_ALIAS("audio", "aid"),
|
||||
OPT_STRINGLIST("alang", stream_lang[STREAM_AUDIO], 0),
|
||||
OPT_STRINGLIST("slang", stream_lang[STREAM_SUB], 0),
|
||||
OPT_FLAG("track-auto-selection", stream_auto_sel, 0),
|
||||
|
||||
OPT_STRING("lavfi-complex", lavfi_complex, 0),
|
||||
OPT_STRING("lavfi-complex", lavfi_complex, UPDATE_LAVFI_COMPLEX),
|
||||
|
||||
OPT_CHOICE("audio-display", audio_display, 0,
|
||||
({"no", 0}, {"attachment", 1})),
|
||||
@@ -420,10 +423,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SETTINGSLIST("vf-defaults", vf_defs, 0, &vf_obj_list, ),
|
||||
OPT_SETTINGSLIST("vf", vf_settings, 0, &vf_obj_list, ),
|
||||
|
||||
OPT_CHOICE("deinterlace", deinterlace, 0,
|
||||
({"auto", -1},
|
||||
{"no", 0},
|
||||
{"yes", 1})),
|
||||
OPT_FLAG("deinterlace", deinterlace, UPDATE_DEINT),
|
||||
|
||||
OPT_STRING("ad", audio_decoders, 0),
|
||||
OPT_STRING("vd", video_decoders, 0),
|
||||
@@ -443,13 +443,7 @@ const m_option_t mp_opts[] = {
|
||||
// 0 means square pixels
|
||||
OPT_ASPECT("video-aspect", movie_aspect, UPDATE_IMGPAR, -1.0, 10.0),
|
||||
OPT_CHOICE("video-aspect-method", aspect_method, UPDATE_IMGPAR,
|
||||
({"hybrid", 0}, {"bitstream", 1}, {"container", 2})),
|
||||
|
||||
#if HAVE_GPL
|
||||
OPT_CHOICE("field-dominance", field_dominance, UPDATE_IMGPAR,
|
||||
({"auto", -1}, {"top", 0}, {"bottom", 1}),
|
||||
.deprecation_message = "use --vf=setfield=bff or tff"),
|
||||
#endif
|
||||
({"bitstream", 1}, {"container", 2})),
|
||||
|
||||
OPT_SUBSTRUCT("vd-lavc", vd_lavc_params, vd_lavc_conf, 0),
|
||||
OPT_SUBSTRUCT("ad-lavc", ad_lavc_params, ad_lavc_conf, 0),
|
||||
@@ -466,7 +460,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_PATHLIST("sub-file-paths", sub_paths, 0),
|
||||
OPT_PATHLIST("audio-file-paths", audiofile_paths, 0),
|
||||
OPT_PATHLIST("external-files", external_files, 0),
|
||||
OPT_CLI_ALIAS("external-file", "external-file-append"),
|
||||
OPT_CLI_ALIAS("external-file", "external-files-append"),
|
||||
OPT_FLAG("autoload-files", autoload_files, 0),
|
||||
OPT_FLOAT("sub-delay", sub_delay, UPDATE_OSD),
|
||||
OPT_FLOAT("sub-fps", sub_fps, UPDATE_OSD),
|
||||
@@ -575,15 +569,7 @@ const m_option_t mp_opts[] = {
|
||||
.deprecation_message = "use Lua scripting instead"),
|
||||
OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
|
||||
|
||||
#if HAVE_GPL
|
||||
OPT_INTRANGE("brightness", gamma_brightness, 0, -100, 100),
|
||||
OPT_INTRANGE("saturation", gamma_saturation, 0, -100, 100),
|
||||
OPT_INTRANGE("contrast", gamma_contrast, 0, -100, 100),
|
||||
OPT_INTRANGE("hue", gamma_hue, 0, -100, 100),
|
||||
OPT_INTRANGE("gamma", gamma_gamma, 0, -100, 100),
|
||||
OPT_CHOICE_C("video-output-levels", video_output_levels, 0,
|
||||
mp_csp_levels_names),
|
||||
#endif
|
||||
OPT_SUBSTRUCT("", video_equalizer, mp_csp_equalizer_conf, 0),
|
||||
|
||||
OPT_FLAG("use-filedir-conf", use_filedir_conf, 0),
|
||||
OPT_CHOICE("osd-level", osd_level, 0,
|
||||
@@ -617,6 +603,7 @@ const m_option_t mp_opts[] = {
|
||||
({"no", 0},
|
||||
{"yes", -1},
|
||||
{"inf", -1})),
|
||||
OPT_ALIAS("loop", "loop-file"),
|
||||
|
||||
OPT_FLAG("resume-playback", position_resume, 0),
|
||||
OPT_FLAG("save-position-on-quit", position_save_on_quit, 0),
|
||||
@@ -834,8 +821,6 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("ass-scale-with-window", "sub-ass-scale-with-window"),
|
||||
OPT_REPLACED("sub-ass-style-override", "sub-ass-override"),
|
||||
OPT_REMOVED("fs-black-out-screens", NULL),
|
||||
OPT_REPLACED_MSG("loop", "loop-playlist", "--loop will be changed to map to"
|
||||
" --loop-file in future releases."),
|
||||
OPT_REPLACED("sub-paths", "sub-file-paths"),
|
||||
|
||||
{0}
|
||||
@@ -847,7 +832,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.audio_driver_list = NULL,
|
||||
.audio_decoders = NULL,
|
||||
.video_decoders = NULL,
|
||||
.deinterlace = -1,
|
||||
.softvol = SOFTVOL_AUTO,
|
||||
.softvol_max = 130,
|
||||
.softvol_volume = 100,
|
||||
@@ -860,11 +844,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.heartbeat_interval = 30.0,
|
||||
.stop_screensaver = 1,
|
||||
.cursor_autohide_delay = 1000,
|
||||
.gamma_gamma = 0,
|
||||
.gamma_brightness = 0,
|
||||
.gamma_contrast = 0,
|
||||
.gamma_saturation = 0,
|
||||
.gamma_hue = 0,
|
||||
.video_osd = 1,
|
||||
.osd_level = 1,
|
||||
.osd_duration = 1000,
|
||||
@@ -925,6 +904,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.stream_id_ff = { [STREAM_AUDIO] = -1,
|
||||
[STREAM_VIDEO] = -1,
|
||||
[STREAM_SUB] = -1, },
|
||||
.stream_auto_sel = 1,
|
||||
.audio_display = 1,
|
||||
.sub_visibility = 1,
|
||||
.sub_pos = 100,
|
||||
@@ -934,7 +914,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.pitch_correction = 1,
|
||||
.movie_aspect = -1.,
|
||||
.aspect_method = 2,
|
||||
.field_dominance = -1,
|
||||
.sub_auto = 0,
|
||||
.audiofile_auto = -1,
|
||||
.osd_bar_visible = 1,
|
||||
|
||||
+3
-8
@@ -40,6 +40,7 @@ typedef struct mp_vo_opts {
|
||||
int keepaspect;
|
||||
int keepaspect_window;
|
||||
int hidpi_window_scale;
|
||||
int native_fs;
|
||||
|
||||
int64_t WinID;
|
||||
|
||||
@@ -118,13 +119,7 @@ typedef struct MPOpts {
|
||||
char *media_title;
|
||||
int force_rgba_osd;
|
||||
|
||||
// ranges -100 - 100, 1000 if the vo default should be used
|
||||
int gamma_gamma;
|
||||
int gamma_brightness;
|
||||
int gamma_contrast;
|
||||
int gamma_saturation;
|
||||
int gamma_hue;
|
||||
int video_output_levels;
|
||||
struct mp_csp_equalizer_opts *video_equalizer;
|
||||
|
||||
int stop_screensaver;
|
||||
int cursor_autohide_delay;
|
||||
@@ -211,6 +206,7 @@ typedef struct MPOpts {
|
||||
int stream_id[2][STREAM_TYPE_COUNT];
|
||||
int stream_id_ff[STREAM_TYPE_COUNT];
|
||||
char **stream_lang[STREAM_TYPE_COUNT];
|
||||
int stream_auto_sel;
|
||||
int audio_display;
|
||||
char **display_tags;
|
||||
int sub_visibility;
|
||||
@@ -253,7 +249,6 @@ typedef struct MPOpts {
|
||||
int deinterlace;
|
||||
float movie_aspect;
|
||||
int aspect_method;
|
||||
int field_dominance;
|
||||
char **sub_name;
|
||||
char **sub_paths;
|
||||
char **audiofile_paths;
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#if HAVE_POSIX
|
||||
#if HAVE_GLOB_POSIX
|
||||
#include <glob.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -18,14 +18,7 @@
|
||||
#ifndef MPV_MACOSX_APPLICATION
|
||||
#define MPV_MACOSX_APPLICATION
|
||||
|
||||
// Menu Keys identifing menu items
|
||||
typedef enum {
|
||||
MPM_H_SIZE,
|
||||
MPM_N_SIZE,
|
||||
MPM_D_SIZE,
|
||||
MPM_MINIMIZE,
|
||||
MPM_ZOOM,
|
||||
} MPMenuKey;
|
||||
#include "osdep/macosx_menubar.h"
|
||||
|
||||
// multithreaded wrapper for mpv_main
|
||||
int cocoa_main(int argc, char *argv[]);
|
||||
|
||||
+15
-131
@@ -47,21 +47,6 @@ static pthread_t playback_thread_id;
|
||||
EventsResponder *_eventsResponder;
|
||||
}
|
||||
|
||||
- (NSMenuItem *)menuItemWithParent:(NSMenu *)parent
|
||||
title:(NSString *)title
|
||||
action:(SEL)selector
|
||||
keyEquivalent:(NSString*)key;
|
||||
|
||||
- (NSMenuItem *)mainMenuItemWithParent:(NSMenu *)parent
|
||||
child:(NSMenu *)child;
|
||||
- (void)registerMenuItem:(NSMenuItem*)menuItem forKey:(MPMenuKey)key;
|
||||
- (NSMenu *)appleMenuWithMainMenu:(NSMenu *)mainMenu;
|
||||
- (NSMenu *)videoMenu;
|
||||
- (NSMenu *)windowMenu;
|
||||
@end
|
||||
|
||||
@interface NSApplication (NiblessAdditions)
|
||||
- (void)setAppleMenu:(NSMenu *)aMenu;
|
||||
@end
|
||||
|
||||
static Application *mpv_shared_app(void)
|
||||
@@ -76,12 +61,12 @@ static void terminate_cocoa_application(void)
|
||||
}
|
||||
|
||||
@implementation Application
|
||||
@synthesize menuItems = _menu_items;
|
||||
@synthesize menuBar = _menu_Bar;
|
||||
@synthesize openCount = _open_count;
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
if (![_eventsResponder processKeyEvent:event])
|
||||
if ([self modalWindow] || ![_eventsResponder processKeyEvent:event])
|
||||
[super sendEvent:event];
|
||||
[_eventsResponder wakeup];
|
||||
}
|
||||
@@ -89,7 +74,6 @@ static void terminate_cocoa_application(void)
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.menuItems = [[[NSMutableDictionary alloc] init] autorelease];
|
||||
_eventsResponder = [EventsResponder sharedInstance];
|
||||
|
||||
NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
|
||||
@@ -125,11 +109,6 @@ static void terminate_cocoa_application(void)
|
||||
currentPosition, timeLeft];
|
||||
return tBar;
|
||||
}
|
||||
|
||||
- (void)toggleTouchBarMenu
|
||||
{
|
||||
[NSApp toggleTouchBarCustomizationPalette:self];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)processEvent:(struct mpv_event *)event
|
||||
@@ -145,117 +124,12 @@ static void terminate_cocoa_application(void)
|
||||
[_eventsResponder queueCommand:cmd];
|
||||
}
|
||||
|
||||
#define _R(P, T, E, K) \
|
||||
{ \
|
||||
NSMenuItem *tmp = [self menuItemWithParent:(P) title:(T) \
|
||||
action:nil keyEquivalent:(E)]; \
|
||||
[self registerMenuItem:tmp forKey:(K)]; \
|
||||
}
|
||||
|
||||
- (NSMenu *)appleMenuWithMainMenu:(NSMenu *)mainMenu
|
||||
{
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Apple Menu"];
|
||||
[self mainMenuItemWithParent:mainMenu child:menu];
|
||||
[self menuItemWithParent:menu title:@"Hide mpv"
|
||||
action:@selector(hide:) keyEquivalent: @"h"];
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[self menuItemWithParent:menu title:@"Quit mpv"
|
||||
action:@selector(stopPlayback) keyEquivalent: @"q"];
|
||||
return [menu autorelease];
|
||||
}
|
||||
|
||||
- (NSMenu *)videoMenu
|
||||
{
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Video"];
|
||||
_R(menu, @"Half Size", @"0", MPM_H_SIZE)
|
||||
_R(menu, @"Normal Size", @"1", MPM_N_SIZE)
|
||||
_R(menu, @"Double Size", @"2", MPM_D_SIZE)
|
||||
return [menu autorelease];
|
||||
}
|
||||
|
||||
- (NSMenu *)windowMenu
|
||||
{
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Window"];
|
||||
_R(menu, @"Minimize", @"m", MPM_MINIMIZE)
|
||||
_R(menu, @"Zoom", @"z", MPM_ZOOM)
|
||||
|
||||
#if HAVE_MACOS_TOUCHBAR
|
||||
if ([self respondsToSelector:@selector(touchBar)]) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[self menuItemWithParent:menu title:@"Customize Touch Bar…"
|
||||
action:@selector(toggleTouchBarMenu) keyEquivalent: @""];
|
||||
}
|
||||
#endif
|
||||
|
||||
return [menu autorelease];
|
||||
}
|
||||
|
||||
- (void)initialize_menu
|
||||
{
|
||||
NSMenu *main_menu = [[NSMenu new] autorelease];
|
||||
[NSApp setMainMenu:main_menu];
|
||||
[NSApp setAppleMenu:[self appleMenuWithMainMenu:main_menu]];
|
||||
|
||||
[NSApp mainMenuItemWithParent:main_menu child:[self videoMenu]];
|
||||
[NSApp mainMenuItemWithParent:main_menu child:[self windowMenu]];
|
||||
}
|
||||
|
||||
#undef _R
|
||||
|
||||
- (void)stopPlayback
|
||||
{
|
||||
[self stopMPV:"quit"];
|
||||
}
|
||||
|
||||
- (void)stopPlaybackAndRememberPosition
|
||||
{
|
||||
[self stopMPV:"quit-watch-later"];
|
||||
}
|
||||
|
||||
- (void)stopMPV:(char *)cmd
|
||||
{
|
||||
if (![_eventsResponder queueCommand:cmd])
|
||||
terminate_cocoa_application();
|
||||
}
|
||||
|
||||
- (void)registerMenuItem:(NSMenuItem*)menuItem forKey:(MPMenuKey)key
|
||||
{
|
||||
[self.menuItems setObject:menuItem forKey:[NSNumber numberWithInt:key]];
|
||||
}
|
||||
|
||||
- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key
|
||||
{
|
||||
NSNumber *boxedKey = [NSNumber numberWithInt:key];
|
||||
NSMenuItem *item = [self.menuItems objectForKey:boxedKey];
|
||||
if (item) {
|
||||
[item setAction:action];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenuItem *)menuItemWithParent:(NSMenu *)parent
|
||||
title:(NSString *)title
|
||||
action:(SEL)action
|
||||
keyEquivalent:(NSString*)key
|
||||
{
|
||||
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title
|
||||
action:action
|
||||
keyEquivalent:key];
|
||||
[parent addItem:item];
|
||||
return [item autorelease];
|
||||
}
|
||||
|
||||
- (NSMenuItem *)mainMenuItemWithParent:(NSMenu *)parent
|
||||
child:(NSMenu *)child
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@""
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[item setSubmenu:child];
|
||||
[parent addItem:item];
|
||||
return [item autorelease];
|
||||
}
|
||||
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
|
||||
@@ -265,10 +139,15 @@ static void terminate_cocoa_application(void)
|
||||
andEventID:kAEQuitApplication];
|
||||
}
|
||||
|
||||
- (void)applicationWillBecomeActive:(NSNotification *)notification
|
||||
{
|
||||
[_eventsResponder setHighestPriotityMediaKeysTap];
|
||||
}
|
||||
|
||||
- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
|
||||
withReplyEvent:(NSAppleEventDescriptor *)replyEvent
|
||||
{
|
||||
[self stopPlayback];
|
||||
[self stopMPV:"quit"];
|
||||
}
|
||||
|
||||
- (void)getUrl:(NSAppleEventDescriptor *)event
|
||||
@@ -292,6 +171,11 @@ static void terminate_cocoa_application(void)
|
||||
mpv_shared_app().openCount--;
|
||||
return;
|
||||
}
|
||||
[self openFiles:filenames];
|
||||
}
|
||||
|
||||
- (void)openFiles:(NSArray *)filenames
|
||||
{
|
||||
SEL cmpsel = @selector(localizedStandardCompare:);
|
||||
NSArray *files = [filenames sortedArrayUsingSelector:cmpsel];
|
||||
[_eventsResponder handleFilesArray:files];
|
||||
@@ -325,14 +209,14 @@ static void *playback_thread(void *ctx_obj)
|
||||
void cocoa_register_menu_item_action(MPMenuKey key, void* action)
|
||||
{
|
||||
if (application_instantiated)
|
||||
[NSApp registerSelector:(SEL)action forKey:key];
|
||||
[[NSApp menuBar] registerSelector:(SEL)action forKey:key];
|
||||
}
|
||||
|
||||
static void init_cocoa_application(bool regular)
|
||||
{
|
||||
NSApp = mpv_shared_app();
|
||||
[NSApp setDelegate:NSApp];
|
||||
[NSApp initialize_menu];
|
||||
[NSApp setMenuBar:[[MenuBar alloc] init]];
|
||||
|
||||
// Will be set to Regular from cocoa_common during UI creation so that we
|
||||
// don't create an icon when playing audio only files.
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "osdep/macosx_application.h"
|
||||
#import "osdep/macosx_menubar_objc.h"
|
||||
|
||||
struct mpv_event;
|
||||
|
||||
@interface Application : NSApplication
|
||||
|
||||
- (void)initialize_menu;
|
||||
- (void)registerSelector:(SEL)selector forKey:(MPMenuKey)key;
|
||||
- (void)stopPlayback;
|
||||
- (void)processEvent:(struct mpv_event *)event;
|
||||
- (void)queueCommand:(char *)cmd;
|
||||
- (void)stopMPV:(char *)cmd;
|
||||
- (void)openFiles:(NSArray *)filenames;
|
||||
|
||||
@property(nonatomic, retain) NSMutableDictionary *menuItems;
|
||||
@property(nonatomic, retain) MenuBar *menuBar;
|
||||
@property(nonatomic, retain) NSArray *files;
|
||||
@property(nonatomic, assign) size_t openCount;
|
||||
@end
|
||||
@@ -25,6 +25,11 @@
|
||||
#include "osdep/macosx_versions.h"
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12)
|
||||
|
||||
@interface NSWindow (macOS10_12_SDK)
|
||||
+ (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
|
||||
@end
|
||||
|
||||
typedef NSUInteger NSWindowStyleMask;
|
||||
static const NSWindowStyleMask NSWindowStyleMaskClosable = NSClosableWindowMask;
|
||||
static const NSWindowStyleMask NSWindowStyleMaskTitled = NSTitledWindowMask;
|
||||
@@ -45,7 +50,7 @@ typedef NSUInteger NSEventModifierFlags;
|
||||
|
||||
static const NSEventModifierFlags NSEventModifierFlagShift = NSShiftKeyMask;
|
||||
static const NSEventModifierFlags NSEventModifierFlagControl = NSControlKeyMask;
|
||||
static const NSEventModifierFlags NSEventModifierFlagCommand = NSCommandKeyMask;
|
||||
static const NSEventModifierFlags NSEventModifierFlagCommand = NSCommandKeyMask;
|
||||
static const NSEventModifierFlags NSEventModifierFlagOption = NSAlternateKeyMask;
|
||||
#endif
|
||||
|
||||
|
||||
+26
-4
@@ -142,7 +142,7 @@ static int mk_flags(NSEvent *event)
|
||||
return ([event data1] & 0x0000FFFF);
|
||||
}
|
||||
|
||||
static int mk_down(NSEvent *event)
|
||||
static int mk_down(NSEvent *event)
|
||||
{
|
||||
return (((mk_flags(event) & 0xFF00) >> 8)) == 0xA;
|
||||
}
|
||||
@@ -254,7 +254,7 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
|
||||
[_input_lock unlock];
|
||||
}
|
||||
|
||||
- (void)setInputContext:(struct input_ctx *)ctx;
|
||||
- (void)setInputContext:(struct input_ctx *)ctx
|
||||
{
|
||||
[_input_lock lock];
|
||||
_inputContext = ctx;
|
||||
@@ -346,7 +346,6 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
|
||||
|
||||
- (void)startAppleRemote
|
||||
{
|
||||
|
||||
#if HAVE_APPLE_REMOTE
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_remote = [[HIDRemote alloc] init];
|
||||
@@ -356,8 +355,8 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)stopAppleRemote
|
||||
{
|
||||
#if HAVE_APPLE_REMOTE
|
||||
@@ -367,10 +366,31 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)restartMediaKeys
|
||||
{
|
||||
CGEventTapEnable(self->_mk_tap_port, true);
|
||||
}
|
||||
|
||||
- (void)setHighestPriotityMediaKeysTap
|
||||
{
|
||||
if (self->_mk_tap_port == nil)
|
||||
return;
|
||||
|
||||
CGEventTapInformation *taps = ta_alloc_size(nil, sizeof(CGEventTapInformation));
|
||||
uint32_t numTaps = 0;
|
||||
CGError err = CGGetEventTapList(1, taps, &numTaps);
|
||||
|
||||
if (err == kCGErrorSuccess && numTaps > 0) {
|
||||
pid_t processID = [NSProcessInfo processInfo].processIdentifier;
|
||||
if (taps[0].tappingProcess != processID) {
|
||||
[self stopMediaKeys];
|
||||
[self startMediaKeys];
|
||||
}
|
||||
}
|
||||
talloc_free(taps);
|
||||
}
|
||||
|
||||
- (void)startMediaKeys
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -390,10 +410,12 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
|
||||
[[NSRunLoop mainRunLoop] addPort:port forMode:NSRunLoopCommonModes];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopMediaKeys
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMachPort *port = (NSMachPort *)self->_mk_tap_port;
|
||||
CGEventTapEnable(self->_mk_tap_port, false);
|
||||
[[NSRunLoop mainRunLoop] removePort:port forMode:NSRunLoopCommonModes];
|
||||
CFRelease(self->_mk_tap_port);
|
||||
self->_mk_tap_port = nil;
|
||||
|
||||
@@ -40,6 +40,8 @@ struct input_ctx;
|
||||
|
||||
- (void)putKey:(int)keycode;
|
||||
|
||||
- (void)setHighestPriotityMediaKeysTap;
|
||||
|
||||
- (void)handleFilesArray:(NSArray *)files;
|
||||
|
||||
- (bool)processKeyEvent:(NSEvent *)event;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPV_MACOSX_MENU
|
||||
#define MPV_MACOSX_MENU
|
||||
|
||||
// Menu Keys identifing menu items
|
||||
typedef enum {
|
||||
MPM_H_SIZE,
|
||||
MPM_N_SIZE,
|
||||
MPM_D_SIZE,
|
||||
MPM_MINIMIZE,
|
||||
MPM_ZOOM,
|
||||
} MPMenuKey;
|
||||
|
||||
#endif /* MPV_MACOSX_MENU */
|
||||
@@ -0,0 +1,751 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#import "macosx_menubar_objc.h"
|
||||
#import "osdep/macosx_application_objc.h"
|
||||
#include "osdep/macosx_compat.h"
|
||||
|
||||
@implementation MenuBar
|
||||
{
|
||||
NSArray *menuTree;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
|
||||
[userDefaults setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
|
||||
[userDefaults setBool:YES forKey:@"NSDisabledDictationMenuItem"];
|
||||
[userDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
|
||||
|
||||
if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
|
||||
[NSWindow setAllowsAutomaticWindowTabbing: NO];
|
||||
|
||||
menuTree = @[
|
||||
@{
|
||||
@"name": @"Apple",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"About mpv",
|
||||
@"action" : @"about",
|
||||
@"key" : @"",
|
||||
@"target" : self
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Preferences…",
|
||||
@"action" : @"preferences:",
|
||||
@"key" : @",",
|
||||
@"target" : self,
|
||||
@"file" : @"mpv.conf",
|
||||
@"alertTitle1": @"No Application found to open your config file.",
|
||||
@"alertText1" : @"Please open the mpv.conf file with "
|
||||
"your preferred text editor in the now "
|
||||
"open folder to edit your config.",
|
||||
@"alertTitle2": @"No config file found.",
|
||||
@"alertText2" : @"Please create a mpv.conf file with your "
|
||||
"preferred text editor in the now open folder.",
|
||||
@"alertTitle3": @"No config path or file found.",
|
||||
@"alertText3" : @"Please create the following path ~/.config/mpv/ "
|
||||
"and a mpv.conf file within with your preferred "
|
||||
"text editor."
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Keyboard Shortcuts Config…",
|
||||
@"action" : @"preferences:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"file" : @"input.conf",
|
||||
@"alertTitle1": @"No Application found to open your config file.",
|
||||
@"alertText1" : @"Please open the input.conf file with "
|
||||
"your preferred text editor in the now "
|
||||
"open folder to edit your config.",
|
||||
@"alertTitle2": @"No config file found.",
|
||||
@"alertText2" : @"Please create a input.conf file with your "
|
||||
"preferred text editor in the now open folder.",
|
||||
@"alertTitle3": @"No config path or file found.",
|
||||
@"alertText3" : @"Please create the following path ~/.config/mpv/ "
|
||||
"and a input.conf file within with your preferred "
|
||||
"text editor."
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Hide mpv",
|
||||
@"action" : @"hide:",
|
||||
@"key" : @"h",
|
||||
@"target" : NSApp
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Quit and Remember Position",
|
||||
@"action" : @"quit:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"quit-watch-later"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Quit mpv",
|
||||
@"action" : @"quit:",
|
||||
@"key" : @"q",
|
||||
@"target" : self,
|
||||
@"cmd" : @"quit"
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"File",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Open File…",
|
||||
@"action" : @"openFile",
|
||||
@"key" : @"o",
|
||||
@"target" : self
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Open URL…",
|
||||
@"action" : @"openURL",
|
||||
@"key" : @"O",
|
||||
@"target" : self
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Save Screenshot",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"async screenshot"
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Edit",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Undo",
|
||||
@"action" : @"undo:",
|
||||
@"key" : @"z"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Redo",
|
||||
@"action" : @"redo:",
|
||||
@"key" : @"Z"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Cut",
|
||||
@"action" : @"cut:",
|
||||
@"key" : @"x"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Copy",
|
||||
@"action" : @"copy:",
|
||||
@"key" : @"c"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Paste",
|
||||
@"action" : @"paste:",
|
||||
@"key" : @"v"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Select All",
|
||||
@"action" : @"selectAll:",
|
||||
@"key" : @"a"
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"View",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Fullscreen",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle fullscreen"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Float on Top",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle ontop"
|
||||
}],
|
||||
#if HAVE_MACOS_TOUCHBAR
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Customize Touch Bar…",
|
||||
@"action" : @"toggleTouchBarCustomizationPalette:",
|
||||
@"key" : @"",
|
||||
@"target" : NSApp
|
||||
}]
|
||||
#endif
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Video",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Zoom Out",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add panscan -0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Zoom In",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add panscan 0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Reset Zoom",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set panscan 0"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Aspect Ratio 4:3",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set video-aspect \"4:3\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Aspect Ratio 16:9",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set video-aspect \"16:9\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Aspect Ratio 1.85:1",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set video-aspect \"1.85:1\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Aspect Ratio 2.35:1",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set video-aspect \"2.35:1\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Reset Aspect Ratio",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set video-aspect \"-1\""
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Half Size",
|
||||
@"key" : @"0",
|
||||
@"cmdSpecial" : [NSNumber numberWithInt:MPM_H_SIZE]
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Normal Size",
|
||||
@"key" : @"1",
|
||||
@"cmdSpecial" : [NSNumber numberWithInt:MPM_N_SIZE]
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Double Size",
|
||||
@"key" : @"2",
|
||||
@"cmdSpecial" : [NSNumber numberWithInt:MPM_D_SIZE]
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Audio",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Next Audio Track",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle audio"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Previous Audio Track",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle audio down"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Mute",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle mute"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Play Audio Later",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add audio-delay 0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Play Audio Earlier",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add audio-delay -0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Reset Audio Delay",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set audio-delay 0.0 "
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Subtitle",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Next Subtitle Track",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle sub"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Previous Subtitle Track",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle sub down"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Force Style",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle-values sub-ass-override \"force\" \"no\""
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Display Subtitles Later",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add sub-delay 0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Display Subtitles Earlier",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add sub-delay -0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Reset Subtitle Delay",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set sub-delay 0.0"
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Playback",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Pause",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle pause"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Increase Speed",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add speed 0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Decrease Speed",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add speed -0.1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Reset Speed",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"set speed 1.0"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Show Playlist",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"script-message osc-playlist"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Show Chapters",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"script-message osc-chapterlist"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Show Tracks",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"script-message osc-tracklist"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Next File",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"playlist-next"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Previous File",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"playlist-prev"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Loop File",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle-values loop-file \"inf\" \"no\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Toggle Loop Playlist",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"cycle-values loop-playlist \"inf\" \"no\""
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Shuffle",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"playlist-shuffle"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Next Chapter",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add chapter 1"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Previous Chapter",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"add chapter -1"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Step Forward",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"frame-step"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Step Backward",
|
||||
@"action" : @"cmd:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"cmd" : @"frame-back-step"
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Window",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Minimize",
|
||||
@"key" : @"m",
|
||||
@"cmdSpecial" : [NSNumber numberWithInt:MPM_MINIMIZE]
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Zoom",
|
||||
@"key" : @"z",
|
||||
@"cmdSpecial" : [NSNumber numberWithInt:MPM_ZOOM]
|
||||
}]
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"name": @"Help",
|
||||
@"menu": @[
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"mpv Website…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://mpv.io"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"mpv on github…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://github.com/mpv-player/mpv"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Online Manual…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://mpv.io/manual/master/"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Online Wiki…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://github.com/mpv-player/mpv/wiki"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Release Notes…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://github.com/mpv-player/mpv/blob/master/RELEASE_NOTES"
|
||||
}],
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Keyboard Shortcuts…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://github.com/mpv-player/mpv/blob/master/etc/input.conf"
|
||||
}],
|
||||
@{ @"name": @"separator" },
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
@"name" : @"Report Issue…",
|
||||
@"action" : @"url:",
|
||||
@"key" : @"",
|
||||
@"target" : self,
|
||||
@"url" : @"https://github.com/mpv-player/mpv/issues/new"
|
||||
}]
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
[NSApp setMainMenu:[self mainMenu]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSMenu *)mainMenu
|
||||
{
|
||||
NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
|
||||
|
||||
for(id mMenu in menuTree) {
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:mMenu[@"name"]];
|
||||
NSMenuItem *mItem = [mainMenu addItemWithTitle:mMenu[@"name"]
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[mainMenu setSubmenu:menu forItem:mItem];
|
||||
|
||||
for(id subMenu in mMenu[@"menu"]) {
|
||||
#if HAVE_MACOS_TOUCHBAR
|
||||
if ([subMenu[@"action"] isEqual:@"toggleTouchBarCustomizationPalette:"]) {
|
||||
if (![NSApp respondsToSelector:@selector(touchBar)])
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if ([subMenu[@"name"] isEqual:@"separator"]) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
} else {
|
||||
NSMenuItem *iItem = [menu addItemWithTitle:subMenu[@"name"]
|
||||
action:NSSelectorFromString(subMenu[@"action"])
|
||||
keyEquivalent:subMenu[@"key"]];
|
||||
[iItem setTarget:subMenu[@"target"]];
|
||||
[subMenu setObject:iItem forKey:@"menuItem"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainMenu;
|
||||
}
|
||||
|
||||
- (void)about
|
||||
{
|
||||
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"mpv", @"ApplicationName",
|
||||
[self getMPVIcon], @"ApplicationIcon",
|
||||
@"Copyright © 2000-2017 mpv/MPlayer/mplayer2 projects", @"Copyright",
|
||||
[NSString stringWithUTF8String:mpv_version], @"ApplicationVersion",
|
||||
nil];
|
||||
[NSApp orderFrontStandardAboutPanelWithOptions:options];
|
||||
}
|
||||
|
||||
- (void)preferences:(NSMenuItem *)menuItem
|
||||
{
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSMutableDictionary *mItemDict = [self getDictFromMenuItem:menuItem];
|
||||
NSArray *configPaths = @[
|
||||
[NSString stringWithFormat:@"%@/.mpv/", NSHomeDirectory()],
|
||||
[NSString stringWithFormat:@"%@/.config/mpv/", NSHomeDirectory()]];
|
||||
|
||||
for (id path in configPaths) {
|
||||
NSString *fileP = [path stringByAppendingString:mItemDict[@"file"]];
|
||||
if ([fileManager fileExistsAtPath:fileP]){
|
||||
if ([workspace openFile:fileP])
|
||||
return;
|
||||
[workspace openFile:path];
|
||||
[self alertWithTitle:mItemDict[@"alertTitle1"]
|
||||
andText:mItemDict[@"alertText1"]];
|
||||
return;
|
||||
}
|
||||
if ([workspace openFile:path]) {
|
||||
[self alertWithTitle:mItemDict[@"alertTitle2"]
|
||||
andText:mItemDict[@"alertText2"]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self alertWithTitle:mItemDict[@"alertTitle3"]
|
||||
andText:mItemDict[@"alertText3"]];
|
||||
}
|
||||
|
||||
- (void)quit:(NSMenuItem *)menuItem
|
||||
{
|
||||
NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
|
||||
[(Application *)NSApp stopMPV:(char *)[cmd UTF8String]];
|
||||
}
|
||||
|
||||
- (void)openFile
|
||||
{
|
||||
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
|
||||
[panel setCanChooseDirectories:YES];
|
||||
[panel setAllowsMultipleSelection:YES];
|
||||
|
||||
if ([panel runModal] == NSFileHandlingPanelOKButton){
|
||||
NSMutableArray *fileArray = [[NSMutableArray alloc] init];
|
||||
for (id url in [panel URLs])
|
||||
[fileArray addObject:[url path]];
|
||||
[(Application *)NSApp openFiles:fileArray];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)openURL
|
||||
{
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Open URL"];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setIcon:[self getMPVIcon]];
|
||||
|
||||
NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
|
||||
[input setPlaceholderString:@"URL"];
|
||||
[alert setAccessoryView:input];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[input becomeFirstResponder];
|
||||
});
|
||||
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn && [input stringValue].length > 0) {
|
||||
NSArray *url = [NSArray arrayWithObjects:[input stringValue], nil];
|
||||
[(Application *)NSApp openFiles:url];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cmd:(NSMenuItem *)menuItem
|
||||
{
|
||||
NSString *cmd = [self getDictFromMenuItem:menuItem][@"cmd"];
|
||||
[(Application *)NSApp queueCommand:(char *)[cmd UTF8String]];
|
||||
}
|
||||
|
||||
- (void)url:(NSMenuItem *)menuItem
|
||||
{
|
||||
NSString *url = [self getDictFromMenuItem:menuItem][@"url"];
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
|
||||
}
|
||||
|
||||
static const char macosx_icon[] =
|
||||
#include "osdep/macosx_icon.inc"
|
||||
;
|
||||
|
||||
- (NSImage *)getMPVIcon
|
||||
{
|
||||
NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
|
||||
length:sizeof(macosx_icon)
|
||||
freeWhenDone:NO];
|
||||
return [[NSImage alloc] initWithData:icon_data];
|
||||
}
|
||||
|
||||
- (void)alertWithTitle:(NSString *)title andText:(NSString *)text
|
||||
{
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:title];
|
||||
[alert setInformativeText:text];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert setIcon:[self getMPVIcon]];
|
||||
[alert runModal];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)getDictFromMenuItem:(NSMenuItem *)menuItem
|
||||
{
|
||||
for(id mMenu in menuTree) {
|
||||
for(id subMenu in mMenu[@"menu"]) {
|
||||
if([subMenu[@"menuItem"] isEqual:menuItem])
|
||||
return subMenu;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key
|
||||
{
|
||||
for(id mMenu in menuTree) {
|
||||
for(id subMenu in mMenu[@"menu"]) {
|
||||
if([subMenu[@"cmdSpecial"] isEqual:[NSNumber numberWithInt:key]]) {
|
||||
[subMenu[@"menuItem"] setAction:action];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "osdep/macosx_menubar.h"
|
||||
|
||||
@interface MenuBar : NSObject
|
||||
|
||||
- (void)registerSelector:(SEL)action forKey:(MPMenuKey)key;
|
||||
|
||||
@end
|
||||
@@ -112,7 +112,8 @@ static int create_overlapped_pipe(HANDLE *read, HANDLE *write)
|
||||
unsigned long id = atomic_fetch_add(&counter, 1);
|
||||
unsigned pid = GetCurrentProcessId();
|
||||
wchar_t buf[36];
|
||||
swprintf(buf, sizeof(buf), L"\\\\.\\pipe\\mpv-anon-%08x-%08lx", pid, id);
|
||||
swprintf(buf, MP_ARRAY_SIZE(buf), L"\\\\.\\pipe\\mpv-anon-%08x-%08lx",
|
||||
pid, id);
|
||||
|
||||
// The function for creating anonymous pipes (CreatePipe) can't create
|
||||
// overlapped pipes, so instead, use a named pipe with a unique name
|
||||
@@ -126,8 +127,10 @@ static int create_overlapped_pipe(HANDLE *read, HANDLE *write)
|
||||
// Open the write end of the pipe as a synchronous handle
|
||||
*write = CreateFileW(buf, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (*write == INVALID_HANDLE_VALUE)
|
||||
if (*write == INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(*read);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
@@ -173,8 +176,10 @@ error:
|
||||
static int sparse_wait(HANDLE *handles, unsigned num_handles)
|
||||
{
|
||||
unsigned w_num_handles = 0;
|
||||
HANDLE w_handles[num_handles];
|
||||
int map[num_handles];
|
||||
HANDLE w_handles[10];
|
||||
int map[10];
|
||||
if (num_handles > MP_ARRAY_SIZE(w_handles))
|
||||
return -1;
|
||||
|
||||
for (unsigned i = 0; i < num_handles; i++) {
|
||||
if (!handles[i])
|
||||
|
||||
@@ -69,6 +69,21 @@ static const struct keymap vk_map[] = {
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static const struct keymap appcmd_map[] = {
|
||||
{APPCOMMAND_MEDIA_NEXTTRACK, MP_KEY_NEXT},
|
||||
{APPCOMMAND_MEDIA_PREVIOUSTRACK, MP_KEY_PREV},
|
||||
{APPCOMMAND_MEDIA_STOP, MP_KEY_STOP},
|
||||
{APPCOMMAND_MEDIA_PLAY_PAUSE, MP_KEY_PLAYPAUSE},
|
||||
{APPCOMMAND_MEDIA_PLAY, MP_KEY_PLAY},
|
||||
{APPCOMMAND_MEDIA_PAUSE, MP_KEY_PAUSE},
|
||||
{APPCOMMAND_MEDIA_RECORD, MP_KEY_RECORD},
|
||||
{APPCOMMAND_MEDIA_FAST_FORWARD, MP_KEY_FORWARD},
|
||||
{APPCOMMAND_MEDIA_REWIND, MP_KEY_REWIND},
|
||||
{APPCOMMAND_MEDIA_CHANNEL_UP, MP_KEY_CHANNEL_UP},
|
||||
{APPCOMMAND_MEDIA_CHANNEL_DOWN, MP_KEY_CHANNEL_DOWN},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
static int lookup_keymap(const struct keymap *map, int key)
|
||||
{
|
||||
while (map->from && map->from != key) map++;
|
||||
@@ -89,3 +104,8 @@ int mp_w32_vkey_to_mpkey(UINT vkey, bool extended)
|
||||
|
||||
return mpkey;
|
||||
}
|
||||
|
||||
int mp_w32_appcmd_to_mpkey(UINT appcmd)
|
||||
{
|
||||
return lookup_keymap(appcmd_map, appcmd);
|
||||
}
|
||||
@@ -23,4 +23,7 @@
|
||||
/* Convert a Windows virtual key code to an mpv key */
|
||||
int mp_w32_vkey_to_mpkey(UINT vkey, bool extended);
|
||||
|
||||
/* Convert a WM_APPCOMMAND value to an mpv key */
|
||||
int mp_w32_appcmd_to_mpkey(UINT appcmd);
|
||||
|
||||
#endif
|
||||
@@ -205,8 +205,13 @@ int pthread_detach(pthread_t thread)
|
||||
|
||||
static DWORD WINAPI run_thread(LPVOID lpParameter)
|
||||
{
|
||||
struct m_thread_info *info = lpParameter;
|
||||
pthread_exit(info->user_fn(info->user_arg));
|
||||
pthread_mutex_lock(&pthread_table_lock);
|
||||
struct m_thread_info *pinfo = find_thread_info(pthread_self());
|
||||
assert(pinfo);
|
||||
struct m_thread_info info = *pinfo;
|
||||
pthread_mutex_unlock(&pthread_table_lock);
|
||||
|
||||
pthread_exit(info.user_fn(info.user_arg));
|
||||
abort(); // not reached
|
||||
}
|
||||
|
||||
@@ -228,7 +233,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
.user_fn = start_routine,
|
||||
.user_arg = arg,
|
||||
};
|
||||
info->handle = CreateThread(NULL, 0, run_thread, info, CREATE_SUSPENDED,
|
||||
info->handle = CreateThread(NULL, 0, run_thread, NULL, CREATE_SUSPENDED,
|
||||
&info->id);
|
||||
if (!info->handle) {
|
||||
remove_thread_info(info);
|
||||
|
||||
+68
-54
@@ -51,6 +51,7 @@ enum {
|
||||
AD_NEW_FMT = -3,
|
||||
AD_WAIT = -4,
|
||||
AD_NO_PROGRESS = -5,
|
||||
AD_STARVE = -6,
|
||||
};
|
||||
|
||||
// Use pitch correction only for speed adjustments by the user, not minor sync
|
||||
@@ -278,12 +279,8 @@ int reinit_audio_filters(struct MPContext *mpctx)
|
||||
|
||||
// Only force refresh if the amount of dropped buffered data is going to
|
||||
// cause "issues" for the A/V sync logic.
|
||||
if (mpctx->audio_status == STATUS_PLAYING &&
|
||||
mpctx->playback_pts != MP_NOPTS_VALUE && delay > 0.2)
|
||||
{
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->playback_pts,
|
||||
MPSEEK_EXACT, 0);
|
||||
}
|
||||
if (mpctx->audio_status == STATUS_PLAYING && delay > 0.2)
|
||||
issue_refresh_seek(mpctx, MPSEEK_EXACT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -355,6 +352,7 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
|
||||
|
||||
af_destroy(ao_c->af);
|
||||
talloc_free(ao_c->input_frame);
|
||||
talloc_free(ao_c->input_format);
|
||||
talloc_free(ao_c->ao_buffer);
|
||||
talloc_free(ao_c);
|
||||
}
|
||||
@@ -371,6 +369,18 @@ void uninit_audio_chain(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
static void get_ao_format(struct ao *ao, struct mp_audio *aformat)
|
||||
{
|
||||
int samplerate;
|
||||
int format;
|
||||
struct mp_chmap channels;
|
||||
ao_get_format(ao, &samplerate, &format, &channels);
|
||||
*aformat = (struct mp_audio){0};
|
||||
mp_audio_set_format(aformat, format);
|
||||
mp_audio_set_channels(aformat, &channels);
|
||||
aformat->rate = samplerate;
|
||||
}
|
||||
|
||||
static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
@@ -379,12 +389,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
struct track *track = ao_c->track;
|
||||
struct af_stream *afs = ao_c->af;
|
||||
|
||||
if (ao_c->input_frame)
|
||||
mp_audio_copy_config(&ao_c->input_format, ao_c->input_frame);
|
||||
|
||||
struct mp_audio in_format = ao_c->input_format;
|
||||
|
||||
if (!mp_audio_config_valid(&in_format)) {
|
||||
if (!mp_aframe_config_is_valid(ao_c->input_format)) {
|
||||
// We don't know the audio format yet - so configure it later as we're
|
||||
// resyncing. fill_audio_buffers() will call this function again.
|
||||
mp_wakeup_core(mpctx);
|
||||
@@ -393,18 +398,20 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
|
||||
// Weak gapless audio: drain AO on decoder format changes
|
||||
if (mpctx->ao_decoder_fmt && mpctx->ao && opts->gapless_audio < 0 &&
|
||||
!mp_audio_config_equals(mpctx->ao_decoder_fmt, &in_format))
|
||||
!mp_aframe_config_equals(mpctx->ao_decoder_fmt, ao_c->input_format))
|
||||
{
|
||||
uninit_audio_out(mpctx);
|
||||
}
|
||||
|
||||
struct mp_audio in_format;
|
||||
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
|
||||
if (mpctx->ao && mp_audio_config_equals(&in_format, &afs->input))
|
||||
return;
|
||||
|
||||
afs->output = (struct mp_audio){0};
|
||||
if (mpctx->ao) {
|
||||
ao_get_format(mpctx->ao, &afs->output);
|
||||
} else if (af_fmt_is_pcm(in_format.format)) {
|
||||
get_ao_format(mpctx->ao, &afs->output);
|
||||
} else if (af_fmt_is_pcm(mp_aframe_get_format(ao_c->input_format))) {
|
||||
afs->output.rate = opts->force_srate;
|
||||
mp_audio_set_format(&afs->output, opts->audio_output_format);
|
||||
if (opts->audio_output_channels.num_chmaps == 1) {
|
||||
@@ -458,7 +465,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
|
||||
struct mp_audio fmt = {0};
|
||||
if (mpctx->ao)
|
||||
ao_get_format(mpctx->ao, &fmt);
|
||||
get_ao_format(mpctx->ao, &fmt);
|
||||
|
||||
// Verify passthrough format was not changed.
|
||||
if (mpctx->ao && af_fmt_is_spdif(afs->output.format)) {
|
||||
@@ -480,7 +487,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
if (!audio_init_best_codec(ao_c->audio_src))
|
||||
goto init_error;
|
||||
reset_audio_state(mpctx);
|
||||
ao_c->input_format = (struct mp_audio){0};
|
||||
mp_aframe_reset(ao_c->input_format);
|
||||
mp_wakeup_core(mpctx); // reinit with new format next time
|
||||
return;
|
||||
}
|
||||
@@ -495,8 +502,8 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
if (!mp_audio_config_equals(&afs->output, &afs->filter_output))
|
||||
afs->initialized = 0;
|
||||
|
||||
mpctx->ao_decoder_fmt = talloc(NULL, struct mp_audio);
|
||||
*mpctx->ao_decoder_fmt = in_format;
|
||||
mpctx->ao_decoder_fmt = mp_aframe_create();
|
||||
mp_aframe_config_copy(mpctx->ao_decoder_fmt, ao_c->input_format);
|
||||
|
||||
MP_INFO(mpctx, "AO: [%s] %s\n", ao_get_name(mpctx->ao),
|
||||
mp_audio_config_to_str(&fmt));
|
||||
@@ -556,25 +563,19 @@ init_error:
|
||||
|
||||
void reinit_audio_chain(struct MPContext *mpctx)
|
||||
{
|
||||
reinit_audio_chain_src(mpctx, NULL);
|
||||
struct track *track = NULL;
|
||||
track = mpctx->current_track[0][STREAM_AUDIO];
|
||||
if (!track || !track->stream) {
|
||||
uninit_audio_out(mpctx);
|
||||
error_on_track(mpctx, track);
|
||||
return;
|
||||
}
|
||||
reinit_audio_chain_src(mpctx, track);
|
||||
}
|
||||
|
||||
void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
// (track=NULL creates a blank chain, used for lavfi-complex)
|
||||
void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track)
|
||||
{
|
||||
struct track *track = NULL;
|
||||
struct sh_stream *sh = NULL;
|
||||
if (!src) {
|
||||
track = mpctx->current_track[0][STREAM_AUDIO];
|
||||
if (!track) {
|
||||
uninit_audio_out(mpctx);
|
||||
return;
|
||||
}
|
||||
sh = track->stream;
|
||||
if (!sh) {
|
||||
uninit_audio_out(mpctx);
|
||||
goto no_audio;
|
||||
}
|
||||
}
|
||||
assert(!mpctx->ao_chain);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
@@ -583,15 +584,15 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
mpctx->ao_chain = ao_c;
|
||||
ao_c->log = mpctx->log;
|
||||
ao_c->af = af_new(mpctx->global);
|
||||
if (sh)
|
||||
ao_c->af->replaygain_data = sh->codec->replaygain_data;
|
||||
if (track && track->stream)
|
||||
ao_c->af->replaygain_data = track->stream->codec->replaygain_data;
|
||||
ao_c->spdif_passthrough = true;
|
||||
ao_c->pts = MP_NOPTS_VALUE;
|
||||
ao_c->ao_buffer = mp_audio_buffer_create(NULL);
|
||||
ao_c->ao = mpctx->ao;
|
||||
ao_c->input_format = mp_aframe_create();
|
||||
|
||||
ao_c->filter_src = src;
|
||||
if (!ao_c->filter_src) {
|
||||
if (track) {
|
||||
ao_c->track = track;
|
||||
track->ao_c = ao_c;
|
||||
if (!init_audio_decoder(mpctx, track))
|
||||
@@ -603,7 +604,7 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
|
||||
if (mpctx->ao) {
|
||||
struct mp_audio fmt;
|
||||
ao_get_format(mpctx->ao, &fmt);
|
||||
get_ao_format(mpctx->ao, &fmt);
|
||||
mp_audio_buffer_reinit(ao_c->ao_buffer, &fmt);
|
||||
}
|
||||
|
||||
@@ -613,7 +614,6 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
init_error:
|
||||
uninit_audio_chain(mpctx);
|
||||
uninit_audio_out(mpctx);
|
||||
no_audio:
|
||||
error_on_track(mpctx, track);
|
||||
}
|
||||
|
||||
@@ -625,9 +625,7 @@ double written_audio_pts(struct MPContext *mpctx)
|
||||
if (!ao_c)
|
||||
return MP_NOPTS_VALUE;
|
||||
|
||||
struct mp_audio in_format = ao_c->input_format;
|
||||
|
||||
if (!mp_audio_config_valid(&in_format) || ao_c->af->initialized < 1)
|
||||
if (ao_c->af->initialized < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
|
||||
// first calculate the end pts of audio that has been output by decoder
|
||||
@@ -664,7 +662,7 @@ static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags
|
||||
return 0;
|
||||
struct ao *ao = mpctx->ao;
|
||||
struct mp_audio out_format;
|
||||
ao_get_format(ao, &out_format);
|
||||
get_ao_format(ao, &out_format);
|
||||
#if HAVE_ENCODING
|
||||
encode_lavc_set_audio_pts(mpctx->encode_lavc_ctx, playing_audio_pts(mpctx));
|
||||
#endif
|
||||
@@ -717,7 +715,7 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
return true;
|
||||
|
||||
struct mp_audio out_format = {0};
|
||||
ao_get_format(mpctx->ao, &out_format);
|
||||
get_ao_format(mpctx->ao, &out_format);
|
||||
double play_samplerate = out_format.rate / mpctx->audio_speed;
|
||||
|
||||
if (!opts->initial_audio_sync) {
|
||||
@@ -800,8 +798,11 @@ static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
if (endpts != MP_NOPTS_VALUE) {
|
||||
double rate = afs->output.rate / mpctx->audio_speed;
|
||||
double curpts = written_audio_pts(mpctx);
|
||||
if (curpts != MP_NOPTS_VALUE)
|
||||
maxsamples = (endpts - curpts - mpctx->opts->audio_delay) * rate;
|
||||
if (curpts != MP_NOPTS_VALUE) {
|
||||
double remaining =
|
||||
(endpts - curpts - mpctx->opts->audio_delay) * rate;
|
||||
maxsamples = MPCLAMP(remaining, 0, INT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *mpa = af_read_output_frame(afs);
|
||||
@@ -839,10 +840,14 @@ static int decode_new_frame(struct ao_chain *ao_c)
|
||||
res = audio_get_frame(ao_c->audio_src, &ao_c->input_frame);
|
||||
}
|
||||
|
||||
if (ao_c->input_frame)
|
||||
mp_aframe_config_copy(ao_c->input_format, ao_c->input_frame);
|
||||
|
||||
switch (res) {
|
||||
case DATA_OK: return AD_OK;
|
||||
case DATA_WAIT: return AD_WAIT;
|
||||
case DATA_AGAIN: return AD_NO_PROGRESS;
|
||||
case DATA_STARVE: return AD_STARVE;
|
||||
case DATA_EOF: return AD_EOF;
|
||||
default: abort();
|
||||
}
|
||||
@@ -875,7 +880,9 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
break;
|
||||
|
||||
res = decode_new_frame(ao_c);
|
||||
if (res == AD_NO_PROGRESS || res == AD_WAIT)
|
||||
if (res == AD_NO_PROGRESS)
|
||||
continue;
|
||||
if (res == AD_WAIT || res == AD_STARVE)
|
||||
break;
|
||||
if (res < 0) {
|
||||
// drain filters first (especially for true EOF case)
|
||||
@@ -884,14 +891,19 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
}
|
||||
|
||||
// On format change, make sure to drain the filter chain.
|
||||
if (!mp_audio_config_equals(&afs->input, ao_c->input_frame)) {
|
||||
struct mp_audio in_format;
|
||||
mp_audio_config_from_aframe(&in_format, ao_c->input_format);
|
||||
if (!mp_audio_config_equals(&afs->input, &in_format)) {
|
||||
copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
|
||||
res = AD_NEW_FMT;
|
||||
break;
|
||||
}
|
||||
|
||||
struct mp_audio *mpa = ao_c->input_frame;
|
||||
struct mp_audio *mpa = mp_audio_from_aframe(ao_c->input_frame);
|
||||
talloc_free(ao_c->input_frame);
|
||||
ao_c->input_frame = NULL;
|
||||
if (!mpa)
|
||||
abort();
|
||||
if (mpa->pts == MP_NOPTS_VALUE) {
|
||||
ao_c->pts = MP_NOPTS_VALUE;
|
||||
} else {
|
||||
@@ -967,7 +979,9 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
if (ao_c->af->initialized < 1 || !mpctx->ao) {
|
||||
// Probe the initial audio format. Returns AD_OK (and does nothing) if
|
||||
// the format is already known.
|
||||
int r = decode_new_frame(mpctx->ao_chain);
|
||||
int r = AD_NO_PROGRESS;
|
||||
while (r == AD_NO_PROGRESS)
|
||||
r = decode_new_frame(mpctx->ao_chain);
|
||||
if (r == AD_WAIT)
|
||||
return; // continue later when new data is available
|
||||
if (r == AD_EOF) {
|
||||
@@ -993,7 +1007,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
}
|
||||
|
||||
struct mp_audio out_format = {0};
|
||||
ao_get_format(mpctx->ao, &out_format);
|
||||
get_ao_format(mpctx->ao, &out_format);
|
||||
double play_samplerate = out_format.rate / mpctx->audio_speed;
|
||||
int align = af_format_sample_alignment(out_format.format);
|
||||
|
||||
@@ -1038,7 +1052,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
status = filter_audio(mpctx, ao_c->ao_buffer, playsize);
|
||||
if (status == AD_WAIT)
|
||||
return;
|
||||
if (status == AD_NO_PROGRESS) {
|
||||
if (status == AD_NO_PROGRESS || status == AD_STARVE) {
|
||||
mp_wakeup_core(mpctx);
|
||||
return;
|
||||
}
|
||||
|
||||
+66
-134
@@ -56,7 +56,7 @@
|
||||
#include "video/decode/vd.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "video/csputils.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "video/decode/dec_video.h"
|
||||
@@ -1805,9 +1805,6 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
|
||||
.max = opts->softvol_max,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_NEUTRAL:
|
||||
*(float *)arg = 100;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT:
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)opts->softvol_volume);
|
||||
return M_PROPERTY_OK;
|
||||
@@ -2019,17 +2016,20 @@ static int mp_property_audio_codec(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, c);
|
||||
}
|
||||
|
||||
static int property_audiofmt(struct mp_audio a, int action, void *arg)
|
||||
static int property_audiofmt(struct mp_aframe *fmt, int action, void *arg)
|
||||
{
|
||||
if (!mp_audio_config_valid(&a))
|
||||
if (!fmt || !mp_aframe_config_is_valid(fmt))
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(fmt, &chmap);
|
||||
|
||||
struct m_sub_property props[] = {
|
||||
{"samplerate", SUB_PROP_INT(a.rate)},
|
||||
{"channel-count", SUB_PROP_INT(a.channels.num)},
|
||||
{"channels", SUB_PROP_STR(mp_chmap_to_str(&a.channels))},
|
||||
{"hr-channels", SUB_PROP_STR(mp_chmap_to_str_hr(&a.channels))},
|
||||
{"format", SUB_PROP_STR(af_fmt_to_str(a.format))},
|
||||
{"samplerate", SUB_PROP_INT(mp_aframe_get_rate(fmt))},
|
||||
{"channel-count", SUB_PROP_INT(chmap.num)},
|
||||
{"channels", SUB_PROP_STR(mp_chmap_to_str(&chmap))},
|
||||
{"hr-channels", SUB_PROP_STR(mp_chmap_to_str_hr(&chmap))},
|
||||
{"format", SUB_PROP_STR(af_fmt_to_str(mp_aframe_get_format(fmt)))},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -2040,20 +2040,28 @@ static int mp_property_audio_params(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_audio fmt = {0};
|
||||
if (mpctx->ao_chain)
|
||||
fmt = mpctx->ao_chain->input_format;
|
||||
return property_audiofmt(fmt, action, arg);
|
||||
return property_audiofmt(mpctx->ao_chain ? mpctx->ao_chain->input_format : NULL,
|
||||
action, arg);
|
||||
}
|
||||
|
||||
static int mp_property_audio_out_params(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct mp_audio fmt = {0};
|
||||
if (mpctx->ao)
|
||||
ao_get_format(mpctx->ao, &fmt);
|
||||
return property_audiofmt(fmt, action, arg);
|
||||
struct mp_aframe *frame = NULL;
|
||||
if (mpctx->ao) {
|
||||
frame = mp_aframe_create();
|
||||
int samplerate;
|
||||
int format;
|
||||
struct mp_chmap channels;
|
||||
ao_get_format(mpctx->ao, &samplerate, &format, &channels);
|
||||
mp_aframe_set_rate(frame, samplerate);
|
||||
mp_aframe_set_format(frame, format);
|
||||
mp_aframe_set_chmap(frame, &channels);
|
||||
}
|
||||
int r = property_audiofmt(frame, action, arg);
|
||||
talloc_free(frame);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Balance (RW)
|
||||
@@ -2470,29 +2478,6 @@ static int mp_property_hwdec_interop(void *ctx, struct m_property *prop,
|
||||
return m_property_strdup_ro(action, arg, name);
|
||||
}
|
||||
|
||||
#if HAVE_GPL
|
||||
// Possibly GPL due to 7b25afd7423e9056782993cbd1b32ead64ac1462.
|
||||
static int mp_property_deinterlace(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->vo_chain)
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
*(int *)arg = get_deinterlacing(mpctx) > 0;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_CONSTRICTED_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_SET:
|
||||
set_deinterlacing(mpctx, *(int *)arg);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Helper to set vo flags.
|
||||
/** \ingroup PropertyImplHelper
|
||||
*/
|
||||
@@ -2602,35 +2587,6 @@ static int mp_property_frame_count(void *ctx, struct m_property *prop,
|
||||
return m_property_int_ro(action, arg, frames);
|
||||
}
|
||||
|
||||
#if HAVE_GPL
|
||||
static int mp_property_video_color(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
const char *name = prop->priv ? prop->priv : prop->name;
|
||||
MPContext *mpctx = ctx;
|
||||
if (!mpctx->vo_chain)
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
if (video_set_colors(mpctx->vo_chain, name, *(int *) arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
break;
|
||||
}
|
||||
case M_PROPERTY_GET:
|
||||
if (video_get_colors(mpctx->vo_chain, name, (int *)arg) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
// Write new value to option variable
|
||||
mp_property_generic_option(mpctx, prop, M_PROPERTY_SET, arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_NEUTRAL:
|
||||
*(int *)arg = 0;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Video codec tag (RO)
|
||||
static int mp_property_video_format(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
@@ -2926,12 +2882,8 @@ static void get_frame_perf(struct mpv_node *node, struct mp_frame_perf *perf)
|
||||
node_map_add(pass, "peak", MPV_FORMAT_INT64)->u.int64 = data->peak;
|
||||
node_map_add(pass, "count", MPV_FORMAT_INT64)->u.int64 = data->count;
|
||||
struct mpv_node *samples = node_map_add(pass, "samples", MPV_FORMAT_NODE_ARRAY);
|
||||
|
||||
int idx = data->index;
|
||||
for (int n = 0; n < data->count; n++) {
|
||||
node_array_add(samples, MPV_FORMAT_INT64)->u.int64 = data->samples[idx];
|
||||
idx = (idx + 1) % PERF_SAMPLE_COUNT;
|
||||
}
|
||||
for (int n = 0; n < data->count; n++)
|
||||
node_array_add(samples, MPV_FORMAT_INT64)->u.int64 = data->samples[n];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2961,19 +2913,21 @@ static int mp_property_vo_passes(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
struct voctrl_performance_data data = {0};
|
||||
if (vo_control(mpctx->video_out, VOCTRL_PERFORMANCE_DATA, &data) <= 0)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
int ret = M_PROPERTY_UNAVAILABLE;
|
||||
struct voctrl_performance_data *data = talloc_ptrtype(NULL, data);
|
||||
if (vo_control(mpctx->video_out, VOCTRL_PERFORMANCE_DATA, data) <= 0)
|
||||
goto out;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_PRINT: {
|
||||
char *res = NULL;
|
||||
res = talloc_asprintf_append(res, "fresh:\n");
|
||||
res = asprint_perf(res, &data.fresh);
|
||||
res = asprint_perf(res, &data->fresh);
|
||||
res = talloc_asprintf_append(res, "\nredraw:\n");
|
||||
res = asprint_perf(res, &data.redraw);
|
||||
res = asprint_perf(res, &data->redraw);
|
||||
*(char **)arg = res;
|
||||
return M_PROPERTY_OK;
|
||||
ret = M_PROPERTY_OK;
|
||||
goto out;
|
||||
}
|
||||
|
||||
case M_PROPERTY_GET: {
|
||||
@@ -2981,14 +2935,19 @@ static int mp_property_vo_passes(void *ctx, struct m_property *prop,
|
||||
node_init(&node, MPV_FORMAT_NODE_MAP, NULL);
|
||||
struct mpv_node *fresh = node_map_add(&node, "fresh", MPV_FORMAT_NODE_ARRAY);
|
||||
struct mpv_node *redraw = node_map_add(&node, "redraw", MPV_FORMAT_NODE_ARRAY);
|
||||
get_frame_perf(fresh, &data.fresh);
|
||||
get_frame_perf(redraw, &data.redraw);
|
||||
get_frame_perf(fresh, &data->fresh);
|
||||
get_frame_perf(redraw, &data->redraw);
|
||||
*(struct mpv_node *)arg = node;
|
||||
return M_PROPERTY_OK;
|
||||
ret = M_PROPERTY_OK;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
ret = M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
out:
|
||||
talloc_free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp_property_vo(void *ctx, struct m_property *p, int action, void *arg)
|
||||
@@ -4023,22 +3982,10 @@ static const struct m_property mp_properties_base[] = {
|
||||
|
||||
// Video
|
||||
{"fullscreen", mp_property_fullscreen},
|
||||
#if HAVE_GPL
|
||||
{"deinterlace", mp_property_deinterlace},
|
||||
#endif
|
||||
{"taskbar-progress", mp_property_taskbar_progress},
|
||||
{"ontop", mp_property_ontop},
|
||||
{"border", mp_property_border},
|
||||
{"on-all-workspaces", mp_property_all_workspaces},
|
||||
#if HAVE_GPL
|
||||
{"gamma", mp_property_video_color},
|
||||
{"brightness", mp_property_video_color},
|
||||
{"contrast", mp_property_video_color},
|
||||
{"saturation", mp_property_video_color},
|
||||
{"hue", mp_property_video_color},
|
||||
{"video-output-levels", mp_property_video_color,
|
||||
.priv = (void *)"output-levels"},
|
||||
#endif
|
||||
{"video-out-params", mp_property_vo_imgparams},
|
||||
{"video-dec-params", mp_property_dec_imgparams},
|
||||
{"video-params", mp_property_vd_imgparams},
|
||||
@@ -4354,6 +4301,8 @@ static const struct property_osd_display {
|
||||
int osd_progbar;
|
||||
// Needs special ways to display the new value (seeks are delayed)
|
||||
int seek_msg, seek_bar;
|
||||
// Show a marker thing on OSD bar. Ignored if osd_progbar==0.
|
||||
float marker;
|
||||
// Free-form message (if NULL, osd_name or the property name is used)
|
||||
const char *msg;
|
||||
} property_osd_display[] = {
|
||||
@@ -4368,10 +4317,10 @@ static const struct property_osd_display {
|
||||
// audio
|
||||
{ "volume", "Volume",
|
||||
.msg = "Volume: ${?volume:${volume}% ${?mute==yes:(Muted)}}${!volume:${volume}}",
|
||||
.osd_progbar = OSD_VOLUME },
|
||||
.osd_progbar = OSD_VOLUME, .marker = 100 },
|
||||
{ "ao-volume", "AO Volume",
|
||||
.msg = "AO Volume: ${?ao-volume:${ao-volume}% ${?ao-mute==yes:(Muted)}}${!ao-volume:${ao-volume}}",
|
||||
.osd_progbar = OSD_VOLUME },
|
||||
.osd_progbar = OSD_VOLUME, .marker = 100 },
|
||||
{ "mute", "Mute" },
|
||||
{ "ao-mute", "AO Mute" },
|
||||
{ "audio-delay", "A-V delay" },
|
||||
@@ -4469,13 +4418,15 @@ static void show_property_osd(MPContext *mpctx, const char *name, int osd_mode)
|
||||
if ((osd_mode & MP_ON_OSD_BAR) && (prop.flags & CONF_RANGE) == CONF_RANGE) {
|
||||
if (prop.type == CONF_TYPE_INT) {
|
||||
int n = prop.min;
|
||||
mp_property_do(name, M_PROPERTY_GET_NEUTRAL, &n, mpctx);
|
||||
if (disp.osd_progbar)
|
||||
n = disp.marker;
|
||||
int i;
|
||||
if (mp_property_do(name, M_PROPERTY_GET, &i, mpctx) > 0)
|
||||
set_osd_bar(mpctx, disp.osd_progbar, prop.min, prop.max, n, i);
|
||||
} else if (prop.type == CONF_TYPE_FLOAT) {
|
||||
float n = prop.min;
|
||||
mp_property_do(name, M_PROPERTY_GET_NEUTRAL, &n, mpctx);
|
||||
if (disp.osd_progbar)
|
||||
n = disp.marker;
|
||||
float f;
|
||||
if (mp_property_do(name, M_PROPERTY_GET, &f, mpctx) > 0)
|
||||
set_osd_bar(mpctx, disp.osd_progbar, prop.min, prop.max, n, f);
|
||||
@@ -5194,28 +5145,6 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
break;
|
||||
}
|
||||
|
||||
#if HAVE_GPL
|
||||
// Potentially GPL due to 8d190244d21a4d40bb9e8f7d51aa09ca1888de09.
|
||||
case MP_CMD_OSD: {
|
||||
MP_WARN(mpctx, "The 'osd' command is deprecated. "
|
||||
"Use 'cycle osd-level' instead.\n");
|
||||
int v = cmd->args[0].v.i;
|
||||
#define MAX_OSD_LEVEL 3
|
||||
if (opts->osd_level > MAX_OSD_LEVEL)
|
||||
opts->osd_level = MAX_OSD_LEVEL;
|
||||
if (v < 0)
|
||||
opts->osd_level = (opts->osd_level + 1) % (MAX_OSD_LEVEL + 1);
|
||||
else
|
||||
opts->osd_level = MPCLAMP(v, 0, MAX_OSD_LEVEL);
|
||||
if (opts->osd_level > 0 && (on_osd & MP_ON_OSD_MSG))
|
||||
set_osd_msg(mpctx, osdl, osd_duration, "OSD level: %d", opts->osd_level);
|
||||
if (opts->osd_level == 0)
|
||||
set_osd_msg(mpctx, 0, 0, "");
|
||||
mp_wakeup_core(mpctx);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case MP_CMD_PRINT_TEXT: {
|
||||
MP_INFO(mpctx, "%s\n", cmd->args[0].v.s);
|
||||
break;
|
||||
@@ -5675,7 +5604,12 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
return -1;
|
||||
}
|
||||
const bool dbc = cmd->args[3].v.i;
|
||||
button += dbc ? MP_MOUSE_BASE_DBL : MP_MOUSE_BASE;
|
||||
if (dbc && button > (MP_MBTN_RIGHT - MP_MBTN_BASE)) {
|
||||
MP_ERR(mpctx, "%d is not a valid mouse button for double-clicks.\n",
|
||||
button);
|
||||
return -1;
|
||||
}
|
||||
button += dbc ? MP_MBTN_DBL_BASE : MP_MBTN_BASE;
|
||||
mp_input_set_mouse_pos_artificial(mpctx->input, x, y);
|
||||
mp_input_put_key_artificial(mpctx->input, button);
|
||||
break;
|
||||
@@ -5850,7 +5784,7 @@ void mp_notify(struct MPContext *mpctx, int event, void *arg)
|
||||
|
||||
static void update_priority(struct MPContext *mpctx)
|
||||
{
|
||||
#if HAVE_WIN32_DESKTOP && HAVE_GPL
|
||||
#if HAVE_WIN32_DESKTOP
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
if (opts->w32_priority > 0)
|
||||
SetPriorityClass(GetCurrentProcess(), opts->w32_priority);
|
||||
@@ -5865,13 +5799,8 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags)
|
||||
if (flags & UPDATE_TERM)
|
||||
mp_update_logging(mpctx, false);
|
||||
|
||||
if (mpctx->video_out) {
|
||||
if (flags & UPDATE_VIDEOPOS)
|
||||
vo_control(mpctx->video_out, VOCTRL_SET_PANSCAN, NULL);
|
||||
|
||||
if (flags & UPDATE_RENDERER)
|
||||
vo_control(mpctx->video_out, VOCTRL_UPDATE_RENDER_OPTS, NULL);
|
||||
}
|
||||
if (flags & UPDATE_DEINT)
|
||||
recreate_auto_filters(mpctx);
|
||||
|
||||
if (flags & UPDATE_OSD) {
|
||||
osd_changed(mpctx->osd);
|
||||
@@ -5923,6 +5852,9 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags)
|
||||
|
||||
if (flags & UPDATE_VOL)
|
||||
audio_update_volume(mpctx);
|
||||
|
||||
if (flags & UPDATE_LAVFI_COMPLEX)
|
||||
update_lavfi_complex(mpctx);
|
||||
}
|
||||
|
||||
void mp_notify_property(struct MPContext *mpctx, const char *property)
|
||||
|
||||
+21
-19
@@ -28,7 +28,7 @@
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "sub/osd.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "video/out/vo.h"
|
||||
|
||||
@@ -73,8 +73,8 @@ enum seek_type {
|
||||
};
|
||||
|
||||
enum seek_precision {
|
||||
MPSEEK_DEFAULT = 0,
|
||||
// The following values are numerically sorted by increasing precision
|
||||
MPSEEK_DEFAULT = 0,
|
||||
MPSEEK_KEYFRAME,
|
||||
MPSEEK_EXACT,
|
||||
MPSEEK_VERY_EXACT,
|
||||
@@ -85,6 +85,13 @@ enum seek_flags {
|
||||
MPSEEK_FLAG_NOFLUSH = 1 << 1, // keeping remaining data for seamless loops
|
||||
};
|
||||
|
||||
struct seek_params {
|
||||
enum seek_type type;
|
||||
enum seek_precision exact;
|
||||
double amount;
|
||||
unsigned flags; // MPSEEK_FLAG_*
|
||||
};
|
||||
|
||||
enum video_sync {
|
||||
VS_DEFAULT = 0,
|
||||
VS_DISP_RESAMPLE,
|
||||
@@ -198,10 +205,10 @@ struct ao_chain {
|
||||
double ao_resume_time;
|
||||
|
||||
// 1-element input frame queue.
|
||||
struct mp_audio *input_frame;
|
||||
struct mp_aframe *input_frame;
|
||||
|
||||
// Last known input_mpi format (so vf can be reinitialized any time).
|
||||
struct mp_audio input_format;
|
||||
// Last known input_mpi format (so af can be reinitialized any time).
|
||||
struct mp_aframe *input_format;
|
||||
|
||||
struct track *track;
|
||||
struct lavfi_pad *filter_src;
|
||||
@@ -306,7 +313,7 @@ typedef struct MPContext {
|
||||
struct lavfi *lavfi;
|
||||
|
||||
struct ao *ao;
|
||||
struct mp_audio *ao_decoder_fmt; // for weak gapless audio check
|
||||
struct mp_aframe *ao_decoder_fmt; // for weak gapless audio check
|
||||
struct ao_chain *ao_chain;
|
||||
|
||||
struct vo_chain *vo_chain;
|
||||
@@ -339,6 +346,7 @@ typedef struct MPContext {
|
||||
bool hrseek_lastframe; // drop everything until last frame reached
|
||||
bool hrseek_backstep; // go to frame before seek target
|
||||
double hrseek_pts;
|
||||
struct seek_params current_seek;
|
||||
bool ab_loop_clip; // clip to the "b" part of an A-B loop if available
|
||||
// AV sync: the next frame should be shown when the audio out has this
|
||||
// much (in seconds) buffered data left. Increased when more data is
|
||||
@@ -396,13 +404,7 @@ typedef struct MPContext {
|
||||
// Used to turn a new time value to a delta from last time.
|
||||
int64_t last_time;
|
||||
|
||||
// Used to communicate the parameters of a seek between parts
|
||||
struct seek_params {
|
||||
enum seek_type type;
|
||||
enum seek_precision exact;
|
||||
double amount;
|
||||
unsigned flags; // MPSEEK_FLAG_*
|
||||
} seek;
|
||||
struct seek_params seek;
|
||||
|
||||
// Allow audio to issue a second seek if audio is too far ahead (for non-hr
|
||||
// seeks with external audio tracks).
|
||||
@@ -478,7 +480,7 @@ void update_playback_speed(struct MPContext *mpctx);
|
||||
void uninit_audio_out(struct MPContext *mpctx);
|
||||
void uninit_audio_chain(struct MPContext *mpctx);
|
||||
int init_audio_decoder(struct MPContext *mpctx, struct track *track);
|
||||
void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src);
|
||||
void reinit_audio_chain_src(struct MPContext *mpctx, struct track *track);
|
||||
void audio_update_volume(struct MPContext *mpctx);
|
||||
void audio_update_balance(struct MPContext *mpctx);
|
||||
void reload_audio_output(struct MPContext *mpctx);
|
||||
@@ -522,6 +524,7 @@ void prefetch_next(struct MPContext *mpctx);
|
||||
void close_recorder(struct MPContext *mpctx);
|
||||
void close_recorder_and_error(struct MPContext *mpctx);
|
||||
void open_recorder(struct MPContext *mpctx, bool on_init);
|
||||
void update_lavfi_complex(struct MPContext *mpctx);
|
||||
|
||||
// main.c
|
||||
int mp_initialize(struct MPContext *mpctx, char **argv);
|
||||
@@ -529,6 +532,7 @@ struct MPContext *mp_create(void);
|
||||
void mp_destroy(struct MPContext *mpctx);
|
||||
void mp_print_version(struct mp_log *log, int always);
|
||||
void mp_update_logging(struct MPContext *mpctx, bool preinit);
|
||||
void issue_refresh_seek(struct MPContext *mpctx, enum seek_precision min_prec);
|
||||
|
||||
// misc.c
|
||||
double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t);
|
||||
@@ -608,11 +612,10 @@ bool update_subtitles(struct MPContext *mpctx, double video_pts);
|
||||
// video.c
|
||||
int video_get_colors(struct vo_chain *vo_c, const char *item, int *value);
|
||||
int video_set_colors(struct vo_chain *vo_c, const char *item, int value);
|
||||
int video_vf_vo_control(struct vo_chain *vo_c, int vf_cmd, void *data);
|
||||
void reset_video_state(struct MPContext *mpctx);
|
||||
int init_video_decoder(struct MPContext *mpctx, struct track *track);
|
||||
int reinit_video_chain(struct MPContext *mpctx);
|
||||
int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src);
|
||||
void reinit_video_chain(struct MPContext *mpctx);
|
||||
void reinit_video_chain_src(struct MPContext *mpctx, struct track *track);
|
||||
int reinit_video_filters(struct MPContext *mpctx);
|
||||
void write_video(struct MPContext *mpctx);
|
||||
void mp_force_video_refresh(struct MPContext *mpctx);
|
||||
@@ -620,8 +623,7 @@ void uninit_video_out(struct MPContext *mpctx);
|
||||
void uninit_video_chain(struct MPContext *mpctx);
|
||||
double calc_average_frame_duration(struct MPContext *mpctx);
|
||||
int init_video_decoder(struct MPContext *mpctx, struct track *track);
|
||||
int get_deinterlacing(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, int opt_val);
|
||||
void recreate_auto_filters(struct MPContext *mpctx);
|
||||
|
||||
// Values of MPOpts.softvol
|
||||
enum {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub",
|
||||
"srt", "rt", "ssa", "ass", "mks", "vtt",
|
||||
"sup", "scc",
|
||||
"sup", "scc", "smi",
|
||||
NULL};
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
|
||||
+51
-29
@@ -37,7 +37,8 @@
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
@@ -96,7 +97,7 @@ struct lavfi_pad {
|
||||
|
||||
// 1-frame queue (used for both input and output)
|
||||
struct mp_image *pending_v;
|
||||
struct mp_audio *pending_a;
|
||||
struct mp_aframe *pending_a;
|
||||
|
||||
// -- dir==LAVFI_IN
|
||||
|
||||
@@ -107,7 +108,7 @@ struct lavfi_pad {
|
||||
|
||||
// used to check for format changes manually
|
||||
struct mp_image *in_fmt_v;
|
||||
struct mp_audio in_fmt_a;
|
||||
struct mp_aframe *in_fmt_a;
|
||||
|
||||
// -- dir==LAVFI_OUT
|
||||
|
||||
@@ -199,7 +200,7 @@ static void free_graph(struct lavfi *c)
|
||||
pad->filter_pad = -1;
|
||||
pad->buffer = NULL;
|
||||
TA_FREEP(&pad->in_fmt_v);
|
||||
pad->in_fmt_a = (struct mp_audio){0};
|
||||
TA_FREEP(&pad->in_fmt_a);
|
||||
pad->buffer_is_eof = false;
|
||||
pad->input_needed = false;
|
||||
pad->input_waiting = false;
|
||||
@@ -249,12 +250,19 @@ struct lavfi *lavfi_create(struct mp_log *log, char *graph_string)
|
||||
|
||||
void lavfi_destroy(struct lavfi *c)
|
||||
{
|
||||
if (!c)
|
||||
return;
|
||||
free_graph(c);
|
||||
clear_data(c);
|
||||
av_frame_free(&c->tmp_frame);
|
||||
talloc_free(c);
|
||||
}
|
||||
|
||||
const char *lavfi_get_graph(struct lavfi *c)
|
||||
{
|
||||
return c->graph_string;
|
||||
}
|
||||
|
||||
struct lavfi_pad *lavfi_find_pad(struct lavfi *c, char *name)
|
||||
{
|
||||
for (int n = 0; n < c->num_pads; n++) {
|
||||
@@ -305,9 +313,14 @@ static void send_global_eof(struct lavfi *c)
|
||||
|
||||
// libavfilter allows changing some parameters on the fly, but not
|
||||
// others.
|
||||
static bool is_aformat_ok(struct mp_audio *a, struct mp_audio *b)
|
||||
static bool is_aformat_ok(struct mp_aframe *a, struct mp_aframe *b)
|
||||
{
|
||||
return mp_audio_config_equals(a, b);
|
||||
struct mp_chmap ca = {0}, cb = {0};
|
||||
mp_aframe_get_chmap(a, &ca);
|
||||
mp_aframe_get_chmap(b, &cb);
|
||||
return mp_chmap_equals(&ca, &cb) &&
|
||||
mp_aframe_get_rate(a) == mp_aframe_get_rate(b) &&
|
||||
mp_aframe_get_format(a) == mp_aframe_get_format(b);
|
||||
}
|
||||
static bool is_vformat_ok(struct mp_image *a, struct mp_image *b)
|
||||
{
|
||||
@@ -324,9 +337,9 @@ static void check_format_changes(struct lavfi *c)
|
||||
if (!pad->buffer || pad->dir != LAVFI_IN)
|
||||
continue;
|
||||
|
||||
if (pad->type == STREAM_AUDIO && pad->pending_a && pad->in_fmt_a.format) {
|
||||
if (pad->type == STREAM_AUDIO && pad->pending_a && pad->in_fmt_a) {
|
||||
c->draining_new_format |= !is_aformat_ok(pad->pending_a,
|
||||
&pad->in_fmt_a);
|
||||
pad->in_fmt_a);
|
||||
}
|
||||
if (pad->type == STREAM_VIDEO && pad->pending_v && pad->in_fmt_v) {
|
||||
c->draining_new_format |= !is_vformat_ok(pad->pending_v,
|
||||
@@ -382,7 +395,10 @@ static bool init_pads(struct lavfi *c)
|
||||
|
||||
if (pad->pending_a) {
|
||||
assert(pad->type == STREAM_AUDIO);
|
||||
mp_audio_copy_config(&pad->in_fmt_a, pad->pending_a);
|
||||
pad->in_fmt_a = mp_aframe_new_ref(pad->pending_a);
|
||||
if (!pad->in_fmt_a)
|
||||
goto error;
|
||||
mp_aframe_unref_data(pad->in_fmt_a);
|
||||
} else if (pad->pending_v) {
|
||||
assert(pad->type == STREAM_VIDEO);
|
||||
pad->in_fmt_v = mp_image_new_ref(pad->pending_v);
|
||||
@@ -393,9 +409,11 @@ static bool init_pads(struct lavfi *c)
|
||||
// libavfilter makes this painful. Init it with a dummy config,
|
||||
// just so we can tell it the stream is EOF.
|
||||
if (pad->type == STREAM_AUDIO) {
|
||||
mp_audio_set_format(&pad->in_fmt_a, AF_FORMAT_FLOAT);
|
||||
mp_audio_set_num_channels(&pad->in_fmt_a, 2);
|
||||
pad->in_fmt_a.rate = 48000;
|
||||
pad->in_fmt_a = mp_aframe_create();
|
||||
mp_aframe_set_format(pad->in_fmt_a, AF_FORMAT_FLOAT);
|
||||
mp_aframe_set_chmap(pad->in_fmt_a,
|
||||
&(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(pad->in_fmt_a, 48000);
|
||||
} else if (pad->type == STREAM_VIDEO) {
|
||||
pad->in_fmt_v = talloc_zero(NULL, struct mp_image);
|
||||
mp_image_setfmt(pad->in_fmt_v, IMGFMT_420P);
|
||||
@@ -414,11 +432,13 @@ static bool init_pads(struct lavfi *c)
|
||||
char *filter_name = NULL;
|
||||
if (pad->type == STREAM_AUDIO) {
|
||||
params->time_base = pad->timebase =
|
||||
(AVRational){1, pad->in_fmt_a.rate};
|
||||
params->format = af_to_avformat(pad->in_fmt_a.format);
|
||||
params->sample_rate = pad->in_fmt_a.rate;
|
||||
params->channel_layout =
|
||||
mp_chmap_to_lavc(&pad->in_fmt_a.channels);
|
||||
(AVRational){1, mp_aframe_get_rate(pad->in_fmt_a)};
|
||||
params->format =
|
||||
af_to_avformat(mp_aframe_get_format(pad->in_fmt_a));
|
||||
params->sample_rate = mp_aframe_get_rate(pad->in_fmt_a);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(pad->in_fmt_a, &chmap);
|
||||
params->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
filter_name = "abuffer";
|
||||
} else if (pad->type == STREAM_VIDEO) {
|
||||
params->time_base = pad->timebase = AV_TIME_BASE_Q;
|
||||
@@ -484,9 +504,11 @@ static void dump_graph(struct lavfi *c)
|
||||
#endif
|
||||
}
|
||||
|
||||
void lavfi_set_hwdec_devs(struct lavfi *c, struct mp_hwdec_devices *hwdevs)
|
||||
void lavfi_pad_set_hwdec_devs(struct lavfi_pad *pad,
|
||||
struct mp_hwdec_devices *hwdevs)
|
||||
{
|
||||
c->hwdec_devs = hwdevs;
|
||||
// We don't actually treat this per-pad.
|
||||
pad->main->hwdec_devs = hwdevs;
|
||||
}
|
||||
|
||||
// Initialize the graph if all inputs have formats set. If it's already
|
||||
@@ -556,8 +578,8 @@ static void feed_input_pads(struct lavfi *c)
|
||||
frame = mp_image_to_av_frame_and_unref(pad->pending_v);
|
||||
pad->pending_v = NULL;
|
||||
} else if (pad->pending_a) {
|
||||
pts = pad->pending_a->pts;
|
||||
frame = mp_audio_to_avframe_and_unref(pad->pending_a);
|
||||
pts = mp_aframe_get_pts(pad->pending_a);
|
||||
frame = mp_aframe_to_avframe_and_unref(pad->pending_a);
|
||||
pad->pending_a = NULL;
|
||||
} else {
|
||||
if (!pad->input_eof) {
|
||||
@@ -612,9 +634,9 @@ static void read_output_pads(struct lavfi *c)
|
||||
pad->output_needed = false;
|
||||
double pts = mp_pts_from_av(c->tmp_frame->pts, &pad->timebase);
|
||||
if (pad->type == STREAM_AUDIO) {
|
||||
pad->pending_a = mp_audio_from_avframe(c->tmp_frame);
|
||||
pad->pending_a = mp_aframe_from_avframe(c->tmp_frame);
|
||||
if (pad->pending_a)
|
||||
pad->pending_a->pts = pts;
|
||||
mp_aframe_set_pts(pad->pending_a, pts);
|
||||
} else if (pad->type == STREAM_VIDEO) {
|
||||
pad->pending_v = mp_image_from_av_frame(c->tmp_frame);
|
||||
if (pad->pending_v)
|
||||
@@ -716,15 +738,15 @@ static int lavfi_request_frame(struct lavfi_pad *pad)
|
||||
} else if (pad->main->all_waiting) {
|
||||
return DATA_WAIT;
|
||||
}
|
||||
return DATA_AGAIN;
|
||||
return DATA_STARVE;
|
||||
}
|
||||
|
||||
// Try to read a new frame from an output pad. Returns one of the following:
|
||||
// DATA_OK: a frame is returned
|
||||
// DATA_AGAIN: needs more input data
|
||||
// DATA_STARVE: needs more input data
|
||||
// DATA_WAIT: needs more input data, and all inputs in LAVFI_WAIT state
|
||||
// DATA_EOF: no more data
|
||||
int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_audio **out_aframe)
|
||||
int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_aframe **out_aframe)
|
||||
{
|
||||
int r = lavfi_request_frame(pad);
|
||||
*out_aframe = pad->pending_a;
|
||||
@@ -750,7 +772,7 @@ bool lavfi_needs_input(struct lavfi_pad *pad)
|
||||
|
||||
// A filter user is supposed to call lavfi_needs_input(), and if that returns
|
||||
// true, send either a new status or a frame. A status can be one of:
|
||||
// DATA_AGAIN: a new frame/status will come, caller will retry
|
||||
// DATA_STARVE: a new frame/status will come, caller will retry
|
||||
// DATA_WAIT: a new frame/status will come, but caller goes to sleep
|
||||
// DATA_EOF: no more input possible (in near time)
|
||||
// If you have a new frame, use lavfi_send_frame_ instead.
|
||||
@@ -764,7 +786,7 @@ void lavfi_send_status(struct lavfi_pad *pad, int status)
|
||||
assert(!pad->pending_v && !pad->pending_a);
|
||||
|
||||
pad->input_waiting = status == DATA_WAIT || status == DATA_EOF;
|
||||
pad->input_again = status == DATA_AGAIN;
|
||||
pad->input_again = status == DATA_STARVE;
|
||||
pad->input_eof = status == DATA_EOF;
|
||||
}
|
||||
|
||||
@@ -778,7 +800,7 @@ static void lavfi_sent_frame(struct lavfi_pad *pad)
|
||||
}
|
||||
|
||||
// See lavfi_send_status() for remarks.
|
||||
void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_audio *aframe)
|
||||
void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_aframe *aframe)
|
||||
{
|
||||
assert(pad->type == STREAM_AUDIO);
|
||||
assert(!pad->pending_a);
|
||||
|
||||
+6
-4
@@ -5,7 +5,7 @@ struct mp_log;
|
||||
struct lavfi;
|
||||
struct lavfi_pad;
|
||||
struct mp_image;
|
||||
struct mp_audio;
|
||||
struct mp_aframe;
|
||||
|
||||
enum lavfi_direction {
|
||||
LAVFI_IN = 1,
|
||||
@@ -13,6 +13,7 @@ enum lavfi_direction {
|
||||
};
|
||||
|
||||
struct lavfi *lavfi_create(struct mp_log *log, char *graph_string);
|
||||
const char *lavfi_get_graph(struct lavfi *c);
|
||||
void lavfi_destroy(struct lavfi *c);
|
||||
struct lavfi_pad *lavfi_find_pad(struct lavfi *c, char *name);
|
||||
enum lavfi_direction lavfi_pad_direction(struct lavfi_pad *pad);
|
||||
@@ -22,12 +23,13 @@ bool lavfi_get_connected(struct lavfi_pad *pad);
|
||||
bool lavfi_process(struct lavfi *c);
|
||||
bool lavfi_has_failed(struct lavfi *c);
|
||||
void lavfi_seek_reset(struct lavfi *c);
|
||||
void lavfi_set_hwdec_devs(struct lavfi *c, struct mp_hwdec_devices *hwdevs);
|
||||
int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_audio **out_aframe);
|
||||
void lavfi_pad_set_hwdec_devs(struct lavfi_pad *pad,
|
||||
struct mp_hwdec_devices *hwdevs);
|
||||
int lavfi_request_frame_a(struct lavfi_pad *pad, struct mp_aframe **out_aframe);
|
||||
int lavfi_request_frame_v(struct lavfi_pad *pad, struct mp_image **out_vframe);
|
||||
bool lavfi_needs_input(struct lavfi_pad *pad);
|
||||
void lavfi_send_status(struct lavfi_pad *pad, int status);
|
||||
void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_audio *aframe);
|
||||
void lavfi_send_frame_a(struct lavfi_pad *pad, struct mp_aframe *aframe);
|
||||
void lavfi_send_frame_v(struct lavfi_pad *pad, struct mp_image *vframe);
|
||||
|
||||
#endif
|
||||
+126
-53
@@ -43,8 +43,6 @@
|
||||
#include "common/recorder.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "demux/demux.h"
|
||||
@@ -945,21 +943,83 @@ void prefetch_next(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
static bool init_complex_filters(struct MPContext *mpctx)
|
||||
// Destroy the complex filter, and remove the references to the filter pads.
|
||||
// (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO
|
||||
// that are not connected anymore due to this.)
|
||||
static void deassociate_complex_filters(struct MPContext *mpctx)
|
||||
{
|
||||
assert(!mpctx->lavfi);
|
||||
for (int n = 0; n < mpctx->num_tracks; n++)
|
||||
mpctx->tracks[n]->sink = NULL;
|
||||
if (mpctx->vo_chain)
|
||||
mpctx->vo_chain->filter_src = NULL;
|
||||
if (mpctx->ao_chain)
|
||||
mpctx->ao_chain->filter_src = NULL;
|
||||
lavfi_destroy(mpctx->lavfi);
|
||||
mpctx->lavfi = NULL;
|
||||
}
|
||||
|
||||
// Close all decoders and sinks (AO/VO) that are not connected to either
|
||||
// a track or a filter pad.
|
||||
static void cleanup_deassociated_complex_filters(struct MPContext *mpctx)
|
||||
{
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
if (!(track->sink || track->vo_c || track->ao_c)) {
|
||||
if (track->d_video && !track->vo_c) {
|
||||
video_uninit(track->d_video);
|
||||
track->d_video = NULL;
|
||||
}
|
||||
if (track->d_audio && !track->ao_c) {
|
||||
audio_uninit(track->d_audio);
|
||||
track->d_audio = NULL;
|
||||
}
|
||||
track->selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->vo_chain && !mpctx->vo_chain->video_src &&
|
||||
!mpctx->vo_chain->filter_src)
|
||||
{
|
||||
uninit_video_chain(mpctx);
|
||||
}
|
||||
if (mpctx->ao_chain && !mpctx->ao_chain->audio_src &&
|
||||
!mpctx->ao_chain->filter_src)
|
||||
{
|
||||
uninit_audio_chain(mpctx);
|
||||
}
|
||||
}
|
||||
|
||||
// >0: changed, 0: no change, -1: error
|
||||
static int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit)
|
||||
{
|
||||
char *graph = mpctx->opts->lavfi_complex;
|
||||
bool have_graph = graph && graph[0] && !force_uninit;
|
||||
if (have_graph && mpctx->lavfi &&
|
||||
strcmp(graph, lavfi_get_graph(mpctx->lavfi)) == 0 &&
|
||||
!lavfi_has_failed(mpctx->lavfi))
|
||||
return 0;
|
||||
if (!mpctx->lavfi && !have_graph)
|
||||
return 0;
|
||||
|
||||
if (!graph || !graph[0])
|
||||
return true;
|
||||
// Deassociate the old filter pads. We leave both sources (tracks) and
|
||||
// sinks (AO/VO) "dangling", connected to neither track or filter pad.
|
||||
// Later, we either reassociate them with new pads, or uninit them if
|
||||
// they are still dangling. This avoids too interruptive actions like
|
||||
// recreating the VO.
|
||||
deassociate_complex_filters(mpctx);
|
||||
|
||||
bool success = false;
|
||||
if (!have_graph) {
|
||||
success = true; // normal full removal of graph
|
||||
goto done;
|
||||
}
|
||||
|
||||
mpctx->lavfi = lavfi_create(mpctx->log, graph);
|
||||
if (!mpctx->lavfi)
|
||||
return false;
|
||||
goto done;
|
||||
|
||||
if (lavfi_has_failed(mpctx->lavfi))
|
||||
return false;
|
||||
goto done;
|
||||
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
@@ -983,6 +1043,12 @@ static bool init_complex_filters(struct MPContext *mpctx)
|
||||
if (lavfi_get_connected(pad))
|
||||
continue;
|
||||
|
||||
assert(!track->sink);
|
||||
if (track->vo_c || track->ao_c) {
|
||||
MP_ERR(mpctx, "Pad %s tries to connect to already selected track.\n",
|
||||
label);
|
||||
goto done;
|
||||
}
|
||||
track->sink = pad;
|
||||
lavfi_set_connected(pad, true);
|
||||
track->selected = true;
|
||||
@@ -992,66 +1058,76 @@ static bool init_complex_filters(struct MPContext *mpctx)
|
||||
if (pad && lavfi_pad_type(pad) == STREAM_VIDEO &&
|
||||
lavfi_pad_direction(pad) == LAVFI_OUT)
|
||||
{
|
||||
if (mpctx->vo_chain) {
|
||||
if (mpctx->vo_chain->video_src) {
|
||||
MP_ERR(mpctx, "Pad vo tries to connected to already used VO.\n");
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
reinit_video_chain_src(mpctx, NULL);
|
||||
if (!mpctx->vo_chain)
|
||||
goto done;
|
||||
}
|
||||
lavfi_set_connected(pad, true);
|
||||
reinit_video_chain_src(mpctx, pad);
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
vo_c->filter_src = pad;
|
||||
lavfi_pad_set_hwdec_devs(vo_c->filter_src, vo_c->hwdec_devs);
|
||||
}
|
||||
|
||||
pad = lavfi_find_pad(mpctx->lavfi, "ao");
|
||||
if (pad && lavfi_pad_type(pad) == STREAM_AUDIO &&
|
||||
lavfi_pad_direction(pad) == LAVFI_OUT)
|
||||
{
|
||||
if (mpctx->ao_chain) {
|
||||
if (mpctx->ao_chain->audio_src) {
|
||||
MP_ERR(mpctx, "Pad ao tries to connected to already used AO.\n");
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
reinit_audio_chain_src(mpctx, NULL);
|
||||
if (!mpctx->ao_chain)
|
||||
goto done;
|
||||
}
|
||||
lavfi_set_connected(pad, true);
|
||||
reinit_audio_chain_src(mpctx, pad);
|
||||
mpctx->ao_chain->filter_src = pad;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_complex_filter_decoders(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->lavfi)
|
||||
return true;
|
||||
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
if (track->sink && track->type == STREAM_VIDEO) {
|
||||
if (!init_video_decoder(mpctx, track))
|
||||
return false;
|
||||
if (!track->d_video && !init_video_decoder(mpctx, track))
|
||||
goto done;
|
||||
}
|
||||
if (track->sink && track->type == STREAM_AUDIO) {
|
||||
if (!init_audio_decoder(mpctx, track))
|
||||
return false;
|
||||
if (!track->d_audio && !init_audio_decoder(mpctx, track))
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
success = true;
|
||||
done:
|
||||
|
||||
if (!success)
|
||||
deassociate_complex_filters(mpctx);
|
||||
|
||||
cleanup_deassociated_complex_filters(mpctx);
|
||||
|
||||
if (mpctx->playback_initialized) {
|
||||
for (int n = 0; n < mpctx->num_tracks; n++)
|
||||
reselect_demux_stream(mpctx, mpctx->tracks[n]);
|
||||
}
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
|
||||
|
||||
return success ? 1 : -1;
|
||||
}
|
||||
|
||||
static void uninit_complex_filters(struct MPContext *mpctx)
|
||||
void update_lavfi_complex(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->lavfi)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *track = mpctx->tracks[n];
|
||||
|
||||
if (track->d_video && !track->vo_c) {
|
||||
video_uninit(track->d_video);
|
||||
track->d_video = NULL;
|
||||
}
|
||||
if (track->d_audio && !track->ao_c) {
|
||||
audio_uninit(track->d_audio);
|
||||
track->d_audio = NULL;
|
||||
}
|
||||
if (mpctx->playback_initialized) {
|
||||
if (reinit_complex_filters(mpctx, false) != 0)
|
||||
issue_refresh_seek(mpctx, MPSEEK_EXACT);
|
||||
}
|
||||
|
||||
if (mpctx->vo_chain && mpctx->vo_chain->filter_src)
|
||||
uninit_video_chain(mpctx);
|
||||
if (mpctx->ao_chain && mpctx->ao_chain->filter_src)
|
||||
uninit_audio_chain(mpctx);
|
||||
|
||||
lavfi_destroy(mpctx->lavfi);
|
||||
mpctx->lavfi = NULL;
|
||||
}
|
||||
|
||||
// Start playing the current playlist entry.
|
||||
@@ -1176,7 +1252,7 @@ reopen_file:
|
||||
if (process_preloaded_hooks(mpctx))
|
||||
goto terminate_playback;
|
||||
|
||||
if (!init_complex_filters(mpctx))
|
||||
if (reinit_complex_filters(mpctx, false) < 0)
|
||||
goto terminate_playback;
|
||||
|
||||
assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2
|
||||
@@ -1185,7 +1261,7 @@ reopen_file:
|
||||
struct track *sel = NULL;
|
||||
bool taken = (t == STREAM_VIDEO && mpctx->vo_chain) ||
|
||||
(t == STREAM_AUDIO && mpctx->ao_chain);
|
||||
if (!taken)
|
||||
if (!taken && opts->stream_auto_sel)
|
||||
sel = select_default_track(mpctx, i, t);
|
||||
mpctx->current_track[i][t] = sel;
|
||||
}
|
||||
@@ -1223,14 +1299,11 @@ reopen_file:
|
||||
|
||||
update_playback_speed(mpctx);
|
||||
|
||||
if (!init_complex_filter_decoders(mpctx))
|
||||
goto terminate_playback;
|
||||
|
||||
reinit_video_chain(mpctx);
|
||||
reinit_audio_chain(mpctx);
|
||||
reinit_sub_all(mpctx);
|
||||
|
||||
if (!mpctx->vo_chain && !mpctx->ao_chain) {
|
||||
if (!mpctx->vo_chain && !mpctx->ao_chain && opts->stream_auto_sel) {
|
||||
MP_FATAL(mpctx, "No video or audio streams selected.\n");
|
||||
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
|
||||
goto terminate_playback;
|
||||
@@ -1307,7 +1380,7 @@ terminate_playback:
|
||||
close_recorder(mpctx);
|
||||
|
||||
// time to uninit all, except global stuff:
|
||||
uninit_complex_filters(mpctx);
|
||||
reinit_complex_filters(mpctx, true);
|
||||
uninit_audio_chain(mpctx);
|
||||
uninit_video_chain(mpctx);
|
||||
uninit_sub_all(mpctx);
|
||||
|
||||
+46
-48
@@ -1000,7 +1000,7 @@ layouts["box"] = function ()
|
||||
lo.style = osc_styles.smallButtonsR
|
||||
|
||||
lo = add_layout("volume")
|
||||
lo.geometry =
|
||||
lo.geometry =
|
||||
{x = posX+pos_offsetX - (25 * 2) - osc_geo.p,
|
||||
y = bigbtnrowY, an = 4, w = 25, h = 25}
|
||||
lo.style = osc_styles.smallButtonsR
|
||||
@@ -1550,7 +1550,7 @@ function osc_init()
|
||||
return not (title == "") and title or "mpv"
|
||||
end
|
||||
|
||||
ne.eventresponder["mouse_btn0_up"] = function ()
|
||||
ne.eventresponder["mbtn_left_up"] = function ()
|
||||
local title = mp.get_property_osd("media-title")
|
||||
if (have_pl) then
|
||||
title = string.format("[%d/%d] %s", countone(pl_pos - 1),
|
||||
@@ -1559,7 +1559,7 @@ function osc_init()
|
||||
show_message(title)
|
||||
end
|
||||
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () show_message(mp.get_property_osd("filename")) end
|
||||
|
||||
-- playlist buttons
|
||||
@@ -1569,14 +1569,14 @@ function osc_init()
|
||||
|
||||
ne.content = "\238\132\144"
|
||||
ne.enabled = (pl_pos > 1) or (loop ~= "no")
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function ()
|
||||
mp.commandv("playlist-prev", "weak")
|
||||
show_message(get_playlist(), 3)
|
||||
end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
ne.eventresponder["shift+mbtn_left_up"] =
|
||||
function () show_message(get_playlist(), 3) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () show_message(get_playlist(), 3) end
|
||||
|
||||
--next
|
||||
@@ -1584,14 +1584,14 @@ function osc_init()
|
||||
|
||||
ne.content = "\238\132\129"
|
||||
ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function ()
|
||||
mp.commandv("playlist-next", "weak")
|
||||
show_message(get_playlist(), 3)
|
||||
end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
ne.eventresponder["shift+mbtn_left_up"] =
|
||||
function () show_message(get_playlist(), 3) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () show_message(get_playlist(), 3) end
|
||||
|
||||
|
||||
@@ -1607,7 +1607,7 @@ function osc_init()
|
||||
return ("\238\128\130")
|
||||
end
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () mp.commandv("cycle", "pause") end
|
||||
|
||||
--skipback
|
||||
@@ -1615,11 +1615,11 @@ function osc_init()
|
||||
|
||||
ne.softrepeat = true
|
||||
ne.content = "\238\128\132"
|
||||
ne.eventresponder["mouse_btn0_down"] =
|
||||
ne.eventresponder["mbtn_left_down"] =
|
||||
function () mp.commandv("seek", -5, "relative", "keyframes") end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
ne.eventresponder["shift+mbtn_left_down"] =
|
||||
function () mp.commandv("frame-back-step") end
|
||||
ne.eventresponder["mouse_btn2_down"] =
|
||||
ne.eventresponder["mbtn_right_down"] =
|
||||
function () mp.commandv("seek", -30, "relative", "keyframes") end
|
||||
|
||||
--skipfrwd
|
||||
@@ -1627,11 +1627,11 @@ function osc_init()
|
||||
|
||||
ne.softrepeat = true
|
||||
ne.content = "\238\128\133"
|
||||
ne.eventresponder["mouse_btn0_down"] =
|
||||
ne.eventresponder["mbtn_left_down"] =
|
||||
function () mp.commandv("seek", 10, "relative", "keyframes") end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
ne.eventresponder["shift+mbtn_left_down"] =
|
||||
function () mp.commandv("frame-step") end
|
||||
ne.eventresponder["mouse_btn2_down"] =
|
||||
ne.eventresponder["mbtn_right_down"] =
|
||||
function () mp.commandv("seek", 60, "relative", "keyframes") end
|
||||
|
||||
--ch_prev
|
||||
@@ -1639,14 +1639,14 @@ function osc_init()
|
||||
|
||||
ne.enabled = have_ch
|
||||
ne.content = "\238\132\132"
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function ()
|
||||
mp.commandv("add", "chapter", -1)
|
||||
show_message(get_chapterlist(), 3)
|
||||
end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
ne.eventresponder["shift+mbtn_left_up"] =
|
||||
function () show_message(get_chapterlist(), 3) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () show_message(get_chapterlist(), 3) end
|
||||
|
||||
--ch_next
|
||||
@@ -1654,14 +1654,14 @@ function osc_init()
|
||||
|
||||
ne.enabled = have_ch
|
||||
ne.content = "\238\132\133"
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function ()
|
||||
mp.commandv("add", "chapter", 1)
|
||||
show_message(get_chapterlist(), 3)
|
||||
end
|
||||
ne.eventresponder["shift+mouse_btn0_up"] =
|
||||
ne.eventresponder["shift+mbtn_left_up"] =
|
||||
function () show_message(get_chapterlist(), 3) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () show_message(get_chapterlist(), 3) end
|
||||
|
||||
--
|
||||
@@ -1679,11 +1679,11 @@ function osc_init()
|
||||
return ("\238\132\134" .. osc_styles.smallButtonsLlabel
|
||||
.. " " .. aid .. "/" .. #tracks_osc.audio)
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () set_track("audio", 1) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () set_track("audio", -1) end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
ne.eventresponder["shift+mbtn_left_down"] =
|
||||
function () show_message(get_tracklist("audio"), 2) end
|
||||
|
||||
--cy_sub
|
||||
@@ -1698,11 +1698,11 @@ function osc_init()
|
||||
return ("\238\132\135" .. osc_styles.smallButtonsLlabel
|
||||
.. " " .. sid .. "/" .. #tracks_osc.sub)
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () set_track("sub", 1) end
|
||||
ne.eventresponder["mouse_btn2_up"] =
|
||||
ne.eventresponder["mbtn_right_up"] =
|
||||
function () set_track("sub", -1) end
|
||||
ne.eventresponder["shift+mouse_btn0_down"] =
|
||||
ne.eventresponder["shift+mbtn_left_down"] =
|
||||
function () show_message(get_tracklist("sub"), 2) end
|
||||
|
||||
--tog_fs
|
||||
@@ -1714,7 +1714,7 @@ function osc_init()
|
||||
return ("\238\132\136")
|
||||
end
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () mp.commandv("cycle", "fullscreen") end
|
||||
|
||||
|
||||
@@ -1760,7 +1760,7 @@ function osc_init()
|
||||
end
|
||||
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_down"] = --exact seeks on single clicks
|
||||
ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks
|
||||
function (element) mp.commandv("seek", get_slider_value(element),
|
||||
"absolute-percent", "exact") end
|
||||
ne.eventresponder["reset"] =
|
||||
@@ -1777,7 +1777,7 @@ function osc_init()
|
||||
return (mp.get_property_osd("playback-time"))
|
||||
end
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] = function ()
|
||||
ne.eventresponder["mbtn_left_up"] = function ()
|
||||
state.tc_ms = not state.tc_ms
|
||||
request_init()
|
||||
end
|
||||
@@ -1801,7 +1801,7 @@ function osc_init()
|
||||
end
|
||||
end
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () state.rightTC_trem = not state.rightTC_trem end
|
||||
|
||||
-- cache
|
||||
@@ -1848,12 +1848,12 @@ function osc_init()
|
||||
return volicon[math.min(4,math.ceil(volume / (100/3)))]
|
||||
end
|
||||
end
|
||||
ne.eventresponder["mouse_btn0_up"] =
|
||||
ne.eventresponder["mbtn_left_up"] =
|
||||
function () mp.commandv("cycle", "mute") end
|
||||
|
||||
ne.eventresponder["mouse_wheel_up_press"] =
|
||||
ne.eventresponder["wheel_up_press"] =
|
||||
function () mp.commandv("osd-auto", "add", "volume", 5) end
|
||||
ne.eventresponder["mouse_wheel_down_press"] =
|
||||
ne.eventresponder["wheel_down_press"] =
|
||||
function () mp.commandv("osd-auto", "add", "volume", -5) end
|
||||
|
||||
|
||||
@@ -2296,19 +2296,17 @@ do_enable_keybindings()
|
||||
|
||||
--mouse input bindings
|
||||
mp.set_key_bindings({
|
||||
{"mouse_btn0", function(e) process_event("mouse_btn0", "up") end,
|
||||
function(e) process_event("mouse_btn0", "down") end},
|
||||
{"shift+mouse_btn0", function(e) process_event("shift+mouse_btn0", "up") end,
|
||||
function(e) process_event("shift+mouse_btn0", "down") end},
|
||||
{"mouse_btn2", function(e) process_event("mouse_btn2", "up") end,
|
||||
function(e) process_event("mouse_btn2", "down") end},
|
||||
{"mouse_btn3", function(e) process_event("mouse_wheel_up", "press") end},
|
||||
{"mouse_btn4", function(e) process_event("mouse_wheel_down", "press") end},
|
||||
{"axis_up", function(e) process_event("mouse_wheel_up", "press") end},
|
||||
{"axis_down", function(e) process_event("mouse_wheel_down", "press") end},
|
||||
{"mouse_btn0_dbl", "ignore"},
|
||||
{"shift+mouse_btn0_dbl", "ignore"},
|
||||
{"mouse_btn2_dbl", "ignore"},
|
||||
{"mbtn_left", function(e) process_event("mbtn_left", "up") end,
|
||||
function(e) process_event("mbtn_left", "down") end},
|
||||
{"shift+mbtn_left", function(e) process_event("shift+mbtn_left", "up") end,
|
||||
function(e) process_event("shift+mbtn_left", "down") end},
|
||||
{"mbtn_right", function(e) process_event("mbtn_right", "up") end,
|
||||
function(e) process_event("mbtn_right", "down") end},
|
||||
{"wheel_up", function(e) process_event("wheel_up", "press") end},
|
||||
{"wheel_down", function(e) process_event("wheel_down", "press") end},
|
||||
{"mbtn_left_dbl", "ignore"},
|
||||
{"shift+mbtn_left_dbl", "ignore"},
|
||||
{"mbtn_right_dbl", "ignore"},
|
||||
}, "input", "force")
|
||||
mp.enable_key_bindings("input")
|
||||
|
||||
|
||||
@@ -122,7 +122,37 @@ local function is_blacklisted(url)
|
||||
return false
|
||||
end
|
||||
|
||||
local function edl_track_joined(fragments, protocol, is_live)
|
||||
local function make_absolute_url(base_url, url)
|
||||
if url:find("https?://") == 1 then return url end
|
||||
|
||||
local proto, domain, rest =
|
||||
base_url:match("(https?://)([^/]+/)(.*)/?")
|
||||
local segs = {}
|
||||
rest:gsub("([^/]+)", function(c) table.insert(segs, c) end)
|
||||
url:gsub("([^/]+)", function(c) table.insert(segs, c) end)
|
||||
local resolved_url = {}
|
||||
for i, v in ipairs(segs) do
|
||||
if v == ".." then
|
||||
table.remove(resolved_url)
|
||||
elseif v ~= "." then
|
||||
table.insert(resolved_url, v)
|
||||
end
|
||||
end
|
||||
return proto .. domain ..
|
||||
table.concat(resolved_url, "/")
|
||||
end
|
||||
|
||||
local function join_url(base_url, fragment)
|
||||
local res = ""
|
||||
if base_url and fragment.path then
|
||||
res = make_absolute_url(base_url, fragment.path)
|
||||
elseif fragment.url then
|
||||
res = fragment.url
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function edl_track_joined(fragments, protocol, is_live, base)
|
||||
if not (type(fragments) == "table") or not fragments[1] then
|
||||
msg.debug("No fragments to join into EDL")
|
||||
return nil
|
||||
@@ -136,7 +166,7 @@ local function edl_track_joined(fragments, protocol, is_live)
|
||||
not fragments[1].duration and not is_live then
|
||||
-- assume MP4 DASH initialization segment
|
||||
table.insert(parts,
|
||||
"!mp4_dash,init=" .. edl_escape(fragments[1].url))
|
||||
"!mp4_dash,init=" .. edl_escape(join_url(base, fragments[1])))
|
||||
offset = 2
|
||||
|
||||
-- Check remaining fragments for duration;
|
||||
@@ -152,7 +182,7 @@ local function edl_track_joined(fragments, protocol, is_live)
|
||||
|
||||
for i = offset, #fragments do
|
||||
local fragment = fragments[i]
|
||||
table.insert(parts, edl_escape(fragment.url))
|
||||
table.insert(parts, edl_escape(join_url(base, fragment)))
|
||||
if fragment.duration then
|
||||
parts[#parts] =
|
||||
parts[#parts] .. ",length="..fragment.duration
|
||||
@@ -169,7 +199,8 @@ local function add_single_video(json)
|
||||
for _, track in pairs(json.requested_formats) do
|
||||
local edl_track = nil
|
||||
edl_track = edl_track_joined(track.fragments,
|
||||
track.protocol, json.is_live)
|
||||
track.protocol, json.is_live,
|
||||
track.fragment_base_url)
|
||||
if track.acodec and track.acodec ~= "none" then
|
||||
-- audio track
|
||||
mp.commandv("audio-add",
|
||||
@@ -184,7 +215,7 @@ local function add_single_video(json)
|
||||
elseif not (json.url == nil) then
|
||||
local edl_track = nil
|
||||
edl_track = edl_track_joined(json.fragments, json.protocol,
|
||||
json.is_live)
|
||||
json.is_live, json.fragment_base_url)
|
||||
|
||||
-- normal video or single track
|
||||
streamurl = edl_track or json.url
|
||||
|
||||
@@ -111,6 +111,25 @@ double get_track_seek_offset(struct MPContext *mpctx, struct track *track)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void issue_refresh_seek(struct MPContext *mpctx, enum seek_precision min_prec)
|
||||
{
|
||||
// let queued seeks execute at a slightly later point
|
||||
if (mpctx->seek.type) {
|
||||
mp_wakeup_core(mpctx);
|
||||
return;
|
||||
}
|
||||
// repeat currently ongoing seeks
|
||||
if (mpctx->current_seek.type) {
|
||||
mpctx->seek = mpctx->current_seek;
|
||||
mp_wakeup_core(mpctx);
|
||||
return;
|
||||
}
|
||||
// maybe happens when changing filters while file is loaded - ignore for now
|
||||
if (mpctx->playback_pts == MP_NOPTS_VALUE)
|
||||
return;
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->playback_pts, min_prec, 0);
|
||||
}
|
||||
|
||||
float mp_get_cache_percent(struct MPContext *mpctx)
|
||||
{
|
||||
struct stream_cache_info info = {0};
|
||||
|
||||
+5
-1
@@ -232,6 +232,7 @@ void reset_playback_state(struct MPContext *mpctx)
|
||||
mpctx->hrseek_framedrop = false;
|
||||
mpctx->hrseek_lastframe = false;
|
||||
mpctx->hrseek_backstep = false;
|
||||
mpctx->current_seek = (struct seek_params){0};
|
||||
mpctx->playback_pts = MP_NOPTS_VALUE;
|
||||
mpctx->last_seek_pts = MP_NOPTS_VALUE;
|
||||
mpctx->cache_wait_time = 0;
|
||||
@@ -376,6 +377,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
!hr_seek && !(demux_flags & SEEK_FORWARD);
|
||||
|
||||
mpctx->ab_loop_clip = mpctx->last_seek_pts < opts->ab_loop[1];
|
||||
|
||||
mpctx->current_seek = seek;
|
||||
}
|
||||
|
||||
// This combines consecutive seek requests.
|
||||
@@ -989,6 +992,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
|
||||
if (!mpctx->restart_complete) {
|
||||
mpctx->hrseek_active = false;
|
||||
mpctx->restart_complete = true;
|
||||
mpctx->current_seek = (struct seek_params){0};
|
||||
mpctx->audio_allow_second_chance_seek = false;
|
||||
handle_playback_time(mpctx);
|
||||
mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL);
|
||||
@@ -1049,7 +1053,7 @@ static void handle_complex_filter_decoders(struct MPContext *mpctx)
|
||||
continue;
|
||||
if (track->d_audio) {
|
||||
audio_work(track->d_audio);
|
||||
struct mp_audio *fr;
|
||||
struct mp_aframe *fr;
|
||||
int res = audio_get_frame(track->d_audio, &fr);
|
||||
if (res == DATA_OK) {
|
||||
lavfi_send_frame_a(track->sink, fr);
|
||||
|
||||
+28
-165
@@ -13,8 +13,6 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Parts under HAVE_GPL are licensed under GNU General Public License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -70,69 +68,6 @@ static const char av_desync_help_text[] =
|
||||
"position will not match to the video (see A-V status field).\n"
|
||||
"\n";
|
||||
|
||||
#if HAVE_GPL
|
||||
int video_set_colors(struct vo_chain *vo_c, const char *item, int value)
|
||||
{
|
||||
vf_equalizer_t data;
|
||||
|
||||
data.item = item;
|
||||
data.value = value;
|
||||
|
||||
MP_VERBOSE(vo_c, "set video colors %s=%d \n", item, value);
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_EQUALIZER, &data) == CONTROL_TRUE)
|
||||
return 1;
|
||||
MP_VERBOSE(vo_c, "Video attribute '%s' is not supported by selected vo.\n",
|
||||
item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int video_get_colors(struct vo_chain *vo_c, const char *item, int *value)
|
||||
{
|
||||
vf_equalizer_t data;
|
||||
|
||||
data.item = item;
|
||||
|
||||
MP_VERBOSE(vo_c, "get video colors %s \n", item);
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_EQUALIZER, &data) == CONTROL_TRUE) {
|
||||
*value = data.value;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Send a VCTRL, or if it doesn't work, translate it to a VOCTRL and try the VO.
|
||||
int video_vf_vo_control(struct vo_chain *vo_c, int vf_cmd, void *data)
|
||||
{
|
||||
if (vo_c->vf->initialized > 0) {
|
||||
int r = vf_control_any(vo_c->vf, vf_cmd, data);
|
||||
if (r != CONTROL_UNKNOWN)
|
||||
return r;
|
||||
}
|
||||
|
||||
switch (vf_cmd) {
|
||||
case VFCTRL_SET_EQUALIZER: {
|
||||
vf_equalizer_t *eq = data;
|
||||
if (!vo_c->vo->config_ok)
|
||||
return CONTROL_FALSE; // vo not configured?
|
||||
struct voctrl_set_equalizer_args param = {
|
||||
eq->item, eq->value
|
||||
};
|
||||
return vo_control(vo_c->vo, VOCTRL_SET_EQUALIZER, ¶m) == VO_TRUE;
|
||||
}
|
||||
case VFCTRL_GET_EQUALIZER: {
|
||||
vf_equalizer_t *eq = data;
|
||||
if (!vo_c->vo->config_ok)
|
||||
return CONTROL_FALSE; // vo not configured?
|
||||
struct voctrl_get_equalizer_args param = {
|
||||
eq->item, &eq->value
|
||||
};
|
||||
return vo_control(vo_c->vo, VOCTRL_GET_EQUALIZER, ¶m) == VO_TRUE;
|
||||
}
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static void set_allowed_vo_formats(struct vo_chain *vo_c)
|
||||
{
|
||||
vo_query_formats(vo_c->vo, vo_c->vf->allowed_output_formats);
|
||||
@@ -162,16 +97,6 @@ static bool check_output_format(struct vo_chain *vo_c, int imgfmt)
|
||||
|
||||
static int probe_deint_filters(struct vo_chain *vo_c)
|
||||
{
|
||||
#if HAVE_GPL
|
||||
// Usually, we prefer inserting/removing deint filters. But If there's VO
|
||||
// support, or the user inserted a filter that supports swichting deint and
|
||||
// that has no VF_DEINTERLACE_LABEL, or if the filter was auto-inserted
|
||||
// for other reasons and supports switching deint (like vf_d3d11vpp), then
|
||||
// use the runtime switching method.
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){1}) == CONTROL_OK)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (check_output_format(vo_c, IMGFMT_VDPAU)) {
|
||||
char *args[5] = {"deint", "yes"};
|
||||
int pref = 0;
|
||||
@@ -216,12 +141,6 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
|
||||
return;
|
||||
}
|
||||
|
||||
#if HAVE_GPL
|
||||
// Make sure to reset this even if runtime deint switching is used.
|
||||
if (mpctx->opts->deinterlace >= 0)
|
||||
video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
|
||||
#endif
|
||||
|
||||
if (params.rotate) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90) || params.rotate % 90) {
|
||||
// Try to insert a rotation filter.
|
||||
@@ -242,12 +161,15 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->opts->deinterlace == 1)
|
||||
if (mpctx->opts->deinterlace)
|
||||
probe_deint_filters(vo_c);
|
||||
}
|
||||
|
||||
static void recreate_auto_filters(struct MPContext *mpctx)
|
||||
void recreate_auto_filters(struct MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->vo_chain)
|
||||
return;
|
||||
|
||||
filter_reconfig(mpctx, mpctx->vo_chain);
|
||||
|
||||
mp_force_video_refresh(mpctx);
|
||||
@@ -255,34 +177,6 @@ static void recreate_auto_filters(struct MPContext *mpctx)
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
|
||||
int get_deinterlacing(struct MPContext *mpctx)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
int enabled = 0;
|
||||
#if HAVE_GPL
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
#endif
|
||||
if (enabled < 0) {
|
||||
// vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL))
|
||||
enabled = 1;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void set_deinterlacing(struct MPContext *mpctx, int opt_val)
|
||||
{
|
||||
if ((opt_val < 0 && mpctx->opts->deinterlace == opt_val) ||
|
||||
(opt_val == (get_deinterlacing(mpctx) > 0)))
|
||||
return;
|
||||
|
||||
mpctx->opts->deinterlace = opt_val;
|
||||
recreate_auto_filters(mpctx);
|
||||
if (opt_val >= 0)
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static void recreate_video_filters(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
@@ -425,6 +319,7 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track)
|
||||
d_video->header = track->stream;
|
||||
d_video->codec = track->stream->codec;
|
||||
d_video->fps = d_video->header->codec->fps;
|
||||
d_video->vo = mpctx->vo_chain->vo;
|
||||
|
||||
// Note: at least mpv_opengl_cb_uninit_gl() relies on being able to get
|
||||
// rid of all references to the VO by destroying the VO chain. Thus,
|
||||
@@ -455,23 +350,19 @@ err_out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reinit_video_chain(struct MPContext *mpctx)
|
||||
void reinit_video_chain(struct MPContext *mpctx)
|
||||
{
|
||||
return reinit_video_chain_src(mpctx, NULL);
|
||||
struct track *track = mpctx->current_track[0][STREAM_VIDEO];
|
||||
if (!track || !track->stream) {
|
||||
error_on_track(mpctx, track);
|
||||
return;
|
||||
}
|
||||
reinit_video_chain_src(mpctx, track);
|
||||
}
|
||||
|
||||
int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
// (track=NULL creates a blank chain, used for lavfi-complex)
|
||||
void reinit_video_chain_src(struct MPContext *mpctx, struct track *track)
|
||||
{
|
||||
struct track *track = NULL;
|
||||
struct sh_stream *sh = NULL;
|
||||
if (!src) {
|
||||
track = mpctx->current_track[0][STREAM_VIDEO];
|
||||
if (!track)
|
||||
return 0;
|
||||
sh = track->stream;
|
||||
if (!sh)
|
||||
goto no_video;
|
||||
}
|
||||
assert(!mpctx->vo_chain);
|
||||
|
||||
if (!mpctx->video_out) {
|
||||
@@ -503,11 +394,7 @@ int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
|
||||
vo_c->hwdec_devs = vo_c->vo->hwdec_devs;
|
||||
|
||||
if (mpctx->lavfi)
|
||||
lavfi_set_hwdec_devs(mpctx->lavfi, vo_c->hwdec_devs);
|
||||
|
||||
vo_c->filter_src = src;
|
||||
if (!vo_c->filter_src) {
|
||||
if (track) {
|
||||
vo_c->track = track;
|
||||
track->vo_c = vo_c;
|
||||
if (!init_video_decoder(mpctx, track))
|
||||
@@ -515,7 +402,7 @@ int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
|
||||
vo_c->video_src = track->d_video;
|
||||
vo_c->container_fps = vo_c->video_src->fps;
|
||||
vo_c->is_coverart = !!sh->attached_picture;
|
||||
vo_c->is_coverart = !!track->stream->attached_picture;
|
||||
|
||||
track->vo_c = vo_c;
|
||||
vo_c->track = track;
|
||||
@@ -539,14 +426,12 @@ int reinit_video_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
|
||||
reset_video_state(mpctx);
|
||||
reset_subtitle_state(mpctx);
|
||||
|
||||
return 1;
|
||||
return;
|
||||
|
||||
err_out:
|
||||
no_video:
|
||||
uninit_video_chain(mpctx);
|
||||
error_on_track(mpctx, track);
|
||||
handle_force_window(mpctx, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to refresh the video by doing a precise seek to the currently displayed
|
||||
@@ -560,13 +445,10 @@ void mp_force_video_refresh(struct MPContext *mpctx)
|
||||
return;
|
||||
|
||||
// If not paused, the next frame should come soon enough.
|
||||
if ((opts->pause || mpctx->time_frame >= 0.5) &&
|
||||
(mpctx->video_status >= STATUS_PLAYING ||
|
||||
mpctx->video_status <= STATUS_DRAINING) &&
|
||||
mpctx->last_vo_pts != MP_NOPTS_VALUE)
|
||||
if (opts->pause || mpctx->time_frame >= 0.5 ||
|
||||
mpctx->video_status == STATUS_EOF)
|
||||
{
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts,
|
||||
MPSEEK_VERY_EXACT, 0);
|
||||
issue_refresh_seek(mpctx, MPSEEK_VERY_EXACT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +498,7 @@ static int decode_image(struct MPContext *mpctx)
|
||||
switch (res) {
|
||||
case DATA_WAIT: return VD_WAIT;
|
||||
case DATA_OK:
|
||||
case DATA_STARVE:
|
||||
case DATA_AGAIN: return VD_PROGRESS;
|
||||
case DATA_EOF: return VD_EOF;
|
||||
default: abort();
|
||||
@@ -991,28 +874,6 @@ static void update_av_diff(struct MPContext *mpctx, double offset)
|
||||
}
|
||||
}
|
||||
|
||||
static void init_vo(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
|
||||
#if HAVE_GPL
|
||||
if (opts->gamma_gamma != 0)
|
||||
video_set_colors(vo_c, "gamma", opts->gamma_gamma);
|
||||
if (opts->gamma_brightness != 0)
|
||||
video_set_colors(vo_c, "brightness", opts->gamma_brightness);
|
||||
if (opts->gamma_contrast != 0)
|
||||
video_set_colors(vo_c, "contrast", opts->gamma_contrast);
|
||||
if (opts->gamma_saturation != 0)
|
||||
video_set_colors(vo_c, "saturation", opts->gamma_saturation);
|
||||
if (opts->gamma_hue != 0)
|
||||
video_set_colors(vo_c, "hue", opts->gamma_hue);
|
||||
video_set_colors(vo_c, "output-levels", opts->video_output_levels);
|
||||
#endif
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
|
||||
double calc_average_frame_duration(struct MPContext *mpctx)
|
||||
{
|
||||
double total = 0;
|
||||
@@ -1064,9 +925,11 @@ static double find_best_speed(struct MPContext *mpctx, double vsync)
|
||||
static bool using_spdif_passthrough(struct MPContext *mpctx)
|
||||
{
|
||||
if (mpctx->ao_chain && mpctx->ao_chain->ao) {
|
||||
struct mp_audio out_format = {0};
|
||||
ao_get_format(mpctx->ao_chain->ao, &out_format);
|
||||
return !af_fmt_is_pcm(out_format.format);
|
||||
int samplerate;
|
||||
int format;
|
||||
struct mp_chmap channels;
|
||||
ao_get_format(mpctx->ao_chain->ao, &samplerate, &format, &channels);
|
||||
return !af_fmt_is_pcm(format);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1454,7 +1317,7 @@ void write_video(struct MPContext *mpctx)
|
||||
mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED;
|
||||
goto error;
|
||||
}
|
||||
init_vo(mpctx);
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
|
||||
mpctx->time_frame -= get_relative_time(mpctx);
|
||||
|
||||
@@ -563,6 +563,7 @@ bool stream_seek(stream_t *s, int64_t pos)
|
||||
int64_t x = pos - (s->pos - (int)s->buf_len);
|
||||
if (x >= 0) {
|
||||
s->buf_pos = x;
|
||||
assert(s->buf_pos <= s->buf_len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,11 @@ static int fill_buffer(stream_t *s, char *buffer, int max_len)
|
||||
AVIOContext *avio = s->priv;
|
||||
if (!avio)
|
||||
return -1;
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 81, 100)
|
||||
int r = avio_read_partial(avio, buffer, max_len);
|
||||
#else
|
||||
int r = avio_read(avio, buffer, max_len);
|
||||
#endif
|
||||
return (r <= 0) ? -1 : r;
|
||||
}
|
||||
|
||||
|
||||
+1
-27
@@ -55,32 +55,6 @@ static int control(stream_t *s, int cmd, void *arg)
|
||||
return STREAM_UNSUPPORTED;
|
||||
}
|
||||
|
||||
static int h_to_i(unsigned char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool bstr_to_hex_inplace(bstr *h)
|
||||
{
|
||||
if (h->len % 2)
|
||||
return false;
|
||||
for (int n = 0; n < h->len / 2; n++) {
|
||||
int hi = h_to_i(h->start[n * 2 + 0]);
|
||||
int lo = h_to_i(h->start[n * 2 + 1]);
|
||||
if (hi < 0 || lo < 0)
|
||||
return false;
|
||||
h->start[n] = (hi << 4) | lo;
|
||||
}
|
||||
h->len /= 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int open_f(stream_t *stream)
|
||||
{
|
||||
stream->fill_buffer = fill_buffer;
|
||||
@@ -100,7 +74,7 @@ static int open_f(stream_t *stream)
|
||||
bstr_eatstart0(&data, "memory://");
|
||||
stream_control(stream, STREAM_CTRL_SET_CONTENTS, &data);
|
||||
|
||||
if (use_hex && !bstr_to_hex_inplace(&p->data)) {
|
||||
if (use_hex && !bstr_decode_hex(stream, p->data, &p->data)) {
|
||||
MP_FATAL(stream, "Invalid data.\n");
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ const struct m_sub_options sub_style_conf = {
|
||||
.change_flags = UPDATE_OSD,
|
||||
};
|
||||
|
||||
static bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b)
|
||||
bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b)
|
||||
{
|
||||
return a.w == b.w && a.h == b.h && a.ml == b.ml && a.mt == b.mt
|
||||
&& a.mr == b.mr && a.mb == b.mb
|
||||
|
||||
@@ -82,6 +82,8 @@ struct mp_osd_res {
|
||||
double display_par;
|
||||
};
|
||||
|
||||
bool osd_res_equals(struct mp_osd_res a, struct mp_osd_res b);
|
||||
|
||||
// 0 <= sub_bitmaps.render_index < MAX_OSD_PARTS
|
||||
#define MAX_OSD_PARTS 5
|
||||
|
||||
|
||||
+33
-34
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "mp_image.h"
|
||||
#include "csputils.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
const struct m_opt_choice_alternatives mp_csp_names[] = {
|
||||
@@ -96,15 +97,6 @@ const struct m_opt_choice_alternatives mp_csp_light_names[] = {
|
||||
{0}
|
||||
};
|
||||
|
||||
const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = {
|
||||
"brightness",
|
||||
"contrast",
|
||||
"hue",
|
||||
"saturation",
|
||||
"gamma",
|
||||
"output-levels",
|
||||
};
|
||||
|
||||
const struct m_opt_choice_alternatives mp_chroma_names[] = {
|
||||
{"unknown", MP_CHROMA_AUTO},
|
||||
{"mpeg2/4/h264",MP_CHROMA_LEFT},
|
||||
@@ -817,9 +809,25 @@ bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2)
|
||||
c1.sig_peak == c2.sig_peak;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct mp_csp_equalizer_opts
|
||||
|
||||
const struct m_sub_options mp_csp_equalizer_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_INTRANGE("brightness", values[MP_CSP_EQ_BRIGHTNESS], 0, -100, 100),
|
||||
OPT_INTRANGE("saturation", values[MP_CSP_EQ_SATURATION], 0, -100, 100),
|
||||
OPT_INTRANGE("contrast", values[MP_CSP_EQ_CONTRAST], 0, -100, 100),
|
||||
OPT_INTRANGE("hue", values[MP_CSP_EQ_HUE], 0, -100, 100),
|
||||
OPT_INTRANGE("gamma", values[MP_CSP_EQ_GAMMA], 0, -100, 100),
|
||||
OPT_CHOICE_C("video-output-levels", values[MP_CSP_EQ_OUTPUT_LEVELS], 0,
|
||||
mp_csp_levels_names),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct mp_csp_equalizer_opts),
|
||||
};
|
||||
|
||||
// Copy settings from eq into params.
|
||||
void mp_csp_copy_equalizer_values(struct mp_csp_params *params,
|
||||
const struct mp_csp_equalizer *eq)
|
||||
const struct mp_csp_equalizer_opts *eq)
|
||||
{
|
||||
params->brightness = eq->values[MP_CSP_EQ_BRIGHTNESS] / 100.0;
|
||||
params->contrast = (eq->values[MP_CSP_EQ_CONTRAST] + 100) / 100.0;
|
||||
@@ -829,37 +837,28 @@ void mp_csp_copy_equalizer_values(struct mp_csp_params *params,
|
||||
params->levels_out = eq->values[MP_CSP_EQ_OUTPUT_LEVELS];
|
||||
}
|
||||
|
||||
static int find_eq(int capabilities, const char *name)
|
||||
struct mp_csp_equalizer_state *mp_csp_equalizer_create(void *ta_parent,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
for (int i = 0; i < MP_CSP_EQ_COUNT; i++) {
|
||||
if (strcmp(name, mp_csp_equalizer_names[i]) == 0)
|
||||
return ((1 << i) & capabilities) ? i : -1;
|
||||
}
|
||||
return -1;
|
||||
struct m_config_cache *c = m_config_cache_alloc(ta_parent, global,
|
||||
&mp_csp_equalizer_conf);
|
||||
// The terrible, terrible truth.
|
||||
return (struct mp_csp_equalizer_state *)c;
|
||||
}
|
||||
|
||||
int mp_csp_equalizer_get(struct mp_csp_equalizer *eq, const char *property,
|
||||
int *out_value)
|
||||
bool mp_csp_equalizer_state_changed(struct mp_csp_equalizer_state *state)
|
||||
{
|
||||
int index = find_eq(eq->capabilities, property);
|
||||
if (index < 0)
|
||||
return -1;
|
||||
|
||||
*out_value = eq->values[index];
|
||||
|
||||
return 0;
|
||||
struct m_config_cache *c = (struct m_config_cache *)state;
|
||||
return m_config_cache_update(c);
|
||||
}
|
||||
|
||||
int mp_csp_equalizer_set(struct mp_csp_equalizer *eq, const char *property,
|
||||
int value)
|
||||
void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state,
|
||||
struct mp_csp_params *params)
|
||||
{
|
||||
int index = find_eq(eq->capabilities, property);
|
||||
if (index < 0)
|
||||
return 0;
|
||||
|
||||
eq->values[index] = value;
|
||||
|
||||
return 1;
|
||||
struct m_config_cache *c = (struct m_config_cache *)state;
|
||||
m_config_cache_update(c);
|
||||
struct mp_csp_equalizer_opts *opts = c->opts;
|
||||
mp_csp_copy_equalizer_values(params, opts);
|
||||
}
|
||||
|
||||
void mp_invert_cmat(struct mp_cmat *out, struct mp_cmat *in)
|
||||
|
||||
+13
-23
@@ -185,6 +185,8 @@ enum mp_chroma_location {
|
||||
|
||||
extern const struct m_opt_choice_alternatives mp_chroma_names[];
|
||||
|
||||
extern const struct m_sub_options mp_csp_equalizer_conf;
|
||||
|
||||
enum mp_csp_equalizer_param {
|
||||
MP_CSP_EQ_BRIGHTNESS,
|
||||
MP_CSP_EQ_CONTRAST,
|
||||
@@ -195,28 +197,23 @@ enum mp_csp_equalizer_param {
|
||||
MP_CSP_EQ_COUNT,
|
||||
};
|
||||
|
||||
#define MP_CSP_EQ_CAPS_COLORMATRIX \
|
||||
( (1 << MP_CSP_EQ_BRIGHTNESS) \
|
||||
| (1 << MP_CSP_EQ_CONTRAST) \
|
||||
| (1 << MP_CSP_EQ_HUE) \
|
||||
| (1 << MP_CSP_EQ_SATURATION) \
|
||||
| (1 << MP_CSP_EQ_OUTPUT_LEVELS) )
|
||||
|
||||
#define MP_CSP_EQ_CAPS_GAMMA (1 << MP_CSP_EQ_GAMMA)
|
||||
#define MP_CSP_EQ_CAPS_BRIGHTNESS (1 << MP_CSP_EQ_BRIGHTNESS)
|
||||
|
||||
extern const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT];
|
||||
|
||||
// Default initialization with 0 is enough, except for the capabilities field
|
||||
struct mp_csp_equalizer {
|
||||
// Bit field of capabilities. For example (1 << MP_CSP_EQ_HUE) means hue
|
||||
// support is available.
|
||||
int capabilities;
|
||||
struct mp_csp_equalizer_opts {
|
||||
// Value for each property is in the range [-100, 100].
|
||||
// 0 is default, meaning neutral or no change.
|
||||
int values[MP_CSP_EQ_COUNT];
|
||||
};
|
||||
|
||||
void mp_csp_copy_equalizer_values(struct mp_csp_params *params,
|
||||
const struct mp_csp_equalizer_opts *eq);
|
||||
|
||||
struct mpv_global;
|
||||
struct mp_csp_equalizer_state *mp_csp_equalizer_create(void *ta_parent,
|
||||
struct mpv_global *global);
|
||||
bool mp_csp_equalizer_state_changed(struct mp_csp_equalizer_state *state);
|
||||
void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state,
|
||||
struct mp_csp_params *params);
|
||||
|
||||
struct mp_csp_col_xy {
|
||||
float x, y;
|
||||
};
|
||||
@@ -233,13 +230,6 @@ struct mp_csp_primaries {
|
||||
struct mp_csp_col_xy red, green, blue, white;
|
||||
};
|
||||
|
||||
void mp_csp_copy_equalizer_values(struct mp_csp_params *params,
|
||||
const struct mp_csp_equalizer *eq);
|
||||
int mp_csp_equalizer_set(struct mp_csp_equalizer *eq, const char *property,
|
||||
int value);
|
||||
int mp_csp_equalizer_get(struct mp_csp_equalizer *eq, const char *property,
|
||||
int *out_value);
|
||||
|
||||
enum mp_csp avcol_spc_to_mp_csp(int avcolorspace);
|
||||
enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange);
|
||||
enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri);
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Almost LGPL.
|
||||
*
|
||||
* Parts under HAVE_GPL are licensed under GNU General Public License forever.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -205,35 +203,10 @@ static void fix_image_params(struct dec_video *d_video,
|
||||
|
||||
// While mp_image_params normally always have to have d_w/d_h set, the
|
||||
// decoder signals unknown bitstream aspect ratio with both set to 0.
|
||||
float dec_aspect = p.p_w > 0 && p.p_h > 0 ? p.p_w / (float)p.p_h : 0;
|
||||
|
||||
#if HAVE_GPL
|
||||
if (d_video->initial_decoder_aspect == 0)
|
||||
d_video->initial_decoder_aspect = dec_aspect;
|
||||
#endif
|
||||
|
||||
bool use_container = true;
|
||||
switch (opts->aspect_method) {
|
||||
case 0:
|
||||
#if HAVE_GPL
|
||||
// We normally prefer the container aspect, unless the decoder aspect
|
||||
// changes at least once.
|
||||
if (dec_aspect > 0 && d_video->initial_decoder_aspect != dec_aspect) {
|
||||
MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
|
||||
// Even if the aspect switches back, don't use container aspect again.
|
||||
d_video->initial_decoder_aspect = -1;
|
||||
use_container = false;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
/* fall through, behave as "bitstream" */
|
||||
#endif
|
||||
case 1:
|
||||
if (dec_aspect) {
|
||||
MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
|
||||
use_container = false;
|
||||
}
|
||||
break;
|
||||
if (opts->aspect_method == 1 && p.p_w > 0 && p.p_h > 0) {
|
||||
MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
|
||||
use_container = false;
|
||||
}
|
||||
|
||||
if (use_container && c->par_w > 0 && c->par_h) {
|
||||
@@ -271,6 +244,10 @@ static void fix_image_params(struct dec_video *d_video,
|
||||
p.color.sig_peak = 0.0;
|
||||
}
|
||||
|
||||
p.spherical = c->spherical;
|
||||
if (p.spherical.type == MP_SPHERICAL_AUTO)
|
||||
p.spherical.type = MP_SPHERICAL_NONE;
|
||||
|
||||
// Guess missing colorspace fields from metadata. This guarantees all
|
||||
// fields are at least set to legal values afterwards.
|
||||
mp_image_params_guess_csp(&p);
|
||||
@@ -327,15 +304,6 @@ static bool receive_frame(struct dec_video *d_video, struct mp_image **out_image
|
||||
if (!mpi)
|
||||
return progress;
|
||||
|
||||
#if HAVE_GPL
|
||||
if (opts->field_dominance == 0) {
|
||||
mpi->fields |= MP_IMGFIELD_TOP_FIRST | MP_IMGFIELD_INTERLACED;
|
||||
} else if (opts->field_dominance == 1) {
|
||||
mpi->fields &= ~MP_IMGFIELD_TOP_FIRST;
|
||||
mpi->fields |= MP_IMGFIELD_INTERLACED;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Note: the PTS is reordered, but the DTS is not. Both should be monotonic.
|
||||
double pts = mpi->pts;
|
||||
double dts = mpi->dts;
|
||||
|
||||
@@ -37,6 +37,7 @@ struct dec_video {
|
||||
struct mp_hwdec_devices *hwdec_devs; // video output hwdec handles
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
struct vo *vo; // required for direct rendering into video memory
|
||||
|
||||
char *decoder_desc;
|
||||
|
||||
@@ -72,7 +73,6 @@ struct dec_video {
|
||||
double decoded_pts;
|
||||
|
||||
struct mp_image_params dec_format, last_format, fixed_format;
|
||||
float initial_decoder_aspect;
|
||||
|
||||
double start_pts;
|
||||
double start, end;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define MPV_LAVC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
@@ -60,6 +61,7 @@ typedef struct lavc_ctx {
|
||||
|
||||
// Set by generic hwaccels.
|
||||
struct mp_hwdec_ctx *hwdec_dev;
|
||||
bool owns_hwdec_dev;
|
||||
|
||||
int hwdec_fmt;
|
||||
int hwdec_w;
|
||||
@@ -72,6 +74,12 @@ typedef struct lavc_ctx {
|
||||
struct mp_image_pool *hwdec_swpool;
|
||||
|
||||
AVBufferRef *cached_hw_frames_ctx;
|
||||
|
||||
// --- The following fields are protected by dr_lock.
|
||||
pthread_mutex_t dr_lock;
|
||||
bool dr_failed;
|
||||
struct mp_image_pool *dr_pool;
|
||||
int dr_imgfmt, dr_w, dr_h, dr_stride_align;
|
||||
} vd_ffmpeg_ctx;
|
||||
|
||||
struct vd_lavc_hwdec {
|
||||
|
||||
Loaded 100 of 186 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user