Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e00bcc14c | ||
|
|
7635438592 | ||
|
|
e518bf2c72 | ||
|
|
d47b708f00 | ||
|
|
2fb2f9b93a | ||
|
|
995c47da9a | ||
|
|
da9590d368 | ||
|
|
34a33ebd4d | ||
|
|
e58ecc3630 | ||
|
|
6d83455a95 | ||
|
|
35be389031 | ||
|
|
885e991312 | ||
|
|
26b6873ffd | ||
|
|
71a8aa2e9a | ||
|
|
16b48a429d | ||
|
|
f2110f582d | ||
|
|
8660b4c9f0 | ||
|
|
ab6fac43b4 | ||
|
|
5d2f1da7c5 | ||
|
|
771a8bf5c6 | ||
|
|
08cd50b84b | ||
|
|
0b1ef81498 | ||
|
|
fc76966d9e | ||
|
|
0fe62bc1d2 | ||
|
|
d72bcc8041 | ||
|
|
329a7147d0 | ||
|
|
4f4eea6b44 | ||
|
|
33e72ed451 | ||
|
|
6a3da439cd | ||
|
|
f98e1b0b96 | ||
|
|
7cd1a3c766 | ||
|
|
46f1ce1e2f | ||
|
|
be230d16e5 | ||
|
|
5b6cce2b73 | ||
|
|
923e3c7b20 | ||
|
|
d81fb97f45 | ||
|
|
3abf9c9204 | ||
|
|
ea23a86f49 | ||
|
|
8ed32e90c9 | ||
|
|
37cf92c07a | ||
|
|
59972fbfe1 | ||
|
|
f110552898 | ||
|
|
315e2e3da8 | ||
|
|
b57debe5b3 | ||
|
|
8c7f9dc1a8 | ||
|
|
823c353faa | ||
|
|
06219c7f88 | ||
|
|
5d47f9a565 | ||
|
|
1298c29dc7 | ||
|
|
76a73a0a5d | ||
|
|
b2ce00ed56 | ||
|
|
d057e7a142 | ||
|
|
3738bfb451 | ||
|
|
8523e1fd95 | ||
|
|
fd5098a409 | ||
|
|
c0f272fcd1 | ||
|
|
1b71cfccba | ||
|
|
549a9ea6fa | ||
|
|
614efea3e6 | ||
|
|
c57304a591 | ||
|
|
2d6da37b6b | ||
|
|
e40851ea22 | ||
|
|
9ed5dae480 | ||
|
|
c6953bfa8c | ||
|
|
deb1c3c7a8 | ||
|
|
9ca1592f3f | ||
|
|
17c5738cb4 | ||
|
|
d5615102d5 | ||
|
|
40c9b38023 | ||
|
|
dc9a5cbfd7 | ||
|
|
65499d863a | ||
|
|
6e6c32ed6c | ||
|
|
f9fe5d06ad | ||
|
|
f3b6966d14 | ||
|
|
740fdc139f | ||
|
|
247ec0cb84 | ||
|
|
13a63331b6 | ||
|
|
9278ce98f7 | ||
|
|
4ce53025cb | ||
|
|
3e58ce96ac | ||
|
|
acb74236ac | ||
|
|
c5094206ce | ||
|
|
24478a8a72 | ||
|
|
e081e46950 | ||
|
|
22c76e85db | ||
|
|
d1d864546c |
No files matched your search
@@ -32,7 +32,11 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.1 ---
|
||||
--- mpv 0.18.1 ---
|
||||
---- - remove "status" log level from mpv_request_log_messages() docs. This
|
||||
is 100% equivalent to "v". The behavior is still the same, thus no
|
||||
actual API change.
|
||||
--- mpv 0.18.0 ---
|
||||
1.21 - mpv_set_property() changes behavior with MPV_FORMAT_NODE. Before this
|
||||
change it rejected mpv_nodes with format==MPV_FORMAT_STRING if the
|
||||
property was not a string or did not have special mechanisms in place
|
||||
|
||||
@@ -19,7 +19,26 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.1 ---
|
||||
--- mpv 0.18.1 ---
|
||||
- deprecate --heartbeat-cmd
|
||||
- remove --softvol=no capability:
|
||||
- deprecate --softvol, it now does nothing
|
||||
- --volume, --mute, and the corrsponding properties now always control
|
||||
softvol, and behave as expected without surprises (e.g. you can set
|
||||
them normally while no audio is initialized)
|
||||
- rename --softvol-max to --volume-max (deprecated alias is added)
|
||||
- the --volume-restore-data option and property are removed without
|
||||
replacement. They were _always_ internal, and used for watch-later
|
||||
resume/restore. Now --volume/--mute are saved directly instead.
|
||||
- the previous point means resuming files with older watch-later configs
|
||||
will print an error about missing --volume-restore-data (which you can
|
||||
ignore), and will not restore the previous value
|
||||
- as a consequence, volume controls will no longer control PulseAudio
|
||||
per-application value, or use the system mixer's per-application
|
||||
volume processing
|
||||
- system or per-application volume can still be controlled with the
|
||||
ao-volume and ao-mute properties (there are no command line options)
|
||||
--- mpv 0.18.0 ---
|
||||
- now ab-loops are active even if one of the "ab-loop-a"/"-b" properties is
|
||||
unset ("no"), in which case the start of the file is used if the A loop
|
||||
point is unset, and the end of the file for an unset B loop point
|
||||
@@ -27,7 +46,7 @@ Interface changes
|
||||
(also needs --embeddedfonts=no)
|
||||
- add "hwdec-interop" and "hwdec-current" properties
|
||||
- deprecated "hwdec-active" and "hwdec-detected" properties (to be removed
|
||||
in mpv 0.19.0)
|
||||
in mpv 0.20.0)
|
||||
- choice option/property values that are "yes" or "no" will now be returned
|
||||
as booleans when using the mpv_node functions in the client API, the
|
||||
"native" property accessors in Lua, and the JSON API. They can be set as
|
||||
|
||||
+36
-42
@@ -459,6 +459,8 @@ Input Commands that are Possibly Subject to Change
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
not control automatically inserted filters.
|
||||
|
||||
The argument is always needed. E.g. in case of ``clr`` use ``vf clr ""``.
|
||||
|
||||
You can assign labels to filter by prefixing them with ``@name:`` (where
|
||||
``name`` is a user-chosen arbitrary identifier). Labels can be used to
|
||||
refer to filters by name in all of the filter chain modification commands.
|
||||
@@ -561,29 +563,20 @@ Input Commands that are Possibly Subject to Change
|
||||
the resolution is reduced to that of the video's. You can read the
|
||||
``osd-width`` and ``osd-height`` properties. At least with ``--vo-xv`` and
|
||||
anamorphic video (such as DVD), ``osd-par`` should be read as well, and the
|
||||
overlay should be aspect-compensated. (Future directions: maybe mpv should
|
||||
take care of some of these things automatically, but it's hard to tell
|
||||
where to draw the line.)
|
||||
overlay should be aspect-compensated.
|
||||
|
||||
``id`` is an integer between 0 and 63 identifying the overlay element. The
|
||||
ID can be used to add multiple overlay parts, update a part by using this
|
||||
command with an already existing ID, or to remove a part with
|
||||
``overlay-remove``. Using a previously unused ID will add a new overlay,
|
||||
while reusing an ID will update it. (Future directions: there should be
|
||||
something to ensure different programs wanting to create overlays don't
|
||||
conflict with each others, should that ever be needed.)
|
||||
while reusing an ID will update it.
|
||||
|
||||
``x`` and ``y`` specify the position where the OSD should be displayed.
|
||||
|
||||
``file`` specifies the file the raw image data is read from. It can be
|
||||
either a numeric UNIX file descriptor prefixed with ``@`` (e.g. ``@4``),
|
||||
or a filename. The file will be mapped into memory with ``mmap()``. Some VOs
|
||||
will pass the mapped pointer directly to display APIs (e.g. opengl or
|
||||
vdpau), so no actual copying is involved. Truncating the source file while
|
||||
the overlay is active will crash the player. You shouldn't change the data
|
||||
while the overlay is active, because the data is essentially accessed at
|
||||
random points. Instead, call ``overlay-add`` again (preferably with a
|
||||
different memory region to prevent tearing).
|
||||
or a filename. The file will be mapped into memory with ``mmap()``,
|
||||
copied, and unmapped before the command returns (changed in mpv 0.18.1).
|
||||
|
||||
It is also possible to pass a raw memory address for use as bitmap memory
|
||||
by passing a memory address as integer prefixed with an ``&`` character.
|
||||
@@ -616,15 +609,14 @@ Input Commands that are Possibly Subject to Change
|
||||
(Technically, the minimum size would be ``stride * (h - 1) + w * 4``, but
|
||||
for simplicity, the player will access all ``stride * h`` bytes.)
|
||||
|
||||
.. admonition:: Warning
|
||||
.. note::
|
||||
|
||||
When updating the overlay, you should prepare a second shared memory
|
||||
region (e.g. make use of the offset parameter) and add this as overlay,
|
||||
instead of reusing the same memory every time. Otherwise, you might
|
||||
get the equivalent of tearing, when your application and mpv write/read
|
||||
the buffer at the same time. Also, keep in mind that mpv might access
|
||||
an overlay's memory at random times whenever it feels the need to do
|
||||
so, for example when redrawing the screen.
|
||||
Before mpv 0.18.1, you had to do manual "double buffering" when updating
|
||||
an overlay by replacing it with a different memory buffer. Since mpv
|
||||
0.18.1, the memory is simply copied and doesn't reference any of the
|
||||
memory indicated by the command's arguments after the commend returns.
|
||||
If you want to use this command before mpv 0.18.1, reads the old docs
|
||||
to see how to handle this correctly.
|
||||
|
||||
``overlay-remove <id>``
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
@@ -1242,29 +1234,31 @@ Property list
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
the ``no`` case will provide possibly wrong or insignificant values.
|
||||
|
||||
Note that an active mixer does not necessarily imply active audio output,
|
||||
although this is implied in the current implementation.
|
||||
This option is relatively useless. Before mpv 0.18.1, it could be used to
|
||||
infer behavior of the ``volume`` property.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (see ``--volume`` for details). Also see ``mixer-active``
|
||||
property.
|
||||
Current volume (see ``--volume`` for details).
|
||||
|
||||
``volume-max``
|
||||
Current maximum value the volume property can be set to. (This may depend
|
||||
on the ``--softvol-max`` option.)
|
||||
``volume-max`` (RW)
|
||||
Current maximum value the volume property can be set to. (Equivalent to the
|
||||
``--volume-max`` option.)
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``). Also see ``mixer-active`` property.
|
||||
Current mute status (``yes``/``no``).
|
||||
|
||||
``ao-volume`` (RW)
|
||||
System volume. This property is available only if mpv audio output is
|
||||
currently active, and only if the underlying implementation supports volume
|
||||
control. What this option does depends on the API. For example, on ALSA
|
||||
this usually changes system-wide audio, while with PulseAudio this controls
|
||||
per-application volume.
|
||||
|
||||
``ao-mute`` (RW)
|
||||
Similar to ``ao-volume``, but controls the mute state. May be unimplemented
|
||||
even if ``ao-volume`` works.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
@@ -1388,7 +1382,7 @@ Property list
|
||||
properties to see whether this was successful.
|
||||
|
||||
Unlike in mpv 0.9.x and before, this does not return the currently active
|
||||
hardware decoder. Since mpv 0.17.1, ``hwdec-current`` is available for
|
||||
hardware decoder. Since mpv 0.18.0, ``hwdec-current`` is available for
|
||||
this purpose.
|
||||
|
||||
``hwdec-current``
|
||||
@@ -1412,13 +1406,13 @@ Property list
|
||||
platform and VO.
|
||||
|
||||
``hwdec-active``
|
||||
Deprecated. To be removed in mpv 0.19.0. Use ``hwdec-current`` instead.
|
||||
Deprecated. To be removed in mpv 0.20.0. Use ``hwdec-current`` instead.
|
||||
|
||||
Return ``yes`` or ``no``, depending on whether any type of hardware decoding
|
||||
is actually in use.
|
||||
|
||||
``hwdec-detected``
|
||||
Deprecated. To be removed in mpv 0.19.0.
|
||||
Deprecated. To be removed in mpv 0.20.0.
|
||||
|
||||
If hardware decoding is active, this returns the hardware decoder in use.
|
||||
Otherwise, it returns either ``no``, or if applicable, the currently loaded
|
||||
@@ -2134,7 +2128,7 @@ Property list
|
||||
|
||||
(Note that if an option is marked as file-local, even ``options/`` will
|
||||
access the local value, and the ``old`` value, which will be restored on
|
||||
end of playback, can not be read or written until end of playback.)
|
||||
end of playback, cannot be read or written until end of playback.)
|
||||
|
||||
``option-info/<name>``
|
||||
Additional per-option information.
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Similar to ``mp.set_property``, but set the given property using its native
|
||||
type.
|
||||
|
||||
Since there are several data types which can not represented natively in
|
||||
Since there are several data types which cannot represented natively in
|
||||
Lua, this might not always work as expected. For example, while the Lua
|
||||
wrapper can do some guesswork to decide whether a Lua table is an array
|
||||
or a map, this would fail with empty tables. Also, there are not many
|
||||
|
||||
+34
-31
@@ -638,15 +638,15 @@ Video
|
||||
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, and not certain filtering (such as debanding)
|
||||
can not be applied in an ideal way.
|
||||
not display correctly, not certain filtering (such as debanding)
|
||||
cannot be applied in an ideal way.
|
||||
|
||||
``vdpau`` is usually safe. If deinterlacing enabled (or the ``vdpaupp``
|
||||
video filter is active in general), it forces RGB conversion. The latter
|
||||
currently does not treat certain colorspaces like BT.2020 correctly
|
||||
(which is mostly a mpv-specific restriction). If the ``vdpauprb``
|
||||
retrieves image data without RGB conversion, but does not work with
|
||||
postprocessing.
|
||||
(which is mostly a mpv-specific restriction). The ``vdpauprb`` 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
|
||||
@@ -1072,14 +1072,10 @@ Audio
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
amplification. A value of -1 (the default) will not change the volume. See
|
||||
also ``--softvol``.
|
||||
amplification. Negative values can be passed for compatibility, but are
|
||||
treated as 0.
|
||||
|
||||
.. note::
|
||||
|
||||
This was changed after the mpv 0.9 release. Before that, 100 actually
|
||||
meant maximum volume. At the same time, the volume scale was made cubic,
|
||||
so the old values won't match up with the new ones anyway.
|
||||
Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
|
||||
|
||||
``--audio-delay=<sec>``
|
||||
Audio delay in seconds (positive or negative float value). Positive values
|
||||
@@ -1094,20 +1090,17 @@ Audio
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
the internal mpv volume filter.
|
||||
``--softvol=<no|yes|auto>``
|
||||
Deprecated/unfunctional. Before mpv 0.18.1, this used to control whether
|
||||
to use the volume controls of the audio output driver or the internal mpv
|
||||
volume filter.
|
||||
|
||||
:no: prefer audio driver controls, use the volume filter only if
|
||||
absolutely needed
|
||||
:yes: always use the volume filter
|
||||
:auto: prefer the volume filter if the audio driver uses the system mixer
|
||||
(default)
|
||||
The current behavior is as if this option was set to ``yes``. The other
|
||||
behaviors are not available anymore, although ``auto`` almost matches
|
||||
current behavior in most cases.
|
||||
|
||||
The intention of ``auto`` is to avoid changing system mixer settings from
|
||||
within mpv with default settings. mpv is a video player, not a mixer panel.
|
||||
On the other hand, mixer controls are enabled for sound servers like
|
||||
PulseAudio, which provide per-application volume.
|
||||
The ``no`` behavior is still partially available through the ``ao-volume``
|
||||
and ``ao-mute`` properties. But there are no options to reset these.
|
||||
|
||||
``--audio-demuxer=<[+]name>``
|
||||
Use this audio demuxer type when using ``--audio-file``. Use a '+' before
|
||||
@@ -1264,10 +1257,12 @@ Audio
|
||||
their start timestamps differ, and then video timing is gradually adjusted
|
||||
if necessary to reach correct synchronization later.
|
||||
|
||||
``--softvol-max=<100.0-1000.0>``
|
||||
``--volume-max=<100.0-1000.0>``, ``--softvol-max=<...>``
|
||||
Set the maximum amplification level in percent (default: 130). A value of
|
||||
130 will allow you to adjust the volume up to about double the normal level.
|
||||
|
||||
``--softvol-max`` is a deprecated alias and should not be used.
|
||||
|
||||
``--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
|
||||
@@ -1276,7 +1271,7 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
:all: Load all audio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
@@ -1320,7 +1315,7 @@ Subtitles
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
subtitles (DVD, Bluray/PGS, DVB) cannot changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
@@ -2019,6 +2014,14 @@ Window
|
||||
be the default behavior. Currently only affects X11 VOs.
|
||||
|
||||
``--heartbeat-cmd=<command>``
|
||||
|
||||
.. warning::
|
||||
|
||||
This option is redundant with Lua scripting. Further, it shouldn't be
|
||||
needed for disabling screensaver anyway, since mpv will call
|
||||
``xdg-screensaver`` when using X11 backend. As a consequence this
|
||||
option has been deprecated with no direct replacement.
|
||||
|
||||
Command that is executed every 30 seconds during playback via *system()* -
|
||||
i.e. using the shell. The time between the commands can be customized with
|
||||
the ``--heartbeat-interval`` option. The command is not run while playback
|
||||
@@ -2438,7 +2441,7 @@ Demuxer
|
||||
stop reading additional packets as soon as one of the limits is reached.
|
||||
(The limits still can be slightly overstepped due to technical reasons.)
|
||||
|
||||
Set these limits highher if you get a packet queue overflow warning, and
|
||||
Set these limits higher if you get a packet queue overflow warning, and
|
||||
you think normal playback would be possible with a larger packet queue.
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
@@ -2463,7 +2466,7 @@ Demuxer
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's a http stream with a server that doesn't support range
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
requests), seeking will be disabled. This option can forcibly enable it.
|
||||
For seeks within the cache, there's a good chance of success.
|
||||
|
||||
@@ -3290,7 +3293,7 @@ Cache
|
||||
With ``auto``, the cache will usually be enabled for network streams,
|
||||
using the size set by ``--cache-default``. With ``yes``, the cache will
|
||||
always be enabled with the size set by ``--cache-default`` (unless the
|
||||
stream can not be cached, or ``--cache-default`` disables caching).
|
||||
stream cannot be cached, or ``--cache-default`` disables caching).
|
||||
|
||||
May be useful when playing files from slow media, but can also have
|
||||
negative effects, especially with file formats that require a lot of
|
||||
@@ -3685,7 +3688,7 @@ Miscellaneous
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter can not be changed yet during playback. It's also not
|
||||
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
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
|
||||
+5
-1
@@ -214,7 +214,7 @@ Available filters are:
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsamplimg, it will fail.
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -288,6 +288,8 @@ Available filters are:
|
||||
:adobe: Adobe RGB (1998)
|
||||
:prophoto: ProPhoto RGB (ROMM)
|
||||
:cie1931: CIE 1931 RGB
|
||||
:dci-p3: DCI-P3 (Digital Cinema)
|
||||
:v-gamut: Panasonic V-Gamut primaries
|
||||
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
@@ -311,6 +313,8 @@ Available filters are:
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:st2084: SMPTE ST2084 (HDR) curve
|
||||
:std-b67: ARIB STD-B67 (Hybrid Log-gamma) curve
|
||||
:v-log: Panasonic V-Log transfer curve
|
||||
|
||||
``<peak>``
|
||||
Reference peak illumination for the video file. This is mostly
|
||||
|
||||
+16
-1
@@ -465,7 +465,7 @@ Available video output drivers are:
|
||||
8
|
||||
Dither to 8 bit output.
|
||||
|
||||
Note that the depth of the connected video display device can not be
|
||||
Note that the depth of the connected video display device cannot be
|
||||
detected. Often, LCD panels will do dithering on their own, which
|
||||
conflicts with ``opengl``'s dithering and leads to ugly output.
|
||||
|
||||
@@ -629,6 +629,9 @@ Available video output drivers are:
|
||||
never resets (regardless of seeks).
|
||||
vec2 image_size
|
||||
The size in pixels of the input image.
|
||||
vec2 target_size
|
||||
The size in pixels of the visible part of the scaled (and possibly
|
||||
cropped) image.
|
||||
|
||||
For example, a shader that inverts the colors could look like this::
|
||||
|
||||
@@ -979,6 +982,8 @@ Available video output drivers are:
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
v-gamut
|
||||
Panasonic V-Gamut (VARICAM) primaries
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
@@ -1003,6 +1008,16 @@ Available video output drivers are:
|
||||
ProPhoto RGB (ROMM)
|
||||
st2084
|
||||
SMPTE ST2084 (HDR) curve, PQ OETF
|
||||
std-b67
|
||||
ARIB STD-B67 (Hybrid Log-gamma) curve, also known as BBC/NHK HDR
|
||||
v-log
|
||||
Panasonic V-Log (VARICAM) curve
|
||||
|
||||
NOTE: When using HDR output formats, mpv will encode to the specified
|
||||
curve but it will not set any HDMI flags or other signalling that
|
||||
might be required for the target device to correctly display the
|
||||
HDR signal. The user should independently guarantee this before
|
||||
using these signal formats for display.
|
||||
|
||||
``target-brightness=<1..100000>``
|
||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||
|
||||
@@ -123,7 +123,7 @@ options/options.h, options/options.c
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from VOs and other sources (such
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
|
||||
@@ -137,6 +137,20 @@ most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
|
||||
+81
-1
@@ -1,3 +1,83 @@
|
||||
Release 0.18.1
|
||||
==============
|
||||
|
||||
Note: Running mpv with different versions of the FFmpeg/Libav libraries than
|
||||
it was compiled with is no longer supported. Even supposedly ABI-compatible
|
||||
versions have been a source of trouble, and it creates far too much
|
||||
complexity with little to no benefit, coupled with absurd and unusable FFmpeg
|
||||
API artifacts.
|
||||
|
||||
Instead, mpv will exit with an error when such a situation is detected.
|
||||
This simply means that mpv needs to be rebuilt whenever FFmpeg libraries change.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- d3d: implement screenshots for --hwdec=d3d11va
|
||||
- vo_opengl: add output_size uniform to custom shader
|
||||
- vo_opengl: implement the Panasonic V-Log function (#3157)
|
||||
- vo_opengl: implement ARIB STD-B68 (HLG) HDR TRC
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
- command: pack sub image data in overlay-add command
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --heartbeat-cmd
|
||||
- audio: deprecate --softvol
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio: drop --softvol=no and --softvol=auto (#3322)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- video: fix deinterlace filter handling on pixel format changes
|
||||
- x11: silence xdg-screensaver
|
||||
- vo_opengl: angle: update the swapchain on resize (#3301)
|
||||
- vo_opengl: error out gracefully when trying to use FBOs without FBO API
|
||||
- vd_lavc: expose mastering display side data reference peak (improves results with HDR content)
|
||||
- vo_opengl: generalize HDR tone mapping mechanism (#3293)
|
||||
- vo_opengl: don't constantly resize the output FBO
|
||||
- vo_opengl: use ringbuffer of PBOs
|
||||
- Windows: make WM_NCHITTEST simpler and more accurate
|
||||
- ao_oss: do not add an entry to audio-device-list if device file missing
|
||||
- dec_audio: fix segment boudnary switching
|
||||
- ao_lavc, vo_lavc: Migrate to new FFmpeg encoding API
|
||||
- vo_opengl: explicitly use main framebuffer when reading window content (#3284)
|
||||
- vo_xv: fix behavior with odd sizes
|
||||
- audio: insert auto-inserted filters at end of chain
|
||||
- x11: add missing FocusChangeMask (disables key repeat when losing focus while a key is down)
|
||||
- ao_coreaudio: error out when selecting invalid device
|
||||
- ad_lavc: work around misbehavior of some FFmpeg decoders like wmapro (#3297)
|
||||
- player: cut off status line on terminal width
|
||||
|
||||
|
||||
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.18.0..v0.18.1`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.0...v0.18.1 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.0..v0.18.1
|
||||
|
||||
|
||||
Release 0.18.0
|
||||
==============
|
||||
|
||||
@@ -39,7 +119,7 @@ New
|
||||
|
||||
|
||||
Removed
|
||||
-------
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove nnedi3 prescaler (replaced by user shaders)
|
||||
- vo_opengl: remove prescaling framework with superxbr prescaler (replaced by user shaders)
|
||||
|
||||
+10
-2
@@ -257,13 +257,21 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
double f_end = f->pts + f->samples / (double)f->rate;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
|
||||
@@ -73,6 +73,7 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
@@ -186,6 +186,12 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
// If the decoder discards the timestamp for some reason, we use the
|
||||
// interpolated PTS. Initialize it so that it works for the initial
|
||||
// packet as well.
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
|
||||
@@ -200,7 +200,9 @@ void audio_work(struct dec_audio *da)
|
||||
if (da->current_frame)
|
||||
return;
|
||||
|
||||
if (!da->packet && demux_read_packet_async(da->header, &da->packet) == 0) {
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
@@ -211,6 +213,7 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
@@ -233,12 +236,12 @@ void audio_work(struct dec_audio *da)
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = true;
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
|
||||
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->start;
|
||||
segment_end = da->current_frame->pts >= da->end;
|
||||
if (da->current_frame->samples == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
|
||||
+4
-35
@@ -210,29 +210,6 @@ static struct af_instance *af_prepend(struct af_stream *s,
|
||||
return new;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name after the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_append(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->first;
|
||||
if (af == s->last)
|
||||
af = s->last->prev;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->prev = af;
|
||||
new->next = af->next;
|
||||
af->next = new;
|
||||
new->next->prev = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
@@ -275,6 +252,8 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af == at)
|
||||
@@ -287,11 +266,6 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
{
|
||||
return af && strcmp(af->info->name, "lavrresample") == 0;
|
||||
}
|
||||
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
// input format doesn't match what af expects.
|
||||
// Returns:
|
||||
@@ -557,7 +531,7 @@ void af_destroy(struct af_stream *s)
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the prefered output parameters is 0 the one that needs
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
@@ -602,12 +576,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new;
|
||||
// Insert the filter somewhere nice
|
||||
if (af_is_conversion_filter(s->first->next))
|
||||
new = af_append(s, s->first->next, name, args);
|
||||
else
|
||||
new = af_prepend(s, s->first->next, name, args);
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
+13
-264
@@ -35,18 +35,6 @@ struct mixer {
|
||||
struct MPOpts *opts;
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool persistent_volume; // volume does not need to be restored
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
float vol_l, vol_r;
|
||||
bool muted;
|
||||
// Used to decide whether we should unmute on uninit
|
||||
bool muted_by_us;
|
||||
/* Contains ao driver name or "softvol" if volume is not persistent
|
||||
* and needs to be restored after the driver is reinitialized. */
|
||||
const char *driver;
|
||||
// Other stuff
|
||||
float balance;
|
||||
};
|
||||
@@ -57,82 +45,25 @@ struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
|
||||
*mixer = (struct mixer) {
|
||||
.log = mp_log_new(mixer, global->log, "mixer"),
|
||||
.opts = global->opts,
|
||||
.vol_l = 100,
|
||||
.vol_r = 100,
|
||||
.driver = "",
|
||||
};
|
||||
return mixer;
|
||||
}
|
||||
|
||||
bool mixer_audio_initialized(struct mixer *mixer)
|
||||
{
|
||||
return !!mixer->ao;
|
||||
return !!mixer->af;
|
||||
}
|
||||
|
||||
float mixer_getmaxvolume(struct mixer *mixer)
|
||||
// Called when opts->softvol_volume or opts->softvol_mute were changed.
|
||||
void mixer_update_volume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? mixer->opts->softvol_max : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
if (!mixer->af)
|
||||
return;
|
||||
|
||||
ao_control_vol_t vol = {mixer->vol_l, mixer->vol_r};
|
||||
if (mixer->softvol) {
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = gain * 100.0;
|
||||
vol.right = gain * 100.0;
|
||||
} else {
|
||||
MP_DBG(mixer, "Reading volume from AO.\n");
|
||||
// Rely on the values not changing if the query is not supported
|
||||
ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol);
|
||||
ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted);
|
||||
}
|
||||
float l = mixer->vol_l;
|
||||
float r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
/* Try to detect cases where the volume has been changed by some external
|
||||
* action (such as something else changing a shared system-wide volume).
|
||||
* We don't test for exact equality, as some AOs may round the value
|
||||
* we last set to some nearby supported value. 3 has been the default
|
||||
* volume step for increase/decrease keys, and is apparently big enough
|
||||
* to step to the next possible value in most setups.
|
||||
*/
|
||||
if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) {
|
||||
mixer->vol_l = vol.left;
|
||||
mixer->vol_r = vol.right;
|
||||
if (mixer->emulate_mute)
|
||||
mixer->muted = false;
|
||||
}
|
||||
mixer->muted_by_us &= mixer->muted;
|
||||
}
|
||||
float gain = MPMAX(mixer->opts->softvol_volume / 100.0, 0);
|
||||
if (mixer->opts->softvol_mute == 1)
|
||||
gain = 0.0;
|
||||
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
*l = mixer->vol_l;
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float gain = (l + r) / 2.0 / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
@@ -143,57 +74,6 @@ static void setvolume_internal(struct mixer *mixer)
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
checkvolume(mixer); // to check mute status
|
||||
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
{
|
||||
float mixer_l, mixer_r;
|
||||
mixer_getvolume(mixer, &mixer_l, &mixer_r);
|
||||
*b = (mixer_l + mixer_r) / 2;
|
||||
}
|
||||
|
||||
void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
if (mute == mixer->muted)
|
||||
return;
|
||||
if (mixer->ao) {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
checkvolume(mixer);
|
||||
} else {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
}
|
||||
}
|
||||
|
||||
bool mixer_getmute(struct mixer *mixer)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
return mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_addvolume(struct mixer *mixer, float step)
|
||||
{
|
||||
float vol_l, vol_r;
|
||||
mixer_getvolume(mixer, &vol_l, &vol_r);
|
||||
mixer_setvolume(mixer, vol_l + step, vol_r + step);
|
||||
}
|
||||
|
||||
void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
{
|
||||
if (mixer->af)
|
||||
@@ -242,130 +122,18 @@ void mixer_setbalance(struct mixer *mixer, float val)
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->driver[0])
|
||||
return NULL;
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us);
|
||||
}
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
{
|
||||
bool ao_perapp = ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) == 1;
|
||||
bool ao_softvol = ao_control(mixer->ao, AOCONTROL_HAS_SOFT_VOLUME, 0) == 1;
|
||||
assert(!(ao_perapp && ao_softvol));
|
||||
mixer->persistent_volume = !ao_softvol;
|
||||
|
||||
if (mixer->opts->softvol == SOFTVOL_AUTO) {
|
||||
// No system-wide volume => fine with AO volume control.
|
||||
mixer->softvol = !ao_softvol && !ao_perapp;
|
||||
} else {
|
||||
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
|
||||
}
|
||||
|
||||
if (mixer->softvol)
|
||||
mixer->persistent_volume = false;
|
||||
|
||||
MP_DBG(mixer, "Will use af_volume: %s\n", mixer->softvol ? "yes" : "no");
|
||||
|
||||
// If we can't use real volume control => force softvol.
|
||||
if (!mixer->softvol) {
|
||||
ao_control_vol_t vol;
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) {
|
||||
mixer->softvol = true;
|
||||
MP_WARN(mixer, "Hardware volume control unavailable.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Probe native mute support.
|
||||
mixer->emulate_mute = true;
|
||||
if (!mixer->softvol) {
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_MUTE, &(bool){0}) == CONTROL_OK)
|
||||
mixer->emulate_mute = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_volume(struct mixer *mixer)
|
||||
{
|
||||
struct MPOpts *opts = mixer->opts;
|
||||
struct ao *ao = mixer->ao;
|
||||
|
||||
float force_vol_l = -1, force_vol_r = -1;
|
||||
int force_mute = -1;
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
if (!mixer->persistent_volume && strcmp(mixer->driver, prev_driver) == 0) {
|
||||
force_vol_l = mixer->vol_l;
|
||||
force_vol_r = mixer->vol_r;
|
||||
}
|
||||
|
||||
// Set mute if we disabled it on uninit last time.
|
||||
if (mixer->muted_by_us)
|
||||
force_mute = 1;
|
||||
|
||||
// Set parameters from command line.
|
||||
if (opts->mixer_init_volume >= 0)
|
||||
force_vol_l = force_vol_r = opts->mixer_init_volume;
|
||||
if (opts->mixer_init_mute >= 0)
|
||||
force_mute = opts->mixer_init_mute;
|
||||
|
||||
// Set parameters from playback resume.
|
||||
char *data = mixer->opts->mixer_restore_volume_data;
|
||||
if (!mixer->persistent_volume && data && data[0]) {
|
||||
char drv[40];
|
||||
float v_l, v_r;
|
||||
int m;
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
|
||||
if (strcmp(mixer->driver, drv) == 0) {
|
||||
force_vol_l = v_l;
|
||||
force_vol_r = v_r;
|
||||
force_mute = !!m;
|
||||
MP_DBG(mixer, "Restoring volume from resume config.\n");
|
||||
}
|
||||
}
|
||||
talloc_free(mixer->opts->mixer_restore_volume_data);
|
||||
mixer->opts->mixer_restore_volume_data = NULL;
|
||||
}
|
||||
|
||||
// Using --volume should not reset the volume on every file (i.e. reinit),
|
||||
// OTOH mpv --{ --volume 10 f1.mkv --} --{ --volume 20 f2.mkv --} must work.
|
||||
// Resetting the option volumes to "auto" (-1) is easiest. If file local
|
||||
// options (as shown above) are used, the option handler code will reset
|
||||
// them to other values, and force the volume to be reset as well.
|
||||
opts->mixer_init_volume = -1;
|
||||
opts->mixer_init_mute = -1;
|
||||
|
||||
checkvolume(mixer);
|
||||
if (force_vol_l >= 0 && force_vol_r >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous volume.\n");
|
||||
mixer_setvolume(mixer, force_vol_l, force_vol_r);
|
||||
}
|
||||
if (force_mute >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous mute toggle.\n");
|
||||
mixer_setmute(mixer, force_mute);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after the audio filter chain is built or rebuilt.
|
||||
// (Can be called multiple times, even without mixer_uninit() in-between.)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af)
|
||||
{
|
||||
if (!ao || !af)
|
||||
return;
|
||||
mixer->ao = ao;
|
||||
mixer->af = af;
|
||||
if (!af)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Reinit...\n");
|
||||
if (mixer->opts->softvol == SOFTVOL_NO)
|
||||
MP_ERR(mixer, "--softvol=no is not supported anymore.\n");
|
||||
|
||||
probe_softvol(mixer);
|
||||
restore_volume(mixer);
|
||||
mixer_update_volume(mixer);
|
||||
|
||||
if (mixer->balance != 0)
|
||||
mixer_setbalance(mixer, mixer->balance);
|
||||
@@ -380,24 +148,5 @@ void mixer_uninit_audio(struct mixer *mixer)
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Uninit...\n");
|
||||
|
||||
checkvolume(mixer);
|
||||
if (mixer->muted_by_us && mixer->persistent_volume) {
|
||||
MP_DBG(mixer, "Draining.\n");
|
||||
/* Current audio output API combines playing the remaining buffered
|
||||
* audio and uninitializing the AO into one operation, even though
|
||||
* ideally unmute would happen between those two steps. We can't do
|
||||
* volume changes after uninitialization, but we don't want the
|
||||
* remaining audio to play at full volume either. Thus this
|
||||
* workaround to drop remaining audio first. */
|
||||
ao_reset(mixer->ao);
|
||||
mixer_setmute(mixer, false);
|
||||
/* We remember mute status and re-enable it if we play more audio
|
||||
* in the same process. */
|
||||
mixer->muted_by_us = true;
|
||||
}
|
||||
mixer->ao = NULL;
|
||||
mixer->af = NULL;
|
||||
mixer->softvol = false;
|
||||
}
|
||||
+2
-9
@@ -33,18 +33,11 @@ struct af_stream;
|
||||
struct mixer;
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af);
|
||||
void mixer_uninit_audio(struct mixer *mixer);
|
||||
bool mixer_audio_initialized(struct mixer *mixer);
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r);
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r);
|
||||
void mixer_addvolume(struct mixer *mixer, float step);
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b);
|
||||
void mixer_setmute(struct mixer *mixer, bool mute);
|
||||
bool mixer_getmute(struct mixer *mixer);
|
||||
void mixer_update_volume(struct mixer *mixer);
|
||||
void mixer_getbalance(struct mixer *mixer, float *bal);
|
||||
void mixer_setbalance(struct mixer *mixer, float bal);
|
||||
float mixer_getmaxvolume(struct mixer *mixer);
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer);
|
||||
|
||||
#endif /* MPLAYER_MIXER_H */
|
||||
+2
-3
@@ -490,10 +490,9 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
static void get_devices(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
int num = list->num_devices;
|
||||
if (ao->driver->list_devs)
|
||||
if (ao->driver->list_devs) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
// Add at least a default entry
|
||||
if (list->num_devices == num) {
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
|
||||
@@ -114,6 +114,13 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
kAudioObjectSystemObject, &p_addr, 0, 0, &size, &v);
|
||||
CFRelease(uid);
|
||||
CHECK_CA_ERROR("unable to query for device UID");
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(*device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_ERROR("could not check whether device is alive (invalid device?)");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao, "device is not alive!\n");
|
||||
} else {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
|
||||
+121
-76
@@ -39,8 +39,6 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int pcmhack;
|
||||
@@ -146,18 +144,10 @@ static int init(struct ao *ao)
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack) {
|
||||
if (ac->pcmhack)
|
||||
ac->aframesize = 16384; // "enough"
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->pcmhack * ao->channels.num * 2 + 200;
|
||||
} else {
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->sample_size * ao->channels.num * 2 + 200;
|
||||
}
|
||||
if (ac->buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
ac->buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
ac->buffer = talloc_size(ac, ac->buffer_size);
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
@@ -182,7 +172,7 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static int encode(struct ao *ao, double apts, void **data);
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
@@ -199,12 +189,12 @@ static void uninit(struct ao *ao)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac->buffer) {
|
||||
if (ac->stream) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
while (encode(ao, outpts, NULL) > 0) ;
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
@@ -220,24 +210,130 @@ static int get_space(struct ao *ao)
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
static void write_packet(struct ao *ao, AVPacket *packet)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
packet->stream_index = ac->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
} else {
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(ac->savepts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx,
|
||||
ac->stream, packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
ac->stream->time_base.num,
|
||||
ac->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
AVPacket packet = {0};
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int status = avcodec_send_frame(ac->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(ac->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(ao, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(ao, "sent audio frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
#else
|
||||
av_init_packet(&packet);
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_audio2(ac->codec, &packet, frame, &got_packet);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
if (!got_packet) {
|
||||
return;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static int encode(struct ao *ao, double apts, void **data)
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
AVPacket packet;
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
int status, gotpacket;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = ac->buffer;
|
||||
packet.size = ac->buffer_size;
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->format = af_to_avformat(ao->format);
|
||||
@@ -270,64 +366,11 @@ static int encode(struct ao *ao, double apts, void **data)
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = ac->codec->global_quality;
|
||||
status = avcodec_encode_audio2(ac->codec, &packet, frame, &gotpacket);
|
||||
|
||||
if (!status) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
|
||||
encode_audio_and_write(ao, frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = avcodec_encode_audio2(ac->codec, &packet, NULL, &gotpacket);
|
||||
}
|
||||
|
||||
if(status) {
|
||||
MP_ERR(ao, "error encoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gotpacket)
|
||||
return 0;
|
||||
|
||||
MP_DBG(ao, "got pts %f (playback time: %f); out size: %d\n",
|
||||
apts, realapts, packet.size);
|
||||
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
|
||||
packet.stream_index = ac->stream->index;
|
||||
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
if (packet.pts == AV_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE)
|
||||
packet.pts = ac->savepts;
|
||||
}
|
||||
|
||||
if (packet.pts != AV_NOPTS_VALUE)
|
||||
packet.pts = av_rescale_q(packet.pts, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if (packet.dts != AV_NOPTS_VALUE)
|
||||
packet.dts = av_rescale_q(packet.dts, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if(packet.duration > 0)
|
||||
packet.duration = av_rescale_q(packet.duration, ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx, ac->stream, &packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %f %f/%f\n",
|
||||
realapts, (double) ac->stream->time_base.num,
|
||||
(double) ac->stream->time_base.den);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return packet.size;
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -492,3 +535,5 @@ const struct ao_driver audio_out_lavc = {
|
||||
.play = play,
|
||||
.drain = drain,
|
||||
};
|
||||
|
||||
// vim: sw=4 ts=4 et tw=80
|
||||
@@ -612,6 +612,12 @@ static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
return r;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_oss = {
|
||||
@@ -629,6 +635,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.drain = drain,
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.audio_fd = -1,
|
||||
|
||||
@@ -132,7 +132,7 @@ static bool thread_feed(struct ao *ao)
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
// committed to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -95,7 +95,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
// maybe MPV can go over to the preferred device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
|
||||
+2
-9
@@ -189,7 +189,7 @@ struct lib {
|
||||
unsigned runv;
|
||||
};
|
||||
|
||||
void print_libav_versions(struct mp_log *log, int v)
|
||||
bool print_libav_versions(struct mp_log *log, int v)
|
||||
{
|
||||
const struct lib libs[] = {
|
||||
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
|
||||
@@ -222,14 +222,7 @@ void print_libav_versions(struct mp_log *log, int v)
|
||||
mp_msg(log, v, "%s version: %s\n", LIB_PREFIX, av_version_info());
|
||||
#endif
|
||||
|
||||
if (mismatch) {
|
||||
// Using mismatched libraries can be legitimate, but even then it's
|
||||
// a bad idea. We don't acknowledge its usefulness and stability.
|
||||
mp_warn(log, "Warning: mpv was compiled against a different version of "
|
||||
"%s than the shared\nlibrary it is linked against. This is "
|
||||
"most likely a broken build\nand misbehavior and crashes are "
|
||||
"to be expected.\n", LIB_PREFIX);
|
||||
}
|
||||
return !mismatch;
|
||||
}
|
||||
|
||||
#undef V
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
#ifndef MP_AV_LOG_H
|
||||
#define MP_AV_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
void init_libav(struct mpv_global *global);
|
||||
void uninit_libav(struct mpv_global *global);
|
||||
void print_libav_versions(struct mp_log *log, int v);
|
||||
bool print_libav_versions(struct mp_log *log, int v);
|
||||
#endif
|
||||
+1
-4
@@ -1247,12 +1247,9 @@ void mp_input_load(struct input_ctx *ictx)
|
||||
void *tmp = talloc_new(NULL);
|
||||
char **files = mp_find_all_config_files(tmp, ictx->global, "input.conf");
|
||||
for (int n = 0; files && files[n]; n++)
|
||||
config_ok = config_ok | parse_config_file(ictx, files[n], false);
|
||||
parse_config_file(ictx, files[n], false);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
if (!config_ok) {
|
||||
MP_VERBOSE(ictx, "Falling back on default (hardcoded) input config\n");
|
||||
}
|
||||
|
||||
if (input_conf->use_alt_gr) {
|
||||
ictx->using_alt_gr = true;
|
||||
|
||||
+1
-1
@@ -1446,7 +1446,7 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
|
||||
* required log level for a message to be received with MPV_EVENT_LOG_MESSAGE.
|
||||
*
|
||||
* @param min_level Minimal log level as string. Valid log levels:
|
||||
* no fatal error warn info status v debug trace
|
||||
* no fatal error warn info v debug trace
|
||||
* The value "no" disables all messages. This is the default.
|
||||
* An exception is the value "terminal-default", which uses the
|
||||
* log level as set by the "--msg-level" option. This works
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ int bstr_validate_utf8(struct bstr s);
|
||||
// talloc, with talloc_ctx as parent.
|
||||
struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
|
||||
|
||||
// Return the text before the occurance of a character, and return it. Change
|
||||
// Return the text before the occurrence of a character, and return it. Change
|
||||
// *rest to point to the text following this character. (rest can be NULL.)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
|
||||
|
||||
|
||||
@@ -499,6 +499,13 @@ struct m_config_option *m_config_get_co(const struct m_config *config,
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
return NULL;
|
||||
} else if (co->opt->deprecation_message) {
|
||||
if (!co->warning_was_printed) {
|
||||
MP_WARN(config, "Warning: option %s%s is deprecated "
|
||||
"and might be removed in the future (%s).\n",
|
||||
prefix, co->name, co->opt->deprecation_message);
|
||||
co->warning_was_printed = true;
|
||||
}
|
||||
}
|
||||
return co;
|
||||
}
|
||||
|
||||
+5
-1
@@ -333,6 +333,10 @@ struct m_option {
|
||||
|
||||
// Initialize variable to given default before parsing options
|
||||
const void *defval;
|
||||
|
||||
// Print a warning when this option is used (for options with no direct
|
||||
// replacement.)
|
||||
const char *deprecation_message;
|
||||
};
|
||||
|
||||
|
||||
@@ -394,7 +398,7 @@ struct m_option {
|
||||
|
||||
// Dynamic data type.
|
||||
/** This flag indicates that the data is dynamically allocated (m_option::p
|
||||
* points to a pointer). It enables a little hack in the \ref Config wich
|
||||
* points to a pointer). It enables a little hack in the \ref Config which
|
||||
* replaces the initial value of such variables with a dynamic copy in case
|
||||
* the initial value is statically allocated (pretty common with strings).
|
||||
*/
|
||||
|
||||
+9
-7
@@ -410,13 +410,13 @@ const m_option_t mp_opts[] = {
|
||||
({"no", SOFTVOL_NO},
|
||||
{"yes", SOFTVOL_YES},
|
||||
{"auto", SOFTVOL_AUTO})),
|
||||
OPT_FLOATRANGE("softvol-max", softvol_max, 0, 100, 1000),
|
||||
OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", mixer_init_mute, 0,
|
||||
OPT_FLOATRANGE("volume-max", softvol_max, 0, 100, 1000),
|
||||
// values <0 for volume and mute are legacy and ignored
|
||||
OPT_FLOATRANGE("volume", softvol_volume, 0, -1, 1000),
|
||||
OPT_CHOICE("mute", softvol_mute, 0,
|
||||
({"auto", -1},
|
||||
{"no", 0},
|
||||
{"yes", 1})),
|
||||
OPT_STRING("volume-restore-data", mixer_restore_volume_data, 0),
|
||||
OPT_CHOICE("gapless-audio", gapless_audio, 0,
|
||||
({"no", 0},
|
||||
{"yes", 1},
|
||||
@@ -469,7 +469,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("vo-mmcss-profile", vo.mmcss_profile, M_OPT_FIXED),
|
||||
#endif
|
||||
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
|
||||
OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0,
|
||||
.deprecation_message = "use Lua scripting instead"),
|
||||
OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
|
||||
|
||||
OPT_CHOICE_OR_INT("screen", vo.screen_id, 0, 0, 32,
|
||||
@@ -684,6 +685,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("ass-use-margins", "sub-use-margins"),
|
||||
OPT_REPLACED("media-title", "force-media-title"),
|
||||
OPT_REPLACED("input-unix-socket", "input-ipc-server"),
|
||||
OPT_REPLACED("softvol-max", "volume-max"),
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -697,8 +699,8 @@ const struct MPOpts mp_default_opts = {
|
||||
.deinterlace = -1,
|
||||
.softvol = SOFTVOL_AUTO,
|
||||
.softvol_max = 130,
|
||||
.mixer_init_volume = -1,
|
||||
.mixer_init_mute = -1,
|
||||
.softvol_volume = 100,
|
||||
.softvol_mute = 0,
|
||||
.gapless_audio = -1,
|
||||
.audio_buffer = 0.2,
|
||||
.audio_device = "auto",
|
||||
|
||||
+2
-3
@@ -89,9 +89,8 @@ typedef struct MPOpts {
|
||||
int ao_null_fallback;
|
||||
int force_vo;
|
||||
int softvol;
|
||||
float mixer_init_volume;
|
||||
int mixer_init_mute;
|
||||
char *mixer_restore_volume_data;
|
||||
float softvol_volume;
|
||||
int softvol_mute;
|
||||
float softvol_max;
|
||||
int gapless_audio;
|
||||
double audio_buffer;
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteModeNone = 0L,
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as noone has an exclusive lock on it
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as no one has an exclusive lock on it
|
||||
// (RECOMMENDED ONLY FOR SPECIAL PURPOSES)
|
||||
|
||||
kHIDRemoteModeExclusive, // Try to acquire an exclusive lock on the remote (NOT RECOMMENDED)
|
||||
@@ -182,7 +182,7 @@ typedef enum
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware is inspected
|
||||
inspectNewHardwareWithService:(io_service_t)service //
|
||||
prematchResult:(BOOL)prematchResult; // Return YES if HIDRemote should go on with this hardware and try
|
||||
// to use it, or NO if it should not be persued further.
|
||||
// to use it, or NO if it should not be pursued further.
|
||||
|
||||
// Exlusive lock lending
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote
|
||||
|
||||
@@ -293,7 +293,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
|
||||
}while(0);
|
||||
|
||||
// An error occured. Do necessary clean up.
|
||||
// An error occurred. Do necessary clean up.
|
||||
if (matchDict!=NULL)
|
||||
{
|
||||
CFRelease(matchDict);
|
||||
@@ -1422,7 +1422,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
[((NSObject <HIDRemoteDelegate> *)[self delegate]) hidRemote:self failedNewHardwareWithError:error];
|
||||
}
|
||||
|
||||
// An error occured or this device is not of interest .. cleanup ..
|
||||
// An error occurred or this device is not of interest .. cleanup ..
|
||||
if (serviceNotification!=0)
|
||||
{
|
||||
IOObjectRelease(serviceNotification);
|
||||
@@ -1615,7 +1615,7 @@ static HIDRemote *sHIDRemote = nil;
|
||||
switch (buttonCode)
|
||||
{
|
||||
case kHIDRemoteButtonCodeIDChanged:
|
||||
// Do nothing, this is handled seperately
|
||||
// Do nothing, this is handled separately
|
||||
break;
|
||||
|
||||
case kHIDRemoteButtonCodeUp:
|
||||
|
||||
@@ -45,6 +45,10 @@ static void *run_subprocess(void *ptr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size)
|
||||
{
|
||||
}
|
||||
|
||||
void mp_subprocess_detached(struct mp_log *log, char **args)
|
||||
{
|
||||
struct subprocess_args *p = talloc_zero(NULL, struct subprocess_args);
|
||||
|
||||
@@ -24,6 +24,8 @@ struct mp_cancel;
|
||||
|
||||
typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);
|
||||
|
||||
void mp_devnull(void *ctx, char *data, size_t size);
|
||||
|
||||
// Start a subprocess. Uses callbacks to read from stdout and stderr.
|
||||
int mp_subprocess(char **args, struct mp_cancel *cancel, void *ctx,
|
||||
subprocess_read_cb on_stdout, subprocess_read_cb on_stderr,
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ static int recreate_audio_filters(struct MPContext *mpctx)
|
||||
if (afs->initialized < 1 && af_init(afs) < 0)
|
||||
goto fail;
|
||||
|
||||
mixer_reinit_audio(mpctx->mixer, mpctx->ao, afs);
|
||||
mixer_reinit_audio(mpctx->mixer, afs);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
|
||||
|
||||
|
||||
+176
-164
@@ -60,6 +60,7 @@
|
||||
#include "audio/filter/af.h"
|
||||
#include "video/decode/dec_video.h"
|
||||
#include "audio/decode/dec_audio.h"
|
||||
#include "video/out/bitmap_packer.h"
|
||||
#include "options/path.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
@@ -88,7 +89,8 @@ struct command_ctx {
|
||||
// One of these is in use by the OSD; the other one exists so that the
|
||||
// bitmap list can be manipulated without additional synchronization.
|
||||
struct sub_bitmaps overlay_osd[2];
|
||||
struct sub_bitmaps *overlay_osd_current;
|
||||
int overlay_osd_current;
|
||||
struct bitmap_packer *overlay_packer;
|
||||
|
||||
struct hook_handler **hooks;
|
||||
int num_hooks;
|
||||
@@ -98,9 +100,8 @@ struct command_ctx {
|
||||
};
|
||||
|
||||
struct overlay {
|
||||
void *map_start;
|
||||
size_t map_size;
|
||||
struct sub_bitmap osd;
|
||||
struct mp_image *source;
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct hook_handler {
|
||||
@@ -1568,44 +1569,29 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET:
|
||||
mixer_getbothvolume(mpctx->mixer, arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){
|
||||
.type = CONF_TYPE_FLOAT,
|
||||
.flags = M_OPT_RANGE,
|
||||
.min = 0,
|
||||
.max = mixer_getmaxvolume(mpctx->mixer),
|
||||
.max = opts->softvol_max,
|
||||
};
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_NEUTRAL:
|
||||
*(float *)arg = 100;
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_PRINT: {
|
||||
float val;
|
||||
mixer_getbothvolume(mpctx->mixer, &val);
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)val);
|
||||
case M_PROPERTY_PRINT:
|
||||
*(char **)arg = talloc_asprintf(NULL, "%i", (int)opts->softvol_volume);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setvolume(mpctx->mixer, *(float *) arg, *(float *) arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_SWITCH: {
|
||||
struct m_property_switch_arg *sarg = arg;
|
||||
mixer_addvolume(mpctx->mixer, sarg->inc);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volume_max(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
return m_property_float_ro(action, arg, mixer_getmaxvolume(mpctx->mixer));
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
mixer_update_volume(mpctx->mixer);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Mute (RW)
|
||||
@@ -1613,34 +1599,76 @@ static int mp_property_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET:
|
||||
mixer_setmute(mpctx->mixer, *(int *) arg);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET:
|
||||
*(int *)arg = mixer_getmute(mpctx->mixer);
|
||||
return M_PROPERTY_OK;
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
|
||||
if (action == M_PROPERTY_GET_TYPE) {
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (action == M_PROPERTY_SET)
|
||||
mixer_update_volume(mpctx->mixer);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mp_property_ao_volume(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
float value = *(float *)arg;
|
||||
ao_control_vol_t vol = {value, value};
|
||||
if (ao_control(ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
ao_control_vol_t vol = {0};
|
||||
if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(float *)arg = (vol.left + vol.right) / 2.0f;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLOAT};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int mp_property_volrestore(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
|
||||
static int mp_property_ao_mute(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
switch (action) {
|
||||
case M_PROPERTY_GET: {
|
||||
char *s = mixer_get_volume_restore_data(mpctx->mixer);
|
||||
*(char **)arg = s;
|
||||
return s ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
|
||||
}
|
||||
case M_PROPERTY_SET:
|
||||
struct ao *ao = mpctx->ao;
|
||||
if (!ao)
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
bool value = *(int *)arg;
|
||||
if (ao_control(ao, AOCONTROL_SET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
case M_PROPERTY_GET: {
|
||||
bool value = false;
|
||||
if (ao_control(ao, AOCONTROL_GET_MUTE, &value) != CONTROL_OK)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
*(int *)arg = value;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET_TYPE:
|
||||
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG};
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static int get_device_entry(int item, int action, void *arg, void *ctx)
|
||||
@@ -2257,88 +2285,6 @@ static int mp_property_detected_hwdec(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
#define VF_DEINTERLACE_LABEL "deinterlace"
|
||||
|
||||
static bool probe_deint_filter(struct MPContext *mpctx, const char *filt)
|
||||
{
|
||||
char filter[80];
|
||||
// add a label so that removing the filter is easier
|
||||
snprintf(filter, sizeof(filter), "@%s:%s", VF_DEINTERLACE_LABEL, filt);
|
||||
return edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "pre", filter) >= 0;
|
||||
}
|
||||
|
||||
static bool check_output_format(struct MPContext *mpctx, int imgfmt)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (!vo_c)
|
||||
return false;
|
||||
return vo_c->vf->allowed_output_formats[imgfmt - IMGFMT_START];
|
||||
}
|
||||
|
||||
static int probe_deint_filters(struct MPContext *mpctx)
|
||||
{
|
||||
if (check_output_format(mpctx, IMGFMT_VDPAU)) {
|
||||
char filter[80] = "vdpaupp:deint=yes";
|
||||
int pref = 0;
|
||||
vo_control(mpctx->video_out, VOCTRL_GET_PREF_DEINT, &pref);
|
||||
pref = pref < 0 ? -pref : pref;
|
||||
if (pref > 0 && pref <= 4) {
|
||||
const char *types[] =
|
||||
{"", "first-field", "bob", "temporal", "temporal-spatial"};
|
||||
mp_snprintf_cat(filter, sizeof(filter), ":deint-mode=%s",
|
||||
types[pref]);
|
||||
}
|
||||
|
||||
probe_deint_filter(mpctx, filter);
|
||||
return 0;
|
||||
}
|
||||
if (check_output_format(mpctx, IMGFMT_VAAPI) &&
|
||||
probe_deint_filter(mpctx, "vavpp"))
|
||||
return 0;
|
||||
if ((check_output_format(mpctx, IMGFMT_D3D11VA) ||
|
||||
check_output_format(mpctx, IMGFMT_D3D11NV12)) &&
|
||||
probe_deint_filter(mpctx, "d3d11vpp"))
|
||||
return 0;
|
||||
if (probe_deint_filter(mpctx, "yadif"))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_deinterlacing(struct MPContext *mpctx)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
int enabled = 0;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
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 remove_deint_filter(struct MPContext *mpctx)
|
||||
{
|
||||
edit_filters(mpctx, mp_null_log, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
|
||||
}
|
||||
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable)
|
||||
{
|
||||
struct vo_chain *vo_c = mpctx->vo_chain;
|
||||
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL)) {
|
||||
if (!enable)
|
||||
remove_deint_filter(mpctx);
|
||||
} else {
|
||||
if ((get_deinterlacing(mpctx) > 0) != enable) {
|
||||
int arg = enable;
|
||||
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &arg) != CONTROL_OK)
|
||||
probe_deint_filters(mpctx);
|
||||
}
|
||||
}
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static int mp_property_deinterlace(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
@@ -2568,13 +2514,13 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
|
||||
{"aspect", SUB_PROP_FLOAT(d_w / (double)d_h)},
|
||||
{"par", SUB_PROP_FLOAT(p.p_w / (double)p.p_h)},
|
||||
{"colormatrix",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.colorspace))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_names, p.color.space))},
|
||||
{"colorlevels",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.colorlevels))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_levels_names, p.color.levels))},
|
||||
{"primaries",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.primaries))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))},
|
||||
{"gamma",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.gamma))},
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
|
||||
{"chroma-location",
|
||||
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
|
||||
{"stereo-in",
|
||||
@@ -3751,8 +3697,10 @@ static const struct m_property mp_properties[] = {
|
||||
// Audio
|
||||
{"mixer-active", mp_property_mixer_active},
|
||||
{"volume", mp_property_volume},
|
||||
{"volume-max", mp_property_volume_max},
|
||||
{"volume-max", mp_property_generic_option},
|
||||
{"mute", mp_property_mute},
|
||||
{"ao-volume", mp_property_ao_volume},
|
||||
{"ao-mute", mp_property_ao_mute},
|
||||
{"audio-delay", mp_property_audio_delay},
|
||||
{"audio-codec-name", mp_property_audio_codec_name},
|
||||
{"audio-codec", mp_property_audio_codec},
|
||||
@@ -3762,7 +3710,6 @@ static const struct m_property mp_properties[] = {
|
||||
M_PROPERTY_DEPRECATED_ALIAS("audio-channels", "audio-params/channel-count"),
|
||||
{"aid", mp_property_audio},
|
||||
{"balance", mp_property_balance},
|
||||
{"volume-restore-data", mp_property_volrestore},
|
||||
{"audio-device", mp_property_audio_device},
|
||||
{"audio-device-list", mp_property_audio_devices},
|
||||
{"current-ao", mp_property_ao},
|
||||
@@ -3940,7 +3887,7 @@ static const char *const *const mp_event_property_change[] = {
|
||||
"colormatrix-output-range", "colormatrix-primaries", "video-aspect"),
|
||||
E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate",
|
||||
"samplerate", "channels", "audio", "volume", "mute", "balance",
|
||||
"volume-restore-data", "current-ao", "audio-codec-name", "audio-params",
|
||||
"current-ao", "audio-codec-name", "audio-params",
|
||||
"audio-out-params", "volume-max", "mixer-active"),
|
||||
E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"),
|
||||
E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle", "eof-reached"),
|
||||
@@ -4314,20 +4261,85 @@ static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype,
|
||||
static void recreate_overlays(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *cmd = mpctx->command_ctx;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[0];
|
||||
if (new == cmd->overlay_osd_current)
|
||||
new += 1; // pick the unused one
|
||||
int overlay_next = !cmd->overlay_osd_current;
|
||||
struct sub_bitmaps *new = &cmd->overlay_osd[overlay_next];
|
||||
new->format = SUBBITMAP_RGBA;
|
||||
new->change_id = 1;
|
||||
// overlay array can have unused entries, but parts list must be "packed"
|
||||
|
||||
bool valid = false;
|
||||
|
||||
new->num_parts = 0;
|
||||
for (int n = 0; n < cmd->num_overlays; n++) {
|
||||
struct overlay *o = &cmd->overlays[n];
|
||||
if (o->osd.bitmap)
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, o->osd);
|
||||
if (o->source) {
|
||||
struct mp_image *s = o->source;
|
||||
struct sub_bitmap b = {
|
||||
.bitmap = s->planes[0],
|
||||
.stride = s->stride[0],
|
||||
.w = s->w, .dw = s->w,
|
||||
.h = s->h, .dh = s->h,
|
||||
.x = o->x,
|
||||
.y = o->y,
|
||||
};
|
||||
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, b);
|
||||
}
|
||||
}
|
||||
cmd->overlay_osd_current = new;
|
||||
osd_set_external2(mpctx->osd, cmd->overlay_osd_current);
|
||||
|
||||
if (!cmd->overlay_packer)
|
||||
cmd->overlay_packer = talloc_zero(cmd, struct bitmap_packer);
|
||||
|
||||
cmd->overlay_packer->padding = 1; // assume bilinear scaling
|
||||
packer_set_size(cmd->overlay_packer, new->num_parts);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++)
|
||||
cmd->overlay_packer->in[n] = (struct pos){new->parts[n].w, new->parts[n].h};
|
||||
|
||||
if (packer_pack(cmd->overlay_packer) < 0 || new->num_parts == 0)
|
||||
goto done;
|
||||
|
||||
struct pos bb[2];
|
||||
packer_get_bb(cmd->overlay_packer, bb);
|
||||
|
||||
new->packed_w = bb[1].x;
|
||||
new->packed_h = bb[1].y;
|
||||
|
||||
if (!new->packed || new->packed->w < new->packed_w ||
|
||||
new->packed->h < new->packed_h)
|
||||
{
|
||||
talloc_free(new->packed);
|
||||
new->packed = mp_image_alloc(IMGFMT_BGRA, cmd->overlay_packer->w,
|
||||
cmd->overlay_packer->h);
|
||||
if (!new->packed)
|
||||
goto done;
|
||||
}
|
||||
|
||||
// clear padding
|
||||
mp_image_clear(new->packed, 0, 0, new->packed->w, new->packed->h);
|
||||
|
||||
for (int n = 0; n < new->num_parts; n++) {
|
||||
struct sub_bitmap *b = &new->parts[n];
|
||||
struct pos pos = cmd->overlay_packer->result[n];
|
||||
|
||||
int stride = new->packed->stride[0];
|
||||
void *pdata = (uint8_t *)new->packed->planes[0] + pos.y * stride + pos.x * 4;
|
||||
memcpy_pic(pdata, b->bitmap, b->w * 4, b->h, stride, b->stride);
|
||||
|
||||
b->bitmap = pdata;
|
||||
b->stride = stride;
|
||||
|
||||
b->src_x = pos.x;
|
||||
b->src_y = pos.y;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
done:
|
||||
if (!valid) {
|
||||
new->format = SUBBITMAP_EMPTY;
|
||||
new->num_parts = 0;
|
||||
}
|
||||
|
||||
osd_set_external2(mpctx->osd, new);
|
||||
cmd->overlay_osd_current = overlay_next;
|
||||
}
|
||||
|
||||
// Set overlay with the given ID to the contents as described by "new".
|
||||
@@ -4342,17 +4354,11 @@ static void replace_overlay(struct MPContext *mpctx, int id, struct overlay *new
|
||||
}
|
||||
|
||||
struct overlay *ptr = &cmd->overlays[id];
|
||||
struct overlay old = *ptr;
|
||||
|
||||
if (!ptr->osd.bitmap && !new->osd.bitmap)
|
||||
return; // don't need to recreate or unmap
|
||||
|
||||
talloc_free(ptr->source);
|
||||
*ptr = *new;
|
||||
recreate_overlays(mpctx);
|
||||
|
||||
// Do this afterwards, so we never unmap while the OSD is using it.
|
||||
if (old.map_start && old.map_size)
|
||||
munmap(old.map_start, old.map_size);
|
||||
recreate_overlays(mpctx);
|
||||
}
|
||||
|
||||
static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
@@ -4368,18 +4374,17 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
MP_ERR(mpctx, "overlay_add: invalid id %d\n", id);
|
||||
goto error;
|
||||
}
|
||||
if (w < 0 || h < 0 || stride < w * 4 || (stride % 4)) {
|
||||
if (w <= 0 || h <= 0 || stride < w * 4 || (stride % 4)) {
|
||||
MP_ERR(mpctx, "overlay_add: inconsistent parameters\n");
|
||||
goto error;
|
||||
}
|
||||
struct overlay overlay = {
|
||||
.osd = {
|
||||
.stride = stride,
|
||||
.x = x, .y = y,
|
||||
.w = w, .h = h,
|
||||
.dw = w, .dh = h,
|
||||
},
|
||||
.source = mp_image_alloc(IMGFMT_BGRA, w, h),
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
if (!overlay.source)
|
||||
goto error;
|
||||
int fd = -1;
|
||||
bool close_fd = true;
|
||||
void *p = NULL;
|
||||
@@ -4398,21 +4403,25 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
|
||||
} else {
|
||||
fd = open(file, O_RDONLY | O_BINARY | O_CLOEXEC);
|
||||
}
|
||||
int map_size = 0;
|
||||
if (fd >= 0) {
|
||||
overlay.map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, overlay.map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
map_size = offset + h * stride;
|
||||
void *m = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (close_fd)
|
||||
close(fd);
|
||||
if (m && m != MAP_FAILED) {
|
||||
overlay.map_start = m;
|
||||
if (m && m != MAP_FAILED)
|
||||
p = m;
|
||||
}
|
||||
}
|
||||
if (!p) {
|
||||
MP_ERR(mpctx, "overlay_add: could not open or map '%s'\n", file);
|
||||
talloc_free(overlay.source);
|
||||
goto error;
|
||||
}
|
||||
overlay.osd.bitmap = (char *)p + offset;
|
||||
memcpy_pic(overlay.source->planes[0], (char *)p + offset, w * 4, h,
|
||||
overlay.source->stride[0], stride);
|
||||
if (map_size)
|
||||
munmap(p, map_size);
|
||||
|
||||
replace_overlay(mpctx, id, &overlay);
|
||||
r = 0;
|
||||
error:
|
||||
@@ -4434,6 +4443,8 @@ static void overlay_uninit(struct MPContext *mpctx)
|
||||
for (int id = 0; id < cmd->num_overlays; id++)
|
||||
overlay_remove(mpctx, id);
|
||||
osd_set_external2(mpctx->osd, NULL);
|
||||
for (int n = 0; n < 2; n++)
|
||||
mp_image_unrefp(&cmd->overlay_osd[n].packed);
|
||||
}
|
||||
|
||||
struct cycle_counter {
|
||||
@@ -4958,6 +4969,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
mpctx->add_osd_seek_info |=
|
||||
(msg_osd ? OSD_SEEK_INFO_TEXT : 0) |
|
||||
(bar_osd ? OSD_SEEK_INFO_BAR : 0);
|
||||
mpctx->osd_force_update = true;
|
||||
break;
|
||||
|
||||
case MP_CMD_TV_LAST_CHANNEL: {
|
||||
|
||||
@@ -59,7 +59,4 @@ void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
|
||||
|
||||
void handle_ab_loop(struct MPContext *mpctx);
|
||||
|
||||
void remove_deint_filter(struct MPContext *mpctx);
|
||||
void set_deinterlacing(struct MPContext *mpctx, bool enable);
|
||||
|
||||
#endif /* MPLAYER_COMMAND_H */
|
||||
@@ -208,7 +208,8 @@ static const char *const backup_properties[] = {
|
||||
"options/speed",
|
||||
"options/edition",
|
||||
"options/pause",
|
||||
"volume-restore-data",
|
||||
"volume",
|
||||
"mute",
|
||||
"options/audio-delay",
|
||||
//"balance",
|
||||
"options/fullscreen",
|
||||
|
||||
@@ -555,5 +555,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, bool enable);
|
||||
|
||||
#endif /* MPLAYER_MP_CORE_H */
|
||||
@@ -418,6 +418,18 @@ int mp_initialize(struct MPContext *mpctx, char **options)
|
||||
if (handle_help_options(mpctx))
|
||||
return -2;
|
||||
|
||||
if (!print_libav_versions(mp_null_log, 0)) {
|
||||
// Using mismatched libraries can be legitimate, but even then it's
|
||||
// a bad idea. We don't acknowledge its usefulness and stability.
|
||||
print_libav_versions(mpctx->log, MSGL_FATAL);
|
||||
MP_FATAL(mpctx, "\nmpv was compiled against a different version of "
|
||||
"FFmpeg/Libav than the shared\nlibrary it is linked against. "
|
||||
"This is most likely a broken build and could\nresult in "
|
||||
"misbehavior and crashes.\n\nmpv does not support this "
|
||||
"configuration and will not run - rebuild mpv instead.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->dump_stats && opts->dump_stats[0]) {
|
||||
if (mp_msg_open_stats_file(mpctx->global, opts->dump_stats) < 0)
|
||||
MP_ERR(mpctx, "Failed to open stats file '%s'\n", opts->dump_stats);
|
||||
|
||||
@@ -129,6 +129,12 @@ static void term_osd_set_status(struct MPContext *mpctx, const char *text)
|
||||
{
|
||||
talloc_free(mpctx->term_osd_status);
|
||||
mpctx->term_osd_status = talloc_strdup(mpctx, text);
|
||||
|
||||
int w = 80, h = 24;
|
||||
terminal_get_size(&w, &h);
|
||||
if (strlen(mpctx->term_osd_status) > w)
|
||||
mpctx->term_osd_status[w] = '\0';
|
||||
|
||||
term_osd_update(mpctx);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
|
||||
if (!mpctx->demuxer->seekable) {
|
||||
MP_ERR(mpctx, "Cannot seek in this file.\n");
|
||||
MP_ERR(mpctx, "You can forcibly enable it with '--force-seeking=yes'.\n");
|
||||
MP_ERR(mpctx, "You can forcibly enable it with '--force-seekable=yes'.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+92
-30
@@ -49,6 +49,8 @@
|
||||
#include "command.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
#define VF_DEINTERLACE_LABEL "deinterlace"
|
||||
|
||||
enum {
|
||||
// update_video() - code also uses: <0 error, 0 eof, >0 progress
|
||||
VD_ERROR = -1,
|
||||
@@ -153,8 +155,45 @@ static int try_filter(struct vo_chain *vo_c, char *name, char *label, char **arg
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool check_output_format(struct vo_chain *vo_c, int imgfmt)
|
||||
{
|
||||
return vo_c->vf->output_params.imgfmt == imgfmt;
|
||||
}
|
||||
|
||||
static int probe_deint_filters(struct vo_chain *vo_c)
|
||||
{
|
||||
// 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;
|
||||
|
||||
if (check_output_format(vo_c, IMGFMT_VDPAU)) {
|
||||
char *args[5] = {"deint", "yes"};
|
||||
int pref = 0;
|
||||
vo_control(vo_c->vo, VOCTRL_GET_PREF_DEINT, &pref);
|
||||
pref = pref < 0 ? -pref : pref;
|
||||
if (pref > 0 && pref <= 4) {
|
||||
const char *types[] =
|
||||
{"", "first-field", "bob", "temporal", "temporal-spatial"};
|
||||
args[2] = "deint-mode";
|
||||
args[3] = (char *)types[pref];
|
||||
}
|
||||
|
||||
return try_filter(vo_c, "vdpaupp", VF_DEINTERLACE_LABEL, args);
|
||||
}
|
||||
if (check_output_format(vo_c, IMGFMT_VAAPI))
|
||||
return try_filter(vo_c, "vavpp", VF_DEINTERLACE_LABEL, NULL);
|
||||
if (check_output_format(vo_c, IMGFMT_D3D11VA) ||
|
||||
check_output_format(vo_c, IMGFMT_D3D11NV12))
|
||||
return try_filter(vo_c, "d3d11vpp", VF_DEINTERLACE_LABEL, NULL);
|
||||
return try_filter(vo_c, "yadif", VF_DEINTERLACE_LABEL, NULL);
|
||||
}
|
||||
|
||||
// Reconfigure the filter chain according to the new input format.
|
||||
static void filter_reconfig(struct vo_chain *vo_c)
|
||||
static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
|
||||
{
|
||||
struct mp_image_params params = vo_c->input_format;
|
||||
if (!params.imgfmt)
|
||||
@@ -162,19 +201,21 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
|
||||
set_allowed_vo_formats(vo_c);
|
||||
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
|
||||
char *filters[] = {"autorotate", "autostereo3d", NULL};
|
||||
char *filters[] = {"autorotate", "autostereo3d", "deinterlace", NULL};
|
||||
for (int n = 0; filters[n]; n++) {
|
||||
struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]);
|
||||
if (vf) {
|
||||
if (vf)
|
||||
vf_remove_filter(vo_c->vf, vf);
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (vo_c->vf->initialized < 1) {
|
||||
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to reset this even if runtime deint switching is used.
|
||||
video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
|
||||
|
||||
if (params.rotate && (params.rotate % 90 == 0)) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) {
|
||||
// Try to insert a rotation filter.
|
||||
@@ -194,6 +235,42 @@ static void filter_reconfig(struct vo_chain *vo_c)
|
||||
MP_ERR(vo_c, "Can't insert 3D conversion filter.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->opts->deinterlace == 1)
|
||||
probe_deint_filters(vo_c);
|
||||
}
|
||||
|
||||
static void recreate_auto_filters(struct MPContext *mpctx)
|
||||
{
|
||||
filter_reconfig(mpctx, mpctx->vo_chain);
|
||||
|
||||
mp_force_video_refresh(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 (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
|
||||
enabled = -1;
|
||||
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, bool enable)
|
||||
{
|
||||
if (enable == (get_deinterlacing(mpctx) > 0))
|
||||
return;
|
||||
|
||||
mpctx->opts->deinterlace = enable;
|
||||
recreate_auto_filters(mpctx);
|
||||
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
|
||||
}
|
||||
|
||||
static void recreate_video_filters(struct MPContext *mpctx)
|
||||
@@ -230,7 +307,7 @@ int reinit_video_filters(struct MPContext *mpctx)
|
||||
recreate_video_filters(mpctx);
|
||||
|
||||
if (need_reconfig)
|
||||
filter_reconfig(vo_c);
|
||||
filter_reconfig(mpctx, vo_c);
|
||||
|
||||
mp_force_video_refresh(mpctx);
|
||||
|
||||
@@ -314,7 +391,6 @@ void uninit_video_chain(struct MPContext *mpctx)
|
||||
|
||||
mpctx->video_status = STATUS_EOF;
|
||||
|
||||
remove_deint_filter(mpctx);
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
}
|
||||
}
|
||||
@@ -522,22 +598,6 @@ static int decode_image(struct MPContext *mpctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Called after video reinit. This can be generally used to try to insert more
|
||||
// filters using the filter chain edit functionality in command.c.
|
||||
static void init_filter_params(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
// Note that the filter chain is already initialized. This code might
|
||||
// recreate the chain a second time, which is not very elegant, but allows
|
||||
// us to test whether enabling deinterlacing works with the current video
|
||||
// format and other filters.
|
||||
if (opts->deinterlace >= 0) {
|
||||
remove_deint_filter(mpctx);
|
||||
set_deinterlacing(mpctx, opts->deinterlace != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Feed newly decoded frames to the filter, take care of format changes.
|
||||
// If eof=true, drain the filter chain, and return VD_EOF if empty.
|
||||
static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
@@ -564,7 +624,8 @@ static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
return VD_PROGRESS;
|
||||
|
||||
// The filter chain is drained; execute the filter format change.
|
||||
filter_reconfig(mpctx->vo_chain);
|
||||
vf->initialized = 0;
|
||||
filter_reconfig(mpctx, mpctx->vo_chain);
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
|
||||
|
||||
@@ -586,7 +647,6 @@ static int video_filter(struct MPContext *mpctx, bool eof)
|
||||
MP_FATAL(mpctx, "Cannot initialize video filters.\n");
|
||||
return VD_ERROR;
|
||||
}
|
||||
init_filter_params(mpctx);
|
||||
return VD_RECONFIG;
|
||||
}
|
||||
|
||||
@@ -1360,11 +1420,13 @@ void write_video(struct MPContext *mpctx)
|
||||
};
|
||||
calculate_frame_duration(mpctx);
|
||||
|
||||
int req = vo_get_num_req_frames(mpctx->video_out);
|
||||
assert(req >= 1 && req <= VO_MAX_REQ_FRAMES);
|
||||
struct vo_frame dummy = {
|
||||
.pts = pts,
|
||||
.duration = -1,
|
||||
.still = mpctx->step_frames > 0,
|
||||
.num_frames = MPMIN(mpctx->num_next_frames, VO_MAX_REQ_FRAMES),
|
||||
.num_frames = MPMIN(mpctx->num_next_frames, req),
|
||||
.num_vsyncs = 1,
|
||||
};
|
||||
for (int n = 0; n < dummy.num_frames; n++)
|
||||
|
||||
+1
-1
@@ -1352,7 +1352,7 @@ static int start(priv_t *priv)
|
||||
if (priv->map[i].buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC)
|
||||
MP_WARN(priv, "compiled without clock_gettime() that is needed to handle monotone video timestamps from the kernel. Expect desync.\n");
|
||||
#endif
|
||||
/* count up to make sure this is correct everytime */
|
||||
/* count up to make sure this is correct every time */
|
||||
priv->mapcount++;
|
||||
|
||||
if (v4l2_ioctl(priv->video_fd, VIDIOC_QBUF, &(priv->map[i].buf)) < 0) {
|
||||
|
||||
+228
-31
@@ -32,9 +32,12 @@
|
||||
#include "common/msg.h"
|
||||
#include "options/path.h"
|
||||
#include "ass_mp.h"
|
||||
#include "img_convert.h"
|
||||
#include "osd.h"
|
||||
#include "stream/stream.h"
|
||||
#include "options/options.h"
|
||||
#include "video/out/bitmap_packer.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
// res_y should be track->PlayResY
|
||||
// It determines scaling of font sizes and more.
|
||||
@@ -97,37 +100,6 @@ void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts,
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time,
|
||||
struct sub_bitmaps *res)
|
||||
{
|
||||
int changed;
|
||||
ASS_Image *imgs = ass_render_frame(renderer, track, time, &changed);
|
||||
if (changed)
|
||||
res->change_id++;
|
||||
assert(res->format == 0 || res->format == SUBBITMAP_LIBASS);
|
||||
res->format = SUBBITMAP_LIBASS;
|
||||
|
||||
int num_parts_alloc = MP_TALLOC_AVAIL(res->parts);
|
||||
for (struct ass_image *img = imgs; img; img = img->next) {
|
||||
if (img->w == 0 || img->h == 0)
|
||||
continue;
|
||||
if (res->num_parts >= num_parts_alloc) {
|
||||
num_parts_alloc = MPMAX(num_parts_alloc * 2, 32);
|
||||
res->parts = talloc_realloc(NULL, res->parts, struct sub_bitmap,
|
||||
num_parts_alloc);
|
||||
}
|
||||
struct sub_bitmap *p = &res->parts[res->num_parts];
|
||||
p->bitmap = img->bitmap;
|
||||
p->stride = img->stride;
|
||||
p->libass.color = img->color;
|
||||
p->dw = p->w = img->w;
|
||||
p->dh = p->h = img->h;
|
||||
p->x = img->dst_x;
|
||||
p->y = img->dst_y;
|
||||
res->num_parts++;
|
||||
}
|
||||
}
|
||||
|
||||
static const int map_ass_level[] = {
|
||||
MSGL_ERR, // 0 "FATAL errors"
|
||||
MSGL_WARN,
|
||||
@@ -177,3 +149,228 @@ void mp_ass_flush_old_events(ASS_Track *track, long long ts)
|
||||
track->events[i] = track->events[i+n];
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_ass_rgba(unsigned char *src, int src_w, int src_h,
|
||||
int src_stride, unsigned char *dst, size_t dst_stride,
|
||||
int dst_x, int dst_y, uint32_t color)
|
||||
{
|
||||
const unsigned int r = (color >> 24) & 0xff;
|
||||
const unsigned int g = (color >> 16) & 0xff;
|
||||
const unsigned int b = (color >> 8) & 0xff;
|
||||
const unsigned int a = 0xff - (color & 0xff);
|
||||
|
||||
dst += dst_y * dst_stride + dst_x * 4;
|
||||
|
||||
for (int y = 0; y < src_h; y++, dst += dst_stride, src += src_stride) {
|
||||
uint32_t *dstrow = (uint32_t *) dst;
|
||||
for (int x = 0; x < src_w; x++) {
|
||||
const unsigned int v = src[x];
|
||||
int rr = (r * a * v);
|
||||
int gg = (g * a * v);
|
||||
int bb = (b * a * v);
|
||||
int aa = a * v;
|
||||
uint32_t dstpix = dstrow[x];
|
||||
unsigned int dstb = dstpix & 0xFF;
|
||||
unsigned int dstg = (dstpix >> 8) & 0xFF;
|
||||
unsigned int dstr = (dstpix >> 16) & 0xFF;
|
||||
unsigned int dsta = (dstpix >> 24) & 0xFF;
|
||||
dstb = (bb + dstb * (255 * 255 - aa)) / (255 * 255);
|
||||
dstg = (gg + dstg * (255 * 255 - aa)) / (255 * 255);
|
||||
dstr = (rr + dstr * (255 * 255 - aa)) / (255 * 255);
|
||||
dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255);
|
||||
dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_ass_packer {
|
||||
struct sub_bitmap *cached_parts; // only for the array memory
|
||||
struct mp_image *cached_img;
|
||||
struct sub_bitmaps cached_subs;
|
||||
bool cached_subs_valid;
|
||||
struct sub_bitmap rgba_imgs[MP_SUB_BB_LIST_MAX];
|
||||
struct bitmap_packer *packer;
|
||||
};
|
||||
|
||||
// Free with talloc_free().
|
||||
struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent)
|
||||
{
|
||||
struct mp_ass_packer *p = talloc_zero(ta_parent, struct mp_ass_packer);
|
||||
p->packer = talloc_zero(p, struct bitmap_packer);
|
||||
return p;
|
||||
}
|
||||
|
||||
static bool pack(struct mp_ass_packer *p, struct sub_bitmaps *res, int imgfmt)
|
||||
{
|
||||
packer_set_size(p->packer, res->num_parts);
|
||||
|
||||
for (int n = 0; n < res->num_parts; n++)
|
||||
p->packer->in[n] = (struct pos){res->parts[n].w, res->parts[n].h};
|
||||
|
||||
if (p->packer->count == 0 || packer_pack(p->packer) < 0)
|
||||
return false;
|
||||
|
||||
struct pos bb[2];
|
||||
packer_get_bb(p->packer, bb);
|
||||
|
||||
res->packed_w = bb[1].x;
|
||||
res->packed_h = bb[1].y;
|
||||
|
||||
if (!p->cached_img || p->cached_img->w < res->packed_w ||
|
||||
p->cached_img->h < res->packed_h)
|
||||
{
|
||||
talloc_free(p->cached_img);
|
||||
p->cached_img = mp_image_alloc(imgfmt, p->packer->w, p->packer->h);
|
||||
if (!p->cached_img)
|
||||
return false;
|
||||
talloc_steal(p, p->cached_img);
|
||||
}
|
||||
|
||||
res->packed = p->cached_img;
|
||||
|
||||
for (int n = 0; n < res->num_parts; n++) {
|
||||
struct sub_bitmap *b = &res->parts[n];
|
||||
struct pos pos = p->packer->result[n];
|
||||
|
||||
b->src_x = pos.x;
|
||||
b->src_y = pos.y;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pack_libass(struct mp_ass_packer *p, struct sub_bitmaps *res)
|
||||
{
|
||||
if (!pack(p, res, IMGFMT_Y8))
|
||||
return false;
|
||||
|
||||
for (int n = 0; n < res->num_parts; n++) {
|
||||
struct sub_bitmap *b = &res->parts[n];
|
||||
|
||||
int stride = res->packed->stride[0];
|
||||
void *pdata =
|
||||
(uint8_t *)res->packed->planes[0] + b->src_y * stride + b->src_x;
|
||||
memcpy_pic(pdata, b->bitmap, b->w, b->h, stride, b->stride);
|
||||
|
||||
b->bitmap = pdata;
|
||||
b->stride = stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pack_rgba(struct mp_ass_packer *p, struct sub_bitmaps *res)
|
||||
{
|
||||
struct mp_rect bb_list[MP_SUB_BB_LIST_MAX];
|
||||
int num_bb = mp_get_sub_bb_list(res, bb_list, MP_SUB_BB_LIST_MAX);
|
||||
|
||||
struct sub_bitmaps imgs = {
|
||||
.change_id = res->change_id,
|
||||
.format = SUBBITMAP_RGBA,
|
||||
.parts = p->rgba_imgs,
|
||||
.num_parts = num_bb,
|
||||
};
|
||||
|
||||
for (int n = 0; n < imgs.num_parts; n++) {
|
||||
imgs.parts[n].w = bb_list[n].x1 - bb_list[n].x0;
|
||||
imgs.parts[n].h = bb_list[n].y1 - bb_list[n].y0;
|
||||
}
|
||||
|
||||
if (!pack(p, &imgs, IMGFMT_BGRA))
|
||||
return false;
|
||||
|
||||
for (int n = 0; n < num_bb; n++) {
|
||||
struct mp_rect bb = bb_list[n];
|
||||
struct sub_bitmap *b = &imgs.parts[n];
|
||||
|
||||
b->x = bb.x0;
|
||||
b->y = bb.y0;
|
||||
b->w = b->dw = bb.x1 - bb.x0;
|
||||
b->h = b->dh = bb.y1 - bb.y0;
|
||||
b->stride = imgs.packed->stride[0];
|
||||
b->bitmap = (uint8_t *)imgs.packed->planes[0] +
|
||||
b->stride * b->src_y + b->src_x * 4;
|
||||
|
||||
memset_pic(b->bitmap, 0, b->w * 4, b->h, b->stride);
|
||||
|
||||
for (int i = 0; i < res->num_parts; i++) {
|
||||
struct sub_bitmap *s = &res->parts[i];
|
||||
|
||||
// Assume mp_get_sub_bb_list() never splits sub bitmaps
|
||||
// So we don't clip/adjust the size of the sub bitmap
|
||||
if (s->x > bb.x1 || s->x + s->w < bb.x0 ||
|
||||
s->y > bb.y1 || s->y + s->h < bb.y0)
|
||||
continue;
|
||||
|
||||
draw_ass_rgba(s->bitmap, s->w, s->h, s->stride,
|
||||
b->bitmap, b->stride,
|
||||
s->x - bb.x0, s->y - bb.y0,
|
||||
s->libass.color);
|
||||
}
|
||||
}
|
||||
|
||||
*res = imgs;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pack the contents of image_lists[0] to image_lists[num_image_lists-1] into
|
||||
// a single image, and make *out point to it. *out is completely overwritten.
|
||||
// If libass reported any change, image_lists_changed must be set (it then
|
||||
// repacks all images). preferred_osd_format can be set to a desired
|
||||
// sub_bitmap_format. Currently, only SUBBITMAP_LIBASS is supported.
|
||||
void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists,
|
||||
int num_image_lists, bool image_lists_changed,
|
||||
int preferred_osd_format, struct sub_bitmaps *out)
|
||||
{
|
||||
int format = preferred_osd_format == SUBBITMAP_RGBA ? SUBBITMAP_RGBA
|
||||
: SUBBITMAP_LIBASS;
|
||||
|
||||
if (p->cached_subs_valid && !image_lists_changed &&
|
||||
p->cached_subs.format == format)
|
||||
{
|
||||
*out = p->cached_subs;
|
||||
return;
|
||||
}
|
||||
|
||||
*out = (struct sub_bitmaps){.change_id = 1};
|
||||
p->cached_subs_valid = false;
|
||||
|
||||
struct sub_bitmaps res = {
|
||||
.change_id = image_lists_changed,
|
||||
.format = SUBBITMAP_LIBASS,
|
||||
.parts = p->cached_parts,
|
||||
};
|
||||
|
||||
for (int n = 0; n < num_image_lists; n++) {
|
||||
for (struct ass_image *img = image_lists[n]; img; img = img->next) {
|
||||
if (img->w == 0 || img->h == 0)
|
||||
continue;
|
||||
MP_TARRAY_GROW(p, p->cached_parts, res.num_parts);
|
||||
res.parts = p->cached_parts;
|
||||
struct sub_bitmap *b = &res.parts[res.num_parts];
|
||||
b->bitmap = img->bitmap;
|
||||
b->stride = img->stride;
|
||||
b->libass.color = img->color;
|
||||
b->dw = b->w = img->w;
|
||||
b->dh = b->h = img->h;
|
||||
b->x = img->dst_x;
|
||||
b->y = img->dst_y;
|
||||
res.num_parts++;
|
||||
}
|
||||
}
|
||||
|
||||
bool r = false;
|
||||
if (format == SUBBITMAP_RGBA) {
|
||||
r = pack_rgba(p, &res);
|
||||
} else {
|
||||
r = pack_libass(p, &res);
|
||||
}
|
||||
|
||||
if (!r)
|
||||
return;
|
||||
|
||||
*out = res;
|
||||
p->cached_subs = res;
|
||||
p->cached_subs.change_id = 0;
|
||||
p->cached_subs_valid = true;
|
||||
}
|
||||
+5
-3
@@ -49,9 +49,11 @@ void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts,
|
||||
struct mpv_global *global, struct mp_log *log);
|
||||
ASS_Library *mp_ass_init(struct mpv_global *global, struct mp_log *log);
|
||||
|
||||
struct sub_bitmap;
|
||||
struct sub_bitmaps;
|
||||
void mp_ass_render_frame(ASS_Renderer *renderer, ASS_Track *track, double time,
|
||||
struct sub_bitmaps *res);
|
||||
struct mp_ass_packer;
|
||||
struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent);
|
||||
void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists,
|
||||
int num_image_lists, bool changed,
|
||||
int preferred_osd_format, struct sub_bitmaps *out);
|
||||
|
||||
#endif /* MPLAYER_ASS_MP_H */
|
||||
+3
-3
@@ -255,8 +255,8 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
|
||||
// You must call sub_lock/sub_unlock if more than 1 thread access sub.
|
||||
// The issue is that *res will contain decoder allocated data, which might
|
||||
// be deallocated on the next decoder access.
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res)
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format,
|
||||
double pts, struct sub_bitmaps *res)
|
||||
{
|
||||
struct MPOpts *opts = sub->opts;
|
||||
|
||||
@@ -267,7 +267,7 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
return;
|
||||
|
||||
if (opts->sub_visibility && sub->sd->driver->get_bitmaps)
|
||||
sub->sd->driver->get_bitmaps(sub->sd, dim, pts, res);
|
||||
sub->sd->driver->get_bitmaps(sub->sd, dim, format, pts, res);
|
||||
}
|
||||
|
||||
// See sub_get_bitmaps() for locking requirements.
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ void sub_unlock(struct dec_sub *sub);
|
||||
bool sub_can_preload(struct dec_sub *sub);
|
||||
void sub_preload(struct dec_sub *sub);
|
||||
bool sub_read_packets(struct dec_sub *sub, double video_pts);
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res);
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format,
|
||||
double pts, struct sub_bitmaps *res);
|
||||
char *sub_get_text(struct dec_sub *sub, double pts);
|
||||
void sub_reset(struct dec_sub *sub);
|
||||
void sub_select(struct dec_sub *sub, bool selected);
|
||||
|
||||
+7
-10
@@ -193,8 +193,7 @@ static void scale_sb_rgba(struct sub_bitmap *sb, struct mp_image *dst_format,
|
||||
mp_image_swscale(sbisrc2, &sbisrc, SWS_BILINEAR);
|
||||
unpremultiply_and_split_BGR32(sbisrc2, sba);
|
||||
|
||||
sbi->params.colorspace = dst_format->params.colorspace;
|
||||
sbi->params.colorlevels = dst_format->params.colorlevels;
|
||||
sbi->params.color = dst_format->params.color;
|
||||
mp_image_swscale(sbi, sbisrc2, SWS_BILINEAR);
|
||||
|
||||
talloc_free(sbisrc2);
|
||||
@@ -367,8 +366,8 @@ static struct part *get_cache(struct mp_draw_sub_cache *cache,
|
||||
if (part) {
|
||||
if (part->change_id != sbs->change_id
|
||||
|| part->imgfmt != format->imgfmt
|
||||
|| part->colorspace != format->params.colorspace
|
||||
|| part->levels != format->params.colorlevels)
|
||||
|| part->colorspace != format->params.color.space
|
||||
|| part->levels != format->params.color.levels)
|
||||
{
|
||||
talloc_free(part);
|
||||
part = NULL;
|
||||
@@ -380,8 +379,8 @@ static struct part *get_cache(struct mp_draw_sub_cache *cache,
|
||||
.change_id = sbs->change_id,
|
||||
.num_imgs = sbs->num_parts,
|
||||
.imgfmt = format->imgfmt,
|
||||
.levels = format->params.colorlevels,
|
||||
.colorspace = format->params.colorspace,
|
||||
.levels = format->params.color.levels,
|
||||
.colorspace = format->params.color.space,
|
||||
};
|
||||
part->imgs = talloc_zero_array(part, struct sub_cache,
|
||||
part->num_imgs);
|
||||
@@ -436,10 +435,8 @@ static struct mp_image *chroma_up(struct mp_draw_sub_cache *cache, int imgfmt,
|
||||
|
||||
// The temp image is always YUV, but src not necessarily.
|
||||
// Reduce amount of conversions in YUV case (upsampling/shifting only)
|
||||
if (src->fmt.flags & MP_IMGFLAG_YUV) {
|
||||
temp->params.colorspace = src->params.colorspace;
|
||||
temp->params.colorlevels = src->params.colorlevels;
|
||||
}
|
||||
if (src->fmt.flags & MP_IMGFLAG_YUV)
|
||||
temp->params.color = src->params.color;
|
||||
|
||||
if (src->imgfmt == IMGFMT_420P) {
|
||||
assert(imgfmt == IMGFMT_444P);
|
||||
|
||||
@@ -30,17 +30,6 @@
|
||||
#include "video/mp_image.h"
|
||||
#include "video/sws_utils.h"
|
||||
|
||||
struct osd_conv_cache {
|
||||
struct sub_bitmap part[MP_SUB_BB_LIST_MAX];
|
||||
struct sub_bitmap *parts;
|
||||
void *scratch;
|
||||
};
|
||||
|
||||
struct osd_conv_cache *osd_conv_cache_new(void)
|
||||
{
|
||||
return talloc_zero(NULL, struct osd_conv_cache);
|
||||
}
|
||||
|
||||
void mp_blur_rgba_sub_bitmap(struct sub_bitmap *d, double gblur)
|
||||
{
|
||||
struct mp_image *tmp1 = mp_image_alloc(IMGFMT_BGRA, d->w, d->h);
|
||||
@@ -58,154 +47,6 @@ void mp_blur_rgba_sub_bitmap(struct sub_bitmap *d, double gblur)
|
||||
talloc_free(tmp1);
|
||||
}
|
||||
|
||||
// If RGBA parts need scaling, scale them.
|
||||
bool osd_scale_rgba(struct osd_conv_cache *c, struct sub_bitmaps *imgs)
|
||||
{
|
||||
struct sub_bitmaps src = *imgs;
|
||||
if (src.format != SUBBITMAP_RGBA)
|
||||
return false;
|
||||
|
||||
bool need_scale = false;
|
||||
for (int n = 0; n < src.num_parts; n++) {
|
||||
struct sub_bitmap *sb = &src.parts[n];
|
||||
if (sb->w != sb->dw || sb->h != sb->dh)
|
||||
need_scale = true;
|
||||
}
|
||||
if (!need_scale)
|
||||
return false;
|
||||
|
||||
talloc_free(c->parts);
|
||||
imgs->parts = c->parts = talloc_array(c, struct sub_bitmap, src.num_parts);
|
||||
imgs->packed = NULL;
|
||||
|
||||
// Note: we scale all parts, since most likely all need scaling anyway, and
|
||||
// to get a proper copy of all data in the imgs list.
|
||||
for (int n = 0; n < src.num_parts; n++) {
|
||||
struct sub_bitmap *d = &imgs->parts[n];
|
||||
struct sub_bitmap *s = &src.parts[n];
|
||||
|
||||
struct mp_image src_image = {0};
|
||||
mp_image_setfmt(&src_image, IMGFMT_BGRA);
|
||||
mp_image_set_size(&src_image, s->w, s->h);
|
||||
src_image.planes[0] = s->bitmap;
|
||||
src_image.stride[0] = s->stride;
|
||||
|
||||
d->x = s->x;
|
||||
d->y = s->y;
|
||||
d->w = d->dw = s->dw;
|
||||
d->h = d->dh = s->dh;
|
||||
struct mp_image *image = mp_image_alloc(IMGFMT_BGRA, d->w, d->h);
|
||||
talloc_steal(c->parts, image);
|
||||
if (image) {
|
||||
d->stride = image->stride[0];
|
||||
d->bitmap = image->planes[0];
|
||||
|
||||
mp_image_swscale(image, &src_image, mp_sws_fast_flags);
|
||||
} else {
|
||||
// on OOM, skip the region; just don't scale it
|
||||
*d = *s;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_ass_rgba(unsigned char *src, int src_w, int src_h,
|
||||
int src_stride, unsigned char *dst, size_t dst_stride,
|
||||
int dst_x, int dst_y, uint32_t color)
|
||||
{
|
||||
const unsigned int r = (color >> 24) & 0xff;
|
||||
const unsigned int g = (color >> 16) & 0xff;
|
||||
const unsigned int b = (color >> 8) & 0xff;
|
||||
const unsigned int a = 0xff - (color & 0xff);
|
||||
|
||||
dst += dst_y * dst_stride + dst_x * 4;
|
||||
|
||||
for (int y = 0; y < src_h; y++, dst += dst_stride, src += src_stride) {
|
||||
uint32_t *dstrow = (uint32_t *) dst;
|
||||
for (int x = 0; x < src_w; x++) {
|
||||
const unsigned int v = src[x];
|
||||
int rr = (r * a * v);
|
||||
int gg = (g * a * v);
|
||||
int bb = (b * a * v);
|
||||
int aa = a * v;
|
||||
uint32_t dstpix = dstrow[x];
|
||||
unsigned int dstb = dstpix & 0xFF;
|
||||
unsigned int dstg = (dstpix >> 8) & 0xFF;
|
||||
unsigned int dstr = (dstpix >> 16) & 0xFF;
|
||||
unsigned int dsta = (dstpix >> 24) & 0xFF;
|
||||
dstb = (bb + dstb * (255 * 255 - aa)) / (255 * 255);
|
||||
dstg = (gg + dstg * (255 * 255 - aa)) / (255 * 255);
|
||||
dstr = (rr + dstr * (255 * 255 - aa)) / (255 * 255);
|
||||
dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255);
|
||||
dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_conv_ass_to_rgba(struct osd_conv_cache *c, struct sub_bitmaps *imgs)
|
||||
{
|
||||
struct sub_bitmaps src = *imgs;
|
||||
if (src.format != SUBBITMAP_LIBASS)
|
||||
return false;
|
||||
assert(!src.scaled); // ASS is always unscaled
|
||||
|
||||
struct mp_rect bb_list[MP_SUB_BB_LIST_MAX];
|
||||
int num_bb = mp_get_sub_bb_list(&src, bb_list, MP_SUB_BB_LIST_MAX);
|
||||
|
||||
imgs->format = SUBBITMAP_RGBA;
|
||||
imgs->parts = c->part;
|
||||
imgs->num_parts = num_bb;
|
||||
imgs->packed = NULL;
|
||||
|
||||
size_t newsize = 0;
|
||||
for (int n = 0; n < num_bb; n++) {
|
||||
struct mp_rect bb = bb_list[n];
|
||||
int w = bb.x1 - bb.x0;
|
||||
int h = bb.y1 - bb.y0;
|
||||
int stride = w * 4;
|
||||
newsize += h * stride;
|
||||
}
|
||||
|
||||
if (talloc_get_size(c->scratch) < newsize) {
|
||||
talloc_free(c->scratch);
|
||||
c->scratch = talloc_array(c, uint8_t, newsize);
|
||||
}
|
||||
|
||||
uint8_t *data = c->scratch;
|
||||
|
||||
for (int n = 0; n < num_bb; n++) {
|
||||
struct mp_rect bb = bb_list[n];
|
||||
struct sub_bitmap *bmp = &c->part[n];
|
||||
|
||||
bmp->x = bb.x0;
|
||||
bmp->y = bb.y0;
|
||||
bmp->w = bmp->dw = bb.x1 - bb.x0;
|
||||
bmp->h = bmp->dh = bb.y1 - bb.y0;
|
||||
bmp->stride = bmp->w * 4;
|
||||
bmp->bitmap = data;
|
||||
data += bmp->h * bmp->stride;
|
||||
|
||||
memset_pic(bmp->bitmap, 0, bmp->w * 4, bmp->h, bmp->stride);
|
||||
|
||||
for (int p = 0; p < src.num_parts; p++) {
|
||||
struct sub_bitmap *s = &src.parts[p];
|
||||
|
||||
// Assume mp_get_sub_bb_list() never splits sub bitmaps
|
||||
// So we don't clip/adjust the size of the sub bitmap
|
||||
if (s->x > bb.x1 || s->x + s->w < bb.x0 ||
|
||||
s->y > bb.y1 || s->y + s->h < bb.y0)
|
||||
continue;
|
||||
|
||||
draw_ass_rgba(s->bitmap, s->w, s->h, s->stride,
|
||||
bmp->bitmap, bmp->stride,
|
||||
s->x - bb.x0, s->y - bb.y0,
|
||||
s->libass.color);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_sub_bitmaps_bb(struct sub_bitmaps *imgs, struct mp_rect *out_bb)
|
||||
{
|
||||
struct mp_rect bb = {INT_MAX, INT_MAX, INT_MIN, INT_MIN};
|
||||
|
||||
@@ -3,19 +3,12 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct osd_conv_cache;
|
||||
struct sub_bitmaps;
|
||||
struct sub_bitmap;
|
||||
struct mp_rect;
|
||||
|
||||
struct osd_conv_cache *osd_conv_cache_new(void);
|
||||
|
||||
// These functions convert from one OSD format to another. On success, they copy
|
||||
// the converted image data into c, and change imgs to point to the data.
|
||||
bool osd_conv_ass_to_rgba(struct osd_conv_cache *c, struct sub_bitmaps *imgs);
|
||||
// Sub postprocessing
|
||||
void mp_blur_rgba_sub_bitmap(struct sub_bitmap *d, double gblur);
|
||||
bool osd_scale_rgba(struct osd_conv_cache *c, struct sub_bitmaps *imgs);
|
||||
|
||||
bool mp_sub_bitmaps_bb(struct sub_bitmaps *imgs, struct mp_rect *out_bb);
|
||||
|
||||
|
||||
@@ -127,8 +127,6 @@ struct osd_state *osd_create(struct mpv_global *global)
|
||||
.text = talloc_strdup(obj, ""),
|
||||
.progbar_state = {.type = -1},
|
||||
};
|
||||
for (int i = 0; i < OSD_CONV_CACHE_MAX; i++)
|
||||
obj->cache[i] = talloc_steal(obj, osd_conv_cache_new());
|
||||
osd->objs[n] = obj;
|
||||
}
|
||||
|
||||
@@ -247,10 +245,9 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
{
|
||||
struct MPOpts *opts = osd->opts;
|
||||
|
||||
bool formats[SUBBITMAP_COUNT];
|
||||
memcpy(formats, sub_formats, sizeof(formats));
|
||||
if (opts->force_rgba_osd)
|
||||
formats[SUBBITMAP_LIBASS] = false;
|
||||
int format = SUBBITMAP_LIBASS;
|
||||
if (!sub_formats[format] || opts->force_rgba_osd)
|
||||
format = SUBBITMAP_RGBA;
|
||||
|
||||
*out_imgs = (struct sub_bitmaps) {0};
|
||||
|
||||
@@ -261,7 +258,7 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
double sub_pts = video_pts;
|
||||
if (sub_pts != MP_NOPTS_VALUE)
|
||||
sub_pts -= opts->sub_delay;
|
||||
sub_get_bitmaps(obj->sub, obj->vo_res, sub_pts, out_imgs);
|
||||
sub_get_bitmaps(obj->sub, obj->vo_res, format, sub_pts, out_imgs);
|
||||
}
|
||||
} else if (obj->type == OSDTYPE_EXTERNAL2) {
|
||||
if (obj->external2 && obj->external2->format) {
|
||||
@@ -269,7 +266,7 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
obj->external2->change_id = 0;
|
||||
}
|
||||
} else {
|
||||
osd_object_get_bitmaps(osd, obj, out_imgs);
|
||||
osd_object_get_bitmaps(osd, obj, format, out_imgs);
|
||||
}
|
||||
|
||||
if (obj->force_redraw)
|
||||
@@ -281,25 +278,8 @@ static void render_object(struct osd_state *osd, struct osd_object *obj,
|
||||
if (out_imgs->num_parts == 0)
|
||||
return;
|
||||
|
||||
if (obj->cached.change_id == obj->vo_change_id && formats[obj->cached.format])
|
||||
{
|
||||
*out_imgs = obj->cached;
|
||||
return;
|
||||
}
|
||||
|
||||
out_imgs->render_index = obj->type;
|
||||
out_imgs->change_id = obj->vo_change_id;
|
||||
|
||||
if (formats[out_imgs->format])
|
||||
return;
|
||||
|
||||
bool cached = false; // do we have a copy of all the image data?
|
||||
|
||||
if (formats[SUBBITMAP_RGBA] && out_imgs->format == SUBBITMAP_LIBASS)
|
||||
cached |= osd_conv_ass_to_rgba(obj->cache[3], out_imgs);
|
||||
|
||||
if (cached)
|
||||
obj->cached = *out_imgs;
|
||||
}
|
||||
|
||||
// draw_flags is a bit field of OSD_DRAW_* constants
|
||||
@@ -456,5 +436,4 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h,
|
||||
bi->dw = (int)(bi->w * xscale + 0.5);
|
||||
bi->dh = (int)(bi->h * yscale + 0.5);
|
||||
}
|
||||
imgs->scaled = xscale != 1 || yscale != 1;
|
||||
}
|
||||
@@ -58,10 +58,6 @@ struct sub_bitmaps {
|
||||
|
||||
enum sub_bitmap_format format;
|
||||
|
||||
// If false, dw==w && dh==h.
|
||||
// SUBBITMAP_LIBASS is never scaled.
|
||||
bool scaled;
|
||||
|
||||
struct sub_bitmap *parts;
|
||||
int num_parts;
|
||||
|
||||
@@ -69,6 +65,7 @@ struct sub_bitmaps {
|
||||
// parts[].bitmap pointer points into the image data here (and stride will
|
||||
// correspond to packed->stride[0]).
|
||||
// SUBBITMAP_RGBA: IMGFMT_BGRA (exact match)
|
||||
// SUBBITMAP_LIBASS: IMGFMT_Y8 (not the same, but compatible layout)
|
||||
// Other formats have this set to NULL.
|
||||
struct mp_image *packed;
|
||||
|
||||
@@ -204,7 +201,7 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h,
|
||||
|
||||
// internal use only
|
||||
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
struct sub_bitmaps *out_imgs);
|
||||
int format, struct sub_bitmaps *out_imgs);
|
||||
void osd_init_backend(struct osd_state *osd);
|
||||
void osd_destroy_backend(struct osd_state *osd);
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function)
|
||||
}
|
||||
|
||||
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
struct sub_bitmaps *out_imgs)
|
||||
int format, struct sub_bitmaps *out_imgs)
|
||||
{
|
||||
*out_imgs = (struct sub_bitmaps) {0};
|
||||
}
|
||||
|
||||
+23
-12
@@ -90,7 +90,6 @@ void osd_destroy_backend(struct osd_state *osd)
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
||||
struct osd_object *obj = osd->objs[n];
|
||||
destroy_ass_renderer(&obj->ass);
|
||||
talloc_free(obj->parts_cache.parts);
|
||||
for (int i = 0; i < obj->num_externals; i++)
|
||||
destroy_external(&obj->externals[i]);
|
||||
obj->num_externals = 0;
|
||||
@@ -501,7 +500,7 @@ void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
|
||||
entry->res_x = res_x;
|
||||
entry->res_y = res_y;
|
||||
update_external(osd, obj, entry);
|
||||
obj->parts_cache.change_id = 1;
|
||||
obj->changed = true;
|
||||
osd_changed_unlocked(osd, obj->type);
|
||||
}
|
||||
|
||||
@@ -510,28 +509,40 @@ done:
|
||||
}
|
||||
|
||||
static void append_ass(struct ass_state *ass, struct mp_osd_res *res,
|
||||
struct sub_bitmaps *imgs)
|
||||
ASS_Image **img_list, bool *changed)
|
||||
{
|
||||
if (!ass->render || !ass->track)
|
||||
if (!ass->render || !ass->track) {
|
||||
*img_list = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
ass_set_frame_size(ass->render, res->w, res->h);
|
||||
ass_set_aspect_ratio(ass->render, res->display_par, 1.0);
|
||||
mp_ass_render_frame(ass->render, ass->track, 0, imgs);
|
||||
|
||||
int ass_changed;
|
||||
*img_list = ass_render_frame(ass->render, ass->track, 0, &ass_changed);
|
||||
*changed |= ass_changed;
|
||||
}
|
||||
|
||||
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
||||
struct sub_bitmaps *out_imgs)
|
||||
int format, struct sub_bitmaps *out_imgs)
|
||||
{
|
||||
if (obj->force_redraw && obj->type == OSDTYPE_OSD)
|
||||
update_osd(osd, obj);
|
||||
|
||||
append_ass(&obj->ass, &obj->vo_res, &obj->parts_cache);
|
||||
for (int n = 0; n < obj->num_externals; n++)
|
||||
append_ass(&obj->externals[n].ass, &obj->vo_res, &obj->parts_cache);
|
||||
if (!obj->ass_packer)
|
||||
obj->ass_packer = mp_ass_packer_alloc(obj);
|
||||
|
||||
*out_imgs = obj->parts_cache;
|
||||
MP_TARRAY_GROW(obj, obj->ass_imgs, obj->num_externals + 1);
|
||||
|
||||
obj->parts_cache.change_id = 0;
|
||||
obj->parts_cache.num_parts = 0;
|
||||
append_ass(&obj->ass, &obj->vo_res, &obj->ass_imgs[0], &obj->changed);
|
||||
for (int n = 0; n < obj->num_externals; n++) {
|
||||
append_ass(&obj->externals[n].ass, &obj->vo_res, &obj->ass_imgs[n + 1],
|
||||
&obj->changed);
|
||||
}
|
||||
|
||||
mp_ass_packer_pack(obj->ass_packer, obj->ass_imgs, obj->num_externals + 1,
|
||||
obj->changed, format, out_imgs);
|
||||
|
||||
obj->changed = false;
|
||||
}
|
||||
+3
-7
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "osd.h"
|
||||
|
||||
#define OSD_CONV_CACHE_MAX 4
|
||||
|
||||
enum mp_osdtype {
|
||||
OSDTYPE_SUB,
|
||||
OSDTYPE_SUB2, // IDs must be numerically successive
|
||||
@@ -48,17 +46,15 @@ struct osd_object {
|
||||
// OSDTYPE_EXTERNAL2
|
||||
struct sub_bitmaps *external2;
|
||||
|
||||
// caches for OSD conversion (internal to render_object())
|
||||
struct osd_conv_cache *cache[OSD_CONV_CACHE_MAX];
|
||||
struct sub_bitmaps cached;
|
||||
|
||||
// VO cache state
|
||||
int vo_change_id;
|
||||
struct mp_osd_res vo_res;
|
||||
|
||||
// Internally used by osd_libass.c
|
||||
struct sub_bitmaps parts_cache;
|
||||
bool changed;
|
||||
struct ass_state ass;
|
||||
struct mp_ass_packer *ass_packer;
|
||||
struct ass_image **ass_imgs;
|
||||
};
|
||||
|
||||
struct osd_external {
|
||||
|
||||
@@ -37,8 +37,8 @@ struct sd_functions {
|
||||
bool (*accepts_packet)(struct sd *sd); // implicit default if NULL: true
|
||||
int (*control)(struct sd *sd, enum sd_ctrl cmd, void *arg);
|
||||
|
||||
void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res);
|
||||
void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, int format,
|
||||
double pts, struct sub_bitmaps *res);
|
||||
char *(*get_text)(struct sd *sd, double pts);
|
||||
};
|
||||
|
||||
|
||||
+30
-23
@@ -44,7 +44,8 @@ struct sd_ass_priv {
|
||||
bool is_converted;
|
||||
struct lavc_conv *converter;
|
||||
bool on_top;
|
||||
struct sub_bitmaps part_cache;
|
||||
struct mp_ass_packer *packer;
|
||||
struct sub_bitmap *bs;
|
||||
char last_text[500];
|
||||
struct mp_image_params video_params;
|
||||
struct mp_image_params last_params;
|
||||
@@ -212,6 +213,8 @@ static int init(struct sd *sd)
|
||||
|
||||
enable_output(sd, true);
|
||||
|
||||
ctx->packer = mp_ass_packer_alloc(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -417,8 +420,8 @@ static long long find_timestamp(struct sd *sd, double pts)
|
||||
|
||||
#undef END
|
||||
|
||||
static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res)
|
||||
static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, int format,
|
||||
double pts, struct sub_bitmaps *res)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
struct MPOpts *opts = sd->opts;
|
||||
@@ -459,15 +462,18 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
|
||||
if (no_ass)
|
||||
fill_plaintext(sd, pts);
|
||||
|
||||
ctx->part_cache.change_id = 0;
|
||||
ctx->part_cache.num_parts = 0;
|
||||
mp_ass_render_frame(renderer, track, ts, &ctx->part_cache);
|
||||
talloc_steal(ctx, ctx->part_cache.parts);
|
||||
int changed;
|
||||
ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed);
|
||||
mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, format, res);
|
||||
|
||||
if (!converted)
|
||||
mangle_colors(sd, &ctx->part_cache);
|
||||
if (!converted && res->num_parts > 0) {
|
||||
// mangle_colors() modifies the color field, so copy the thing.
|
||||
MP_TARRAY_GROW(ctx, ctx->bs, res->num_parts);
|
||||
memcpy(ctx->bs, res->parts, sizeof(ctx->bs[0]) * res->num_parts);
|
||||
res->parts = ctx->bs;
|
||||
|
||||
*res = ctx->part_cache;
|
||||
mangle_colors(sd, res);
|
||||
}
|
||||
}
|
||||
|
||||
struct buf {
|
||||
@@ -727,15 +733,17 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
|
||||
struct mp_image_params params = ctx->video_params;
|
||||
|
||||
if (force_601) {
|
||||
params.colorspace = MP_CSP_BT_709;
|
||||
params.colorlevels = MP_CSP_LEVELS_TV;
|
||||
params.color = (struct mp_colorspace){
|
||||
.space = MP_CSP_BT_709,
|
||||
.levels = MP_CSP_LEVELS_TV,
|
||||
};
|
||||
}
|
||||
|
||||
if (csp == params.colorspace && levels == params.colorlevels)
|
||||
if (csp == params.color.space && levels == params.color.levels)
|
||||
return;
|
||||
|
||||
bool basic_conv = params.colorspace == MP_CSP_BT_709 &&
|
||||
params.colorlevels == MP_CSP_LEVELS_TV &&
|
||||
bool basic_conv = params.color.space == MP_CSP_BT_709 &&
|
||||
params.color.levels == MP_CSP_LEVELS_TV &&
|
||||
csp == MP_CSP_BT_601 &&
|
||||
levels == MP_CSP_LEVELS_TV;
|
||||
|
||||
@@ -743,8 +751,8 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
|
||||
if (opts->ass_vsfilter_color_compat == 1 && !basic_conv)
|
||||
return;
|
||||
|
||||
if (params.colorspace != ctx->last_params.colorspace ||
|
||||
params.colorlevels != ctx->last_params.colorlevels)
|
||||
if (params.color.space != ctx->last_params.color.space ||
|
||||
params.color.levels != ctx->last_params.color.levels)
|
||||
{
|
||||
int msgl = basic_conv ? MSGL_V : MSGL_WARN;
|
||||
ctx->last_params = params;
|
||||
@@ -752,22 +760,21 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
|
||||
"RGB -> %s %s -> %s %s -> RGB\n",
|
||||
m_opt_choice_str(mp_csp_names, csp),
|
||||
m_opt_choice_str(mp_csp_levels_names, levels),
|
||||
m_opt_choice_str(mp_csp_names, params.colorspace),
|
||||
m_opt_choice_str(mp_csp_names, params.colorlevels));
|
||||
m_opt_choice_str(mp_csp_names, params.color.space),
|
||||
m_opt_choice_str(mp_csp_names, params.color.levels));
|
||||
}
|
||||
|
||||
// Conversion that VSFilter would use
|
||||
struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS;
|
||||
vs_params.colorspace = csp;
|
||||
vs_params.levels_in = levels;
|
||||
vs_params.color.space = csp;
|
||||
vs_params.color.levels = levels;
|
||||
struct mp_cmat vs_yuv2rgb, vs_rgb2yuv;
|
||||
mp_get_csp_matrix(&vs_params, &vs_yuv2rgb);
|
||||
mp_invert_cmat(&vs_rgb2yuv, &vs_yuv2rgb);
|
||||
|
||||
// Proper conversion to RGB
|
||||
struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS;
|
||||
rgb_params.colorspace = params.colorspace;
|
||||
rgb_params.levels_in = params.colorlevels;
|
||||
rgb_params.color = params.color;
|
||||
struct mp_cmat vs2rgb;
|
||||
mp_get_csp_matrix(&rgb_params, &vs2rgb);
|
||||
|
||||
|
||||
+2
-2
@@ -422,8 +422,8 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
}
|
||||
}
|
||||
|
||||
static void get_bitmaps(struct sd *sd, struct mp_osd_res d, double pts,
|
||||
struct sub_bitmaps *res)
|
||||
static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format,
|
||||
double pts, struct sub_bitmaps *res)
|
||||
{
|
||||
struct sd_lavc_priv *priv = sd->priv;
|
||||
struct MPOpts *opts = sd->opts;
|
||||
|
||||
+60
-9
@@ -65,6 +65,7 @@ const struct m_opt_choice_alternatives mp_csp_prim_names[] = {
|
||||
{"prophoto", MP_CSP_PRIM_PRO_PHOTO},
|
||||
{"cie1931", MP_CSP_PRIM_CIE_1931},
|
||||
{"dci-p3", MP_CSP_PRIM_DCI_P3},
|
||||
{"v-gamut", MP_CSP_PRIM_V_GAMUT},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -78,6 +79,8 @@ const struct m_opt_choice_alternatives mp_csp_trc_names[] = {
|
||||
{"gamma2.8", MP_CSP_TRC_GAMMA28},
|
||||
{"prophoto", MP_CSP_TRC_PRO_PHOTO},
|
||||
{"st2084", MP_CSP_TRC_SMPTE_ST2084},
|
||||
{"std-b67", MP_CSP_TRC_ARIB_STD_B67},
|
||||
{"v-log", MP_CSP_TRC_V_LOG},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -171,8 +174,9 @@ enum mp_csp_trc avcol_trc_to_mp_csp_trc(int avtrc)
|
||||
case AVCOL_TRC_LINEAR: return MP_CSP_TRC_LINEAR;
|
||||
case AVCOL_TRC_GAMMA22: return MP_CSP_TRC_GAMMA22;
|
||||
case AVCOL_TRC_GAMMA28: return MP_CSP_TRC_GAMMA28;
|
||||
#if HAVE_AVUTIL_ST2084
|
||||
#if HAVE_AVUTIL_HDR
|
||||
case AVCOL_TRC_SMPTEST2084: return MP_CSP_TRC_SMPTE_ST2084;
|
||||
case AVCOL_TRC_ARIB_STD_B67: return MP_CSP_TRC_ARIB_STD_B67;
|
||||
#endif
|
||||
default: return MP_CSP_TRC_AUTO;
|
||||
}
|
||||
@@ -222,8 +226,9 @@ int mp_csp_trc_to_avcol_trc(enum mp_csp_trc trc)
|
||||
case MP_CSP_TRC_LINEAR: return AVCOL_TRC_LINEAR;
|
||||
case MP_CSP_TRC_GAMMA22: return AVCOL_TRC_GAMMA22;
|
||||
case MP_CSP_TRC_GAMMA28: return AVCOL_TRC_GAMMA28;
|
||||
#if HAVE_AVUTIL_ST2084
|
||||
#if HAVE_AVUTIL_HDR
|
||||
case MP_CSP_TRC_SMPTE_ST2084: return AVCOL_TRC_SMPTEST2084;
|
||||
case MP_CSP_TRC_ARIB_STD_B67: return AVCOL_TRC_ARIB_STD_B67;
|
||||
#endif
|
||||
default: return AVCOL_TRC_UNSPECIFIED;
|
||||
}
|
||||
@@ -419,11 +424,45 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc)
|
||||
.blue = {0.150, 0.060},
|
||||
.white = d65
|
||||
};
|
||||
// From Panasonic VARICAM reference manual
|
||||
case MP_CSP_PRIM_V_GAMUT:
|
||||
return (struct mp_csp_primaries) {
|
||||
.red = {0.730, 0.280},
|
||||
.green = {0.165, 0.840},
|
||||
.blue = {0.100, -0.03},
|
||||
.white = d65
|
||||
};
|
||||
default:
|
||||
return (struct mp_csp_primaries) {{0}};
|
||||
}
|
||||
}
|
||||
|
||||
// Get the nominal peak for a given colorspace, based on a known reference peak
|
||||
// (i.e. the display of a reference white illuminant. This may or may not
|
||||
// be the actual signal peak)
|
||||
float mp_csp_trc_nom_peak(enum mp_csp_trc trc, float ref_peak)
|
||||
{
|
||||
switch (trc) {
|
||||
case MP_CSP_TRC_SMPTE_ST2084: return 10000; // fixed peak
|
||||
case MP_CSP_TRC_ARIB_STD_B67: return 12.0 * ref_peak;
|
||||
case MP_CSP_TRC_V_LOG: return 46.0855 * ref_peak;
|
||||
}
|
||||
|
||||
return ref_peak;
|
||||
}
|
||||
|
||||
bool mp_trc_is_hdr(enum mp_csp_trc trc)
|
||||
{
|
||||
switch (trc) {
|
||||
case MP_CSP_TRC_SMPTE_ST2084:
|
||||
case MP_CSP_TRC_ARIB_STD_B67:
|
||||
case MP_CSP_TRC_V_LOG:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the RGB/XYZ matrix as described here:
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||
static void mp_get_rgb2xyz_matrix(struct mp_csp_primaries space, float m[3][3])
|
||||
@@ -506,7 +545,7 @@ static void mp_apply_chromatic_adaptation(struct mp_csp_col_xy src,
|
||||
mp_mul_matrix3x3(m, tmp);
|
||||
}
|
||||
|
||||
// get the coefficients of the source -> bt2020 cms matrix
|
||||
// get the coefficients of the source -> dest cms matrix
|
||||
void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest,
|
||||
enum mp_render_intent intent, float m[3][3])
|
||||
{
|
||||
@@ -543,7 +582,7 @@ void mp_get_cms_matrix(struct mp_csp_primaries src, struct mp_csp_primaries dest
|
||||
static void mp_get_xyz2rgb_coeffs(struct mp_csp_params *params,
|
||||
enum mp_render_intent intent, struct mp_cmat *m)
|
||||
{
|
||||
struct mp_csp_primaries prim = mp_get_csp_primaries(params->primaries);
|
||||
struct mp_csp_primaries prim = mp_get_csp_primaries(params->color.primaries);
|
||||
float brightness = params->brightness;
|
||||
mp_get_rgb2xyz_matrix(prim, m->m);
|
||||
mp_invert_matrix3x3(m->m);
|
||||
@@ -620,10 +659,10 @@ static void luma_coeffs(struct mp_cmat *mat, float lr, float lg, float lb)
|
||||
// get the coefficients of the yuv -> rgb conversion matrix
|
||||
void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m)
|
||||
{
|
||||
int colorspace = params->colorspace;
|
||||
enum mp_csp colorspace = params->color.space;
|
||||
if (colorspace <= MP_CSP_AUTO || colorspace >= MP_CSP_COUNT)
|
||||
colorspace = MP_CSP_BT_601;
|
||||
int levels_in = params->levels_in;
|
||||
enum mp_csp_levels levels_in = params->color.levels;
|
||||
if (levels_in <= MP_CSP_LEVELS_AUTO || levels_in >= MP_CSP_LEVELS_COUNT)
|
||||
levels_in = MP_CSP_LEVELS_TV;
|
||||
|
||||
@@ -682,6 +721,10 @@ void mp_get_csp_matrix(struct mp_csp_params *params, struct mp_cmat *m)
|
||||
// The values below are written in 0-255 scale - thus bring s into range.
|
||||
double s =
|
||||
mp_get_csp_mul(colorspace, params->input_bits, params->texture_bits) / 255;
|
||||
// NOTE: The yuvfull ranges as presented here are arguably ambiguous,
|
||||
// and conflict with at least the full-range YCbCr/ICtCp values as defined
|
||||
// by ITU-R BT.2100. If somebody ever complains about full-range YUV looking
|
||||
// different from their reference display, this comment is probably why.
|
||||
struct yuvlevels { double ymin, ymax, cmin, cmid; }
|
||||
yuvlim = { 16*s, 235*s, 16*s, 128*s },
|
||||
yuvfull = { 0*s, 255*s, 1*s, 128*s }, // '1' for symmetry around 128
|
||||
@@ -734,9 +777,17 @@ void mp_csp_set_image_params(struct mp_csp_params *params,
|
||||
{
|
||||
struct mp_image_params p = *imgparams;
|
||||
mp_image_params_guess_csp(&p); // ensure consistency
|
||||
params->colorspace = p.colorspace;
|
||||
params->levels_in = p.colorlevels;
|
||||
params->primaries = p.primaries;
|
||||
params->color = p.color;
|
||||
}
|
||||
|
||||
bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2)
|
||||
{
|
||||
return c1.space == c2.space &&
|
||||
c1.levels == c2.levels &&
|
||||
c1.primaries == c2.primaries &&
|
||||
c1.gamma == c2.gamma &&
|
||||
c1.sig_peak == c2.sig_peak &&
|
||||
c1.nom_peak == c2.nom_peak;
|
||||
}
|
||||
|
||||
// Copy settings from eq into params.
|
||||
|
||||
+20
-16
@@ -64,6 +64,7 @@ enum mp_csp_prim {
|
||||
MP_CSP_PRIM_PRO_PHOTO,
|
||||
MP_CSP_PRIM_CIE_1931,
|
||||
MP_CSP_PRIM_DCI_P3,
|
||||
MP_CSP_PRIM_V_GAMUT,
|
||||
MP_CSP_PRIM_COUNT
|
||||
};
|
||||
|
||||
@@ -79,6 +80,8 @@ enum mp_csp_trc {
|
||||
MP_CSP_TRC_GAMMA28,
|
||||
MP_CSP_TRC_PRO_PHOTO,
|
||||
MP_CSP_TRC_SMPTE_ST2084,
|
||||
MP_CSP_TRC_ARIB_STD_B67,
|
||||
MP_CSP_TRC_V_LOG,
|
||||
MP_CSP_TRC_COUNT
|
||||
};
|
||||
|
||||
@@ -113,11 +116,18 @@ extern const struct m_opt_choice_alternatives mp_stereo3d_names[];
|
||||
#define MP_STEREO3D_NAME_DEF(x, def) \
|
||||
(MP_STEREO3D_NAME(x) ? MP_STEREO3D_NAME(x) : (def))
|
||||
|
||||
struct mp_csp_params {
|
||||
enum mp_csp colorspace;
|
||||
enum mp_csp_levels levels_in; // encoded video
|
||||
enum mp_csp_levels levels_out; // output device
|
||||
struct mp_colorspace {
|
||||
enum mp_csp space;
|
||||
enum mp_csp_levels levels;
|
||||
enum mp_csp_prim primaries;
|
||||
enum mp_csp_trc gamma;
|
||||
float nom_peak; // nominal (absolute) peak. 0 = auto/unknown
|
||||
float sig_peak; // signal peak, highest value that occurs in the source
|
||||
};
|
||||
|
||||
struct mp_csp_params {
|
||||
struct mp_colorspace color; // input colorspace
|
||||
enum mp_csp_levels levels_out; // output device
|
||||
float brightness;
|
||||
float contrast;
|
||||
float hue;
|
||||
@@ -131,9 +141,8 @@ struct mp_csp_params {
|
||||
};
|
||||
|
||||
#define MP_CSP_PARAMS_DEFAULTS { \
|
||||
.colorspace = MP_CSP_BT_601, \
|
||||
.levels_in = MP_CSP_LEVELS_TV, \
|
||||
.primaries = MP_CSP_PRIM_AUTO, \
|
||||
.color = { .space = MP_CSP_BT_601, \
|
||||
.levels = MP_CSP_LEVELS_TV }, \
|
||||
.levels_out = MP_CSP_LEVELS_PC, \
|
||||
.brightness = 0, .contrast = 1, .hue = 0, .saturation = 1, \
|
||||
.gamma = 1, .texture_bits = 8, .input_bits = 8}
|
||||
@@ -142,6 +151,8 @@ struct mp_image_params;
|
||||
void mp_csp_set_image_params(struct mp_csp_params *params,
|
||||
const struct mp_image_params *imgparams);
|
||||
|
||||
bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2);
|
||||
|
||||
enum mp_chroma_location {
|
||||
MP_CHROMA_AUTO,
|
||||
MP_CHROMA_LEFT, // mpeg2/4, h264
|
||||
@@ -193,27 +204,19 @@ struct mp_csp_primaries {
|
||||
|
||||
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);
|
||||
|
||||
enum mp_csp_trc avcol_trc_to_mp_csp_trc(int avtrc);
|
||||
|
||||
int mp_csp_to_avcol_spc(enum mp_csp colorspace);
|
||||
|
||||
int mp_csp_levels_to_avcol_range(enum mp_csp_levels range);
|
||||
|
||||
int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim);
|
||||
|
||||
int mp_csp_trc_to_avcol_trc(enum mp_csp_trc trc);
|
||||
|
||||
enum mp_csp mp_csp_guess_colorspace(int width, int height);
|
||||
@@ -221,10 +224,11 @@ enum mp_csp_prim mp_csp_guess_primaries(int width, int height);
|
||||
|
||||
enum mp_chroma_location avchroma_location_to_mp(int avloc);
|
||||
int mp_chroma_location_to_av(enum mp_chroma_location mploc);
|
||||
|
||||
void mp_get_chroma_location(enum mp_chroma_location loc, int *x, int *y);
|
||||
|
||||
struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim csp);
|
||||
float mp_csp_trc_nom_peak(enum mp_csp_trc trc, float ref_peak);
|
||||
bool mp_trc_is_hdr(enum mp_csp_trc trc);
|
||||
|
||||
/* Color conversion matrix: RGB = m * YUV + c
|
||||
* m is in row-major matrix, with m[row][col], e.g.:
|
||||
|
||||
+94
-1
@@ -24,6 +24,7 @@
|
||||
#include "common/av_common.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "video/mp_image_pool.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
|
||||
#include "d3d.h"
|
||||
@@ -68,7 +69,7 @@ struct d3dva_mode {
|
||||
|
||||
#define MODE2(id) &MP_CONCAT(DXVA2_Mode, id), # id
|
||||
#define MODE(id) &MP_CONCAT(DXVA_, id), # id
|
||||
// Prefered modes must come first
|
||||
// Preferred modes must come first
|
||||
static const struct d3dva_mode d3dva_modes[] = {
|
||||
// MPEG-1/2
|
||||
{MODE2(MPEG2_VLD), AV_CODEC_ID_MPEG2VIDEO, PROF_MPEG2_MAIN},
|
||||
@@ -266,3 +267,95 @@ void copy_nv12(struct mp_image *dest, uint8_t *src_bits,
|
||||
buf.stride[1] = src_pitch;
|
||||
mp_image_copy_gpu(dest, &buf);
|
||||
}
|
||||
|
||||
// Test if Direct3D11 can be used by us. Basically, this prevents trying to use
|
||||
// D3D11 on Win7, and then failing somewhere in the process.
|
||||
bool d3d11_check_decoding(ID3D11Device *dev)
|
||||
{
|
||||
HRESULT hr;
|
||||
// We assume that NV12 is always supported, if hw decoding is supported at
|
||||
// all.
|
||||
UINT supported = 0;
|
||||
hr = ID3D11Device_CheckFormatSupport(dev, DXGI_FORMAT_NV12, &supported);
|
||||
return !FAILED(hr) && (supported & D3D11_BIND_DECODER);
|
||||
}
|
||||
|
||||
static int get_dxgi_mpfmt(DWORD dxgi_fmt)
|
||||
{
|
||||
switch (dxgi_fmt) {
|
||||
case DXGI_FORMAT_NV12: return IMGFMT_NV12;
|
||||
case DXGI_FORMAT_P010: return IMGFMT_P010;
|
||||
case DXGI_FORMAT_P016: return IMGFMT_P010;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_image *d3d11_download_image(struct mp_hwdec_ctx *ctx,
|
||||
struct mp_image *mpi,
|
||||
struct mp_image_pool *swpool)
|
||||
{
|
||||
HRESULT hr;
|
||||
ID3D11Device *device = ctx->ctx;
|
||||
|
||||
if (mpi->imgfmt != IMGFMT_D3D11VA && mpi->imgfmt != IMGFMT_D3D11NV12)
|
||||
return NULL;
|
||||
|
||||
ID3D11Texture2D *texture = (void *)mpi->planes[1];
|
||||
int subindex = (intptr_t)mpi->planes[2];
|
||||
if (!texture)
|
||||
return NULL;
|
||||
|
||||
D3D11_TEXTURE2D_DESC tex_desc;
|
||||
ID3D11Texture2D_GetDesc(texture, &tex_desc);
|
||||
int mpfmt = get_dxgi_mpfmt(tex_desc.Format);
|
||||
if (!mpfmt)
|
||||
return NULL;
|
||||
|
||||
// create staging texture shared with the CPU with mostly the same
|
||||
// parameters as the source texture
|
||||
tex_desc.MipLevels = 1;
|
||||
tex_desc.MiscFlags = 0;
|
||||
tex_desc.ArraySize = 1;
|
||||
tex_desc.Usage = D3D11_USAGE_STAGING;
|
||||
tex_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
tex_desc.BindFlags = 0;
|
||||
ID3D11Texture2D *staging = NULL;
|
||||
hr = ID3D11Device_CreateTexture2D(device, &tex_desc, NULL, &staging);
|
||||
if (FAILED(hr))
|
||||
return NULL;
|
||||
|
||||
bool ok = false;
|
||||
struct mp_image *sw_img = NULL;
|
||||
ID3D11DeviceContext *device_ctx = NULL;
|
||||
ID3D11Device_GetImmediateContext(device, &device_ctx);
|
||||
|
||||
// copy to the staging texture
|
||||
ID3D11DeviceContext_CopySubresourceRegion(
|
||||
device_ctx,
|
||||
(ID3D11Resource *)staging, 0, 0, 0, 0,
|
||||
(ID3D11Resource *)texture, subindex, NULL);
|
||||
|
||||
sw_img = mp_image_pool_get(swpool, mpfmt, tex_desc.Width, tex_desc.Height);
|
||||
if (!sw_img)
|
||||
goto done;
|
||||
|
||||
// copy staging texture to the cpu mp_image
|
||||
D3D11_MAPPED_SUBRESOURCE lock;
|
||||
hr = ID3D11DeviceContext_Map(device_ctx, (ID3D11Resource *)staging,
|
||||
0, D3D11_MAP_READ, 0, &lock);
|
||||
if (FAILED(hr))
|
||||
goto done;
|
||||
copy_nv12(sw_img, lock.pData, lock.RowPitch, tex_desc.Height);
|
||||
ID3D11DeviceContext_Unmap(device_ctx, (ID3D11Resource *)staging, 0);
|
||||
|
||||
mp_image_set_size(sw_img, mpi->w, mpi->h);
|
||||
mp_image_copy_attributes(sw_img, mpi);
|
||||
ok = true;
|
||||
|
||||
done:
|
||||
ID3D11Texture2D_Release(staging);
|
||||
ID3D11DeviceContext_Release(device_ctx);
|
||||
if (!ok)
|
||||
mp_image_unrefp(&sw_img);
|
||||
return sw_img;
|
||||
}
|
||||
@@ -19,6 +19,9 @@
|
||||
#define MPV_DECODE_D3D_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
struct mp_image;
|
||||
@@ -62,4 +65,10 @@ BOOL is_clearvideo(const GUID *mode_guid);
|
||||
void copy_nv12(struct mp_image *dest, uint8_t *src_bits,
|
||||
unsigned src_pitch, unsigned surf_height);
|
||||
|
||||
bool d3d11_check_decoding(ID3D11Device *dev);
|
||||
|
||||
struct mp_image *d3d11_download_image(struct mp_hwdec_ctx *ctx,
|
||||
struct mp_image *mpi,
|
||||
struct mp_image_pool *swpool);
|
||||
|
||||
#endif
|
||||
@@ -25,6 +25,9 @@ typedef struct lavc_ctx {
|
||||
bool hwdec_failed;
|
||||
bool hwdec_notified;
|
||||
|
||||
// For HDR side-data caching
|
||||
double cached_hdr_peak;
|
||||
|
||||
struct mp_image **delay_queue;
|
||||
int num_delay_queue;
|
||||
int max_delay_queue;
|
||||
|
||||
+35
-4
@@ -47,6 +47,10 @@
|
||||
#include "video/csputils.h"
|
||||
#include "video/sws_utils.h"
|
||||
|
||||
#if HAVE_AVUTIL_MASTERING_METADATA
|
||||
#include <libavutil/mastering_display_metadata.h>
|
||||
#endif
|
||||
|
||||
#include "lavc.h"
|
||||
|
||||
#if AVPALETTE_SIZE != MP_PALETTE_SIZE
|
||||
@@ -129,17 +133,21 @@ extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy;
|
||||
extern const struct vd_lavc_hwdec mp_vd_lavc_d3d11va;
|
||||
extern const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy;
|
||||
|
||||
#if HAVE_RPI
|
||||
static const struct vd_lavc_hwdec mp_vd_lavc_rpi = {
|
||||
.type = HWDEC_RPI,
|
||||
.lavc_suffix = "_mmal",
|
||||
.image_format = IMGFMT_MMAL,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if HAVE_ANDROID
|
||||
static const struct vd_lavc_hwdec mp_vd_lavc_mediacodec = {
|
||||
.type = HWDEC_MEDIACODEC,
|
||||
.lavc_suffix = "_mediacodec",
|
||||
.copying = true,
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct vd_lavc_hwdec *const hwdec_list[] = {
|
||||
#if HAVE_RPI
|
||||
@@ -568,16 +576,39 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
|
||||
vd_ffmpeg_ctx *ctx = vd->priv;
|
||||
struct MPOpts *opts = ctx->opts;
|
||||
|
||||
#if HAVE_AVUTIL_MASTERING_METADATA
|
||||
// Get the reference peak (for HDR) if available. This is cached into ctx
|
||||
// when it's found, since it's not available on every frame (and seems to
|
||||
// be only available for keyframes)
|
||||
AVFrameSideData *sd = av_frame_get_side_data(frame,
|
||||
AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
|
||||
if (sd) {
|
||||
AVMasteringDisplayMetadata *mdm = (AVMasteringDisplayMetadata *)sd->data;
|
||||
if (mdm->has_luminance) {
|
||||
double peak = av_q2d(mdm->max_luminance);
|
||||
if (!isnormal(peak) || peak < 10 || peak > 100000) {
|
||||
// Invalid data, ignore it. Sadly necessary
|
||||
MP_WARN(vd, "Invalid HDR reference peak in stream: %f\n", peak);
|
||||
} else {
|
||||
ctx->cached_hdr_peak = peak;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
*out_params = (struct mp_image_params) {
|
||||
.imgfmt = pixfmt2imgfmt(frame->format),
|
||||
.w = frame->width,
|
||||
.h = frame->height,
|
||||
.p_w = frame->sample_aspect_ratio.num,
|
||||
.p_h = frame->sample_aspect_ratio.den,
|
||||
.colorspace = avcol_spc_to_mp_csp(ctx->avctx->colorspace),
|
||||
.colorlevels = avcol_range_to_mp_csp_levels(ctx->avctx->color_range),
|
||||
.primaries = avcol_pri_to_mp_csp_prim(ctx->avctx->color_primaries),
|
||||
.gamma = avcol_trc_to_mp_csp_trc(ctx->avctx->color_trc),
|
||||
.color = {
|
||||
.space = avcol_spc_to_mp_csp(ctx->avctx->colorspace),
|
||||
.levels = avcol_range_to_mp_csp_levels(ctx->avctx->color_range),
|
||||
.primaries = avcol_pri_to_mp_csp_prim(ctx->avctx->color_primaries),
|
||||
.gamma = avcol_trc_to_mp_csp_trc(ctx->avctx->color_trc),
|
||||
.sig_peak = ctx->cached_hdr_peak,
|
||||
},
|
||||
.chroma_location =
|
||||
avchroma_location_to_mp(ctx->avctx->chroma_sample_location),
|
||||
.rotate = vd->codec->rotate,
|
||||
|
||||
+5
-1
@@ -227,6 +227,8 @@ void vf_print_filter_chain(struct vf_chain *c, int msglevel,
|
||||
for (vf_instance_t *f = c->first; f; f = f->next) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", f->info->name);
|
||||
if (f->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", f->label);
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_image_params_to_str(&f->fmt_out));
|
||||
if (f->autoinserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
@@ -298,6 +300,7 @@ void vf_remove_filter(struct vf_chain *c, struct vf_instance *vf)
|
||||
assert(prev); // not inserted
|
||||
prev->next = vf->next;
|
||||
vf_uninit_filter(vf);
|
||||
c->initialized = 0;
|
||||
}
|
||||
|
||||
struct vf_instance *vf_append_filter(struct vf_chain *c, const char *name,
|
||||
@@ -312,6 +315,7 @@ struct vf_instance *vf_append_filter(struct vf_chain *c, const char *name,
|
||||
pprev = &(*pprev)->next;
|
||||
vf->next = *pprev ? *pprev : NULL;
|
||||
*pprev = vf;
|
||||
c->initialized = 0;
|
||||
}
|
||||
return vf;
|
||||
}
|
||||
@@ -652,7 +656,7 @@ int vf_reconfig(struct vf_chain *c, const struct mp_image_params *params)
|
||||
mp_msg(c->log, loglevel, "Video filter chain:\n");
|
||||
vf_print_filter_chain(c, loglevel, failing);
|
||||
if (r < 0)
|
||||
c->input_params = c->output_params = (struct mp_image_params){0};
|
||||
c->output_params = (struct mp_image_params){0};
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
+11
-13
@@ -211,21 +211,21 @@ static int recreate_video_proc(struct vf_instance *vf)
|
||||
FALSE, 0);
|
||||
|
||||
D3D11_VIDEO_PROCESSOR_COLOR_SPACE csp = {
|
||||
.YCbCr_Matrix = p->params.colorspace != MP_CSP_BT_601,
|
||||
.Nominal_Range = p->params.colorlevels == MP_CSP_LEVELS_TV ? 1 : 2,
|
||||
.YCbCr_Matrix = p->params.color.space != MP_CSP_BT_601,
|
||||
.Nominal_Range = p->params.color.levels == MP_CSP_LEVELS_TV ? 1 : 2,
|
||||
};
|
||||
ID3D11VideoContext_VideoProcessorSetStreamColorSpace(p->video_ctx,
|
||||
p->video_proc,
|
||||
0, &csp);
|
||||
if (p->out_rgb) {
|
||||
if (p->params.colorspace != MP_CSP_BT_601 &&
|
||||
p->params.colorspace != MP_CSP_BT_709)
|
||||
if (p->params.color.space != MP_CSP_BT_601 &&
|
||||
p->params.color.space != MP_CSP_BT_709)
|
||||
{
|
||||
MP_WARN(vf, "Unsupported video colorspace (%s/%s). Consider "
|
||||
"disabling hardware decoding, or using "
|
||||
"--hwdec=d3d11va-copy to get correct output.\n",
|
||||
m_opt_choice_str(mp_csp_names, p->params.colorspace),
|
||||
m_opt_choice_str(mp_csp_levels_names, p->params.colorlevels));
|
||||
m_opt_choice_str(mp_csp_names, p->params.color.space),
|
||||
m_opt_choice_str(mp_csp_levels_names, p->params.color.levels));
|
||||
}
|
||||
} else {
|
||||
ID3D11VideoContext_VideoProcessorSetOutputColorSpace(p->video_ctx,
|
||||
@@ -331,11 +331,6 @@ static int render(struct vf_instance *vf)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Make sure the texture is updated correctly on the shared context.
|
||||
// (I'm not sure if this is correct, though it won't harm.)
|
||||
if (p->out_shared)
|
||||
ID3D11DeviceContext_Flush(p->device_ctx);
|
||||
|
||||
res = 0;
|
||||
cleanup:
|
||||
if (in_view)
|
||||
@@ -360,8 +355,11 @@ static int filter_out(struct vf_instance *vf)
|
||||
|
||||
// no filtering
|
||||
if (!mp_refqueue_should_deint(p->queue) && !p->require_filtering) {
|
||||
struct mp_image *in = mp_refqueue_get(p->queue, 0);
|
||||
vf_add_output_frame(vf, mp_image_new_ref(in));
|
||||
struct mp_image *in = mp_image_new_ref(mp_refqueue_get(p->queue, 0));
|
||||
if (!in)
|
||||
return -1;
|
||||
mp_image_set_params(in, &p->out_params);
|
||||
vf_add_output_frame(vf, in);
|
||||
mp_refqueue_next(p->queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -88,15 +88,15 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
|
||||
if (p->outfmt)
|
||||
out->imgfmt = p->outfmt;
|
||||
if (p->colormatrix)
|
||||
out->colorspace = p->colormatrix;
|
||||
out->color.space = p->colormatrix;
|
||||
if (p->colorlevels)
|
||||
out->colorlevels = p->colorlevels;
|
||||
out->color.levels = p->colorlevels;
|
||||
if (p->primaries)
|
||||
out->primaries = p->primaries;
|
||||
out->color.primaries = p->primaries;
|
||||
if (p->gamma)
|
||||
out->gamma = p->gamma;
|
||||
out->color.gamma = p->gamma;
|
||||
if (p->peak)
|
||||
out->peak = p->peak;
|
||||
out->color.sig_peak = p->peak;
|
||||
if (p->chroma_location)
|
||||
out->chroma_location = p->chroma_location;
|
||||
if (p->stereo_in)
|
||||
|
||||
@@ -166,8 +166,8 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
|
||||
struct mp_imgfmt_desc d_fmt = mp_imgfmt_get_desc(out->imgfmt);
|
||||
// keep colorspace settings if the data stays in yuv
|
||||
if (!(s_fmt.flags & MP_IMGFLAG_YUV) || !(d_fmt.flags & MP_IMGFLAG_YUV)) {
|
||||
out->colorspace = MP_CSP_AUTO;
|
||||
out->colorlevels = MP_CSP_LEVELS_AUTO;
|
||||
out->color.space = MP_CSP_AUTO;
|
||||
out->color.levels = MP_CSP_LEVELS_AUTO;
|
||||
}
|
||||
mp_image_params_guess_csp(out);
|
||||
|
||||
|
||||
@@ -143,13 +143,13 @@ static void copy_mp_to_vs_frame_props_map(struct vf_priv_s *p, VSMap *map,
|
||||
struct mp_image_params *params = &img->params;
|
||||
p->vsapi->propSetInt(map, "_SARNum", params->p_w, 0);
|
||||
p->vsapi->propSetInt(map, "_SARDen", params->p_h, 0);
|
||||
if (params->colorlevels) {
|
||||
if (params->color.levels) {
|
||||
p->vsapi->propSetInt(map, "_ColorRange",
|
||||
params->colorlevels == MP_CSP_LEVELS_TV, 0);
|
||||
params->color.levels == MP_CSP_LEVELS_TV, 0);
|
||||
}
|
||||
// The docs explicitly say it uses libavcodec values.
|
||||
p->vsapi->propSetInt(map, "_ColorSpace",
|
||||
mp_csp_to_avcol_spc(params->colorspace), 0);
|
||||
mp_csp_to_avcol_spc(params->color.space), 0);
|
||||
if (params->chroma_location) {
|
||||
p->vsapi->propSetInt(map, "_ChromaLocation",
|
||||
params->chroma_location == MP_CHROMA_CENTER, 0);
|
||||
|
||||
@@ -168,7 +168,7 @@ static struct mp_image *render(struct vf_instance *vf)
|
||||
mp_image_set_size(img, in->w, in->h);
|
||||
mp_image_copy_attributes(img, in);
|
||||
|
||||
unsigned int flags = va_get_colorspace_flag(p->params.colorspace);
|
||||
unsigned int flags = va_get_colorspace_flag(p->params.color.space);
|
||||
if (!mp_refqueue_is_interlaced(p->queue)) {
|
||||
flags |= VA_FRAME_PICTURE;
|
||||
} else if (mp_refqueue_is_top_field(p->queue)) {
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ void *gpu_memcpy(void *restrict d, const void *restrict s, size_t size)
|
||||
xmm15 = _mm_stream_load_si128(pSrc + 15);
|
||||
#endif
|
||||
pSrc += regsInLoop;
|
||||
// _mm_store_si128 emit the SSE2 intruction MOVDQA (aligned store)
|
||||
// _mm_store_si128 emit the SSE2 instruction MOVDQA (aligned store)
|
||||
_mm_store_si128(pTrg , xmm0);
|
||||
_mm_store_si128(pTrg + 1, xmm1);
|
||||
_mm_store_si128(pTrg + 2, xmm2);
|
||||
|
||||
@@ -133,8 +133,8 @@ static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
|
||||
pic->width = avctx->width;
|
||||
pic->height = avctx->height;
|
||||
if (ctx->opts->tag_csp) {
|
||||
pic->color_primaries = mp_csp_prim_to_avcol_pri(image->params.primaries);
|
||||
pic->color_trc = mp_csp_trc_to_avcol_trc(image->params.gamma);
|
||||
pic->color_primaries = mp_csp_prim_to_avcol_pri(image->params.color.primaries);
|
||||
pic->color_trc = mp_csp_trc_to_avcol_trc(image->params.color.gamma);
|
||||
}
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
|
||||
+58
-55
@@ -393,11 +393,13 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
|
||||
dst->params.p_w = src->params.p_w;
|
||||
dst->params.p_h = src->params.p_h;
|
||||
}
|
||||
dst->params.primaries = src->params.primaries;
|
||||
dst->params.gamma = src->params.gamma;
|
||||
dst->params.color.primaries = src->params.color.primaries;
|
||||
dst->params.color.gamma = src->params.color.gamma;
|
||||
dst->params.color.nom_peak = src->params.color.nom_peak;
|
||||
dst->params.color.sig_peak = src->params.color.sig_peak;
|
||||
if ((dst->fmt.flags & MP_IMGFLAG_YUV) == (src->fmt.flags & MP_IMGFLAG_YUV)) {
|
||||
dst->params.colorspace = src->params.colorspace;
|
||||
dst->params.colorlevels = src->params.colorlevels;
|
||||
dst->params.color.space = src->params.color.space;
|
||||
dst->params.color.levels = src->params.color.levels;
|
||||
dst->params.chroma_location = src->params.chroma_location;
|
||||
}
|
||||
mp_image_params_guess_csp(&dst->params); // ensure colorspace consistency
|
||||
@@ -512,8 +514,8 @@ char *mp_image_params_to_str_buf(char *b, size_t bs,
|
||||
if (p->hw_subfmt)
|
||||
mp_snprintf_cat(b, bs, "[%llu]", (unsigned long long)(p->hw_subfmt));
|
||||
mp_snprintf_cat(b, bs, " %s/%s",
|
||||
m_opt_choice_str(mp_csp_names, p->colorspace),
|
||||
m_opt_choice_str(mp_csp_levels_names, p->colorlevels));
|
||||
m_opt_choice_str(mp_csp_names, p->color.space),
|
||||
m_opt_choice_str(mp_csp_levels_names, p->color.levels));
|
||||
mp_snprintf_cat(b, bs, " CL=%s",
|
||||
m_opt_choice_str(mp_chroma_names, p->chroma_location));
|
||||
if (p->rotate)
|
||||
@@ -564,11 +566,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1,
|
||||
p1->hw_subfmt == p2->hw_subfmt &&
|
||||
p1->w == p2->w && p1->h == p2->h &&
|
||||
p1->p_w == p2->p_w && p1->p_h == p2->p_h &&
|
||||
p1->colorspace == p2->colorspace &&
|
||||
p1->colorlevels == p2->colorlevels &&
|
||||
p1->primaries == p2->primaries &&
|
||||
p1->gamma == p2->gamma &&
|
||||
p1->peak == p2->peak &&
|
||||
mp_colorspace_equal(p1->color, p2->color) &&
|
||||
p1->chroma_location == p2->chroma_location &&
|
||||
p1->rotate == p2->rotate &&
|
||||
p1->stereo_in == p2->stereo_in &&
|
||||
@@ -598,51 +596,56 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
|
||||
if (!fmt.id)
|
||||
return;
|
||||
if (fmt.flags & MP_IMGFLAG_YUV) {
|
||||
if (params->colorspace != MP_CSP_BT_601 &&
|
||||
params->colorspace != MP_CSP_BT_709 &&
|
||||
params->colorspace != MP_CSP_BT_2020_NC &&
|
||||
params->colorspace != MP_CSP_BT_2020_C &&
|
||||
params->colorspace != MP_CSP_SMPTE_240M &&
|
||||
params->colorspace != MP_CSP_YCGCO)
|
||||
if (params->color.space != MP_CSP_BT_601 &&
|
||||
params->color.space != MP_CSP_BT_709 &&
|
||||
params->color.space != MP_CSP_BT_2020_NC &&
|
||||
params->color.space != MP_CSP_BT_2020_C &&
|
||||
params->color.space != MP_CSP_SMPTE_240M &&
|
||||
params->color.space != MP_CSP_YCGCO)
|
||||
{
|
||||
// Makes no sense, so guess instead
|
||||
// YCGCO should be separate, but libavcodec disagrees
|
||||
params->colorspace = MP_CSP_AUTO;
|
||||
params->color.space = MP_CSP_AUTO;
|
||||
}
|
||||
if (params->colorspace == MP_CSP_AUTO)
|
||||
params->colorspace = mp_csp_guess_colorspace(params->w, params->h);
|
||||
if (params->colorlevels == MP_CSP_LEVELS_AUTO)
|
||||
params->colorlevels = MP_CSP_LEVELS_TV;
|
||||
if (params->primaries == MP_CSP_PRIM_AUTO) {
|
||||
// Guess based on the colormatrix as a first priority
|
||||
if (params->colorspace == MP_CSP_BT_2020_NC ||
|
||||
params->colorspace == MP_CSP_BT_2020_C) {
|
||||
params->primaries = MP_CSP_PRIM_BT_2020;
|
||||
} else if (params->colorspace == MP_CSP_BT_709) {
|
||||
params->primaries = MP_CSP_PRIM_BT_709;
|
||||
if (params->color.space == MP_CSP_AUTO)
|
||||
params->color.space = mp_csp_guess_colorspace(params->w, params->h);
|
||||
if (params->color.levels == MP_CSP_LEVELS_AUTO) {
|
||||
if (params->color.gamma == MP_CSP_TRC_V_LOG) {
|
||||
params->color.levels = MP_CSP_LEVELS_PC;
|
||||
} else {
|
||||
// Ambiguous colormatrix for BT.601, guess based on res
|
||||
params->primaries = mp_csp_guess_primaries(params->w, params->h);
|
||||
params->color.levels = MP_CSP_LEVELS_TV;
|
||||
}
|
||||
}
|
||||
if (params->gamma == MP_CSP_TRC_AUTO)
|
||||
params->gamma = MP_CSP_TRC_BT_1886;
|
||||
if (params->color.primaries == MP_CSP_PRIM_AUTO) {
|
||||
// Guess based on the colormatrix as a first priority
|
||||
if (params->color.space == MP_CSP_BT_2020_NC ||
|
||||
params->color.space == MP_CSP_BT_2020_C) {
|
||||
params->color.primaries = MP_CSP_PRIM_BT_2020;
|
||||
} else if (params->color.space == MP_CSP_BT_709) {
|
||||
params->color.primaries = MP_CSP_PRIM_BT_709;
|
||||
} else {
|
||||
// Ambiguous colormatrix for BT.601, guess based on res
|
||||
params->color.primaries = mp_csp_guess_primaries(params->w, params->h);
|
||||
}
|
||||
}
|
||||
if (params->color.gamma == MP_CSP_TRC_AUTO)
|
||||
params->color.gamma = MP_CSP_TRC_BT_1886;
|
||||
} else if (fmt.flags & MP_IMGFLAG_RGB) {
|
||||
params->colorspace = MP_CSP_RGB;
|
||||
params->colorlevels = MP_CSP_LEVELS_PC;
|
||||
params->color.space = MP_CSP_RGB;
|
||||
params->color.levels = MP_CSP_LEVELS_PC;
|
||||
|
||||
// The majority of RGB content is either sRGB or (rarely) some other
|
||||
// color space which we don't even handle, like AdobeRGB or
|
||||
// ProPhotoRGB. The only reasonable thing we can do is assume it's
|
||||
// sRGB and hope for the best, which should usually just work out fine.
|
||||
// Note: sRGB primaries = BT.709 primaries
|
||||
if (params->primaries == MP_CSP_PRIM_AUTO)
|
||||
params->primaries = MP_CSP_PRIM_BT_709;
|
||||
if (params->gamma == MP_CSP_TRC_AUTO)
|
||||
params->gamma = MP_CSP_TRC_SRGB;
|
||||
if (params->color.primaries == MP_CSP_PRIM_AUTO)
|
||||
params->color.primaries = MP_CSP_PRIM_BT_709;
|
||||
if (params->color.gamma == MP_CSP_TRC_AUTO)
|
||||
params->color.gamma = MP_CSP_TRC_SRGB;
|
||||
} else if (fmt.flags & MP_IMGFLAG_XYZ) {
|
||||
params->colorspace = MP_CSP_XYZ;
|
||||
params->colorlevels = MP_CSP_LEVELS_PC;
|
||||
params->color.space = MP_CSP_XYZ;
|
||||
params->color.levels = MP_CSP_LEVELS_PC;
|
||||
|
||||
// The default XYZ matrix converts it to BT.709 color space
|
||||
// since that's the most likely scenario. Proper VOs should ignore
|
||||
@@ -652,22 +655,22 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
|
||||
// gamut for VOs which *do* use the specialized XYZ matrix but don't
|
||||
// know any better output gamut other than whatever the source is
|
||||
// tagged with.
|
||||
if (params->primaries == MP_CSP_PRIM_AUTO)
|
||||
params->primaries = MP_CSP_PRIM_BT_709;
|
||||
if (params->gamma == MP_CSP_TRC_AUTO)
|
||||
params->gamma = MP_CSP_TRC_LINEAR;
|
||||
if (params->color.primaries == MP_CSP_PRIM_AUTO)
|
||||
params->color.primaries = MP_CSP_PRIM_BT_709;
|
||||
if (params->color.gamma == MP_CSP_TRC_AUTO)
|
||||
params->color.gamma = MP_CSP_TRC_LINEAR;
|
||||
} else {
|
||||
// We have no clue.
|
||||
params->colorspace = MP_CSP_AUTO;
|
||||
params->colorlevels = MP_CSP_LEVELS_AUTO;
|
||||
params->primaries = MP_CSP_PRIM_AUTO;
|
||||
params->gamma = MP_CSP_TRC_AUTO;
|
||||
params->color.space = MP_CSP_AUTO;
|
||||
params->color.levels = MP_CSP_LEVELS_AUTO;
|
||||
params->color.primaries = MP_CSP_PRIM_AUTO;
|
||||
params->color.gamma = MP_CSP_TRC_AUTO;
|
||||
}
|
||||
|
||||
// Guess the reference peak (independent of the colorspace)
|
||||
if (params->gamma == MP_CSP_TRC_SMPTE_ST2084) {
|
||||
if (!params->peak)
|
||||
params->peak = 10000; // As per the spec
|
||||
// Guess the nominal peak (independent of the colorspace)
|
||||
if (params->color.gamma == MP_CSP_TRC_SMPTE_ST2084) {
|
||||
if (!params->color.nom_peak)
|
||||
params->color.nom_peak = 10000; // As per the spec
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,8 +727,8 @@ static void mp_image_copy_fields_to_av_frame(struct AVFrame *dst,
|
||||
if (src->fields & MP_IMGFIELD_REPEAT_FIRST)
|
||||
dst->repeat_pict = 1;
|
||||
|
||||
dst->colorspace = mp_csp_to_avcol_spc(src->params.colorspace);
|
||||
dst->color_range = mp_csp_levels_to_avcol_range(src->params.colorlevels);
|
||||
dst->colorspace = mp_csp_to_avcol_spc(src->params.color.space);
|
||||
dst->color_range = mp_csp_levels_to_avcol_range(src->params.color.levels);
|
||||
}
|
||||
|
||||
// Create a new mp_image reference to av_frame.
|
||||
|
||||
+1
-5
@@ -43,11 +43,7 @@ struct mp_image_params {
|
||||
// (will use the HW API's format identifiers)
|
||||
int w, h; // image dimensions
|
||||
int p_w, p_h; // define pixel aspect ratio (undefined: 0/0)
|
||||
enum mp_csp colorspace;
|
||||
enum mp_csp_levels colorlevels;
|
||||
enum mp_csp_prim primaries;
|
||||
enum mp_csp_trc gamma;
|
||||
float peak; // 0 = auto/unknown
|
||||
struct mp_colorspace color;
|
||||
enum mp_chroma_location chroma_location;
|
||||
// The image should be rotated clockwise (0-359 degrees).
|
||||
int rotate;
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
#include "mpv_talloc.h"
|
||||
#include "bitmap_packer.h"
|
||||
#include "common/common.h"
|
||||
#include "sub/dec_sub.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
#define IS_POWER_OF_2(x) (((x) > 0) && !(((x) - 1) & (x)))
|
||||
|
||||
@@ -199,34 +197,3 @@ void packer_set_size(struct bitmap_packer *packer, int size)
|
||||
packer->scratch = talloc_array_ptrtype(packer, packer->scratch,
|
||||
packer->asize + 16);
|
||||
}
|
||||
|
||||
int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
|
||||
struct sub_bitmaps *b)
|
||||
{
|
||||
packer->count = 0;
|
||||
if (b->format == SUBBITMAP_EMPTY)
|
||||
return 0;
|
||||
packer_set_size(packer, b->num_parts);
|
||||
for (int i = 0; i < b->num_parts; i++)
|
||||
packer->in[i] = (struct pos){b->parts[i].w, b->parts[i].h};
|
||||
return packer_pack(packer);
|
||||
}
|
||||
|
||||
void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b,
|
||||
void *data, int pixel_stride, int stride)
|
||||
{
|
||||
assert(packer->count == b->num_parts);
|
||||
if (packer->padding) {
|
||||
struct pos bb[2];
|
||||
packer_get_bb(packer, bb);
|
||||
memset_pic(data, 0, bb[1].x * pixel_stride, bb[1].y, stride);
|
||||
}
|
||||
for (int n = 0; n < packer->count; n++) {
|
||||
struct sub_bitmap *s = &b->parts[n];
|
||||
struct pos p = packer->result[n];
|
||||
|
||||
void *pdata = (uint8_t *)data + p.y * stride + p.x * pixel_stride;
|
||||
memcpy_pic(pdata, s->bitmap, s->w * pixel_stride, s->h,
|
||||
stride, s->stride);
|
||||
}
|
||||
}
|
||||
@@ -48,20 +48,4 @@ void packer_set_size(struct bitmap_packer *packer, int size);
|
||||
*/
|
||||
int packer_pack(struct bitmap_packer *packer);
|
||||
|
||||
/* Like above, but packer->count will be automatically set and
|
||||
* packer->in will be reallocated if needed and filled from the
|
||||
* given image list.
|
||||
*/
|
||||
int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
|
||||
struct sub_bitmaps *b);
|
||||
|
||||
// Copy the (already packed) sub-bitmaps from b to the image in data.
|
||||
// data must point to an image that is at least (packer->w, packer->h) big.
|
||||
// The image has the given stride (bytes between (x, y) to (x, y + 1)), and the
|
||||
// pixel format used by both the sub-bitmaps and the image uses pixel_stride
|
||||
// bytes per pixel (bytes between (x, y) to (x + 1, y)).
|
||||
// If packer->padding is set, the padding borders are cleared with 0.
|
||||
void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b,
|
||||
void *data, int pixel_stride, int stride);
|
||||
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "angle_common.h"
|
||||
|
||||
// Test if Direct3D11 can be used by us. Basically, this prevents trying to use
|
||||
// D3D11 on Win7, and then failing somewhere in the process.
|
||||
bool d3d11_check_decoding(ID3D11Device *dev)
|
||||
{
|
||||
HRESULT hr;
|
||||
// We assume that NV12 is always supported, if hw decoding is supported at
|
||||
// all.
|
||||
UINT supported = 0;
|
||||
hr = ID3D11Device_CheckFormatSupport(dev, DXGI_FORMAT_NV12, &supported);
|
||||
return !FAILED(hr) && (supported & D3D11_BIND_DECODER);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef MP_ANGLE_COMMON_H
|
||||
#define MP_ANGLE_COMMON_H
|
||||
|
||||
#include <initguid.h>
|
||||
#include <assert.h>
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool d3d11_check_decoding(ID3D11Device *dev);
|
||||
|
||||
#endif
|
||||
@@ -38,6 +38,7 @@ struct priv {
|
||||
EGLContext egl_context;
|
||||
EGLSurface egl_surface;
|
||||
bool use_es2;
|
||||
PFNEGLPOSTSUBBUFFERNVPROC eglPostSubBufferNV;
|
||||
};
|
||||
|
||||
static void angle_uninit(MPGLContext *ctx)
|
||||
@@ -288,6 +289,11 @@ static int angle_init(struct MPGLContext *ctx, int flags)
|
||||
// Configure the underlying Direct3D device
|
||||
d3d_init(ctx);
|
||||
|
||||
if (strstr(exts, "EGL_NV_post_sub_buffer")) {
|
||||
p->eglPostSubBufferNV =
|
||||
(PFNEGLPOSTSUBBUFFERNVPROC)eglGetProcAddress("eglPostSubBufferNV");
|
||||
}
|
||||
|
||||
mpgl_load_functions(ctx->gl, get_proc_address, NULL, vo->log);
|
||||
return 0;
|
||||
|
||||
@@ -315,7 +321,16 @@ static int angle_reconfig(struct MPGLContext *ctx)
|
||||
|
||||
static int angle_control(MPGLContext *ctx, int *events, int request, void *arg)
|
||||
{
|
||||
return vo_w32_control(ctx->vo, events, request, arg);
|
||||
struct priv *p = ctx->priv;
|
||||
int r = vo_w32_control(ctx->vo, events, request, arg);
|
||||
|
||||
// Calling eglPostSubBufferNV with a 0-sized region doesn't present a frame
|
||||
// or block, but it does update the swapchain to match the window size
|
||||
// See: https://groups.google.com/d/msg/angleproject/RvyVkjRCQGU/gfKfT64IAgAJ
|
||||
if ((*events & VO_EVENT_RESIZE) && p->eglPostSubBufferNV)
|
||||
p->eglPostSubBufferNV(p->egl_display, p->egl_surface, 0, 0, 0, 0);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void angle_swap_buffers(MPGLContext *ctx)
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "angle_common.h"
|
||||
#include "angle_dynamic.h"
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -31,6 +30,7 @@
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "hwdec.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/decode/d3d.h"
|
||||
|
||||
#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
|
||||
#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x3AAB
|
||||
@@ -195,6 +195,7 @@ static int create(struct gl_hwdec *hw)
|
||||
.type = HWDEC_D3D11VA,
|
||||
.driver_name = hw->driver->name,
|
||||
.ctx = p->d3d11_device,
|
||||
.download_image = d3d11_download_image,
|
||||
};
|
||||
hwdec_devices_add(hw->devs, &p->hwctx);
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "angle_common.h"
|
||||
#include "angle_dynamic.h"
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -31,6 +30,7 @@
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "hwdec.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/decode/d3d.h"
|
||||
|
||||
#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
|
||||
#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x3AAB
|
||||
@@ -87,6 +87,8 @@ static int create(struct gl_hwdec *hw)
|
||||
if (!angle_load())
|
||||
return -1;
|
||||
|
||||
d3d_load_dlls();
|
||||
|
||||
EGLDisplay egl_display = eglGetCurrentDisplay();
|
||||
if (!egl_display)
|
||||
return -1;
|
||||
@@ -104,7 +106,6 @@ static int create(struct gl_hwdec *hw)
|
||||
|
||||
p->egl_display = egl_display;
|
||||
|
||||
HANDLE d3d11_dll = GetModuleHandleW(L"d3d11.dll");
|
||||
if (!d3d11_dll) {
|
||||
if (!hw->probing)
|
||||
MP_ERR(hw, "Failed to load D3D11 library\n");
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "hwdec.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/decode/d3d.h"
|
||||
|
||||
struct priv {
|
||||
struct mp_hwdec_ctx hwctx;
|
||||
|
||||
HMODULE d3d9_dll;
|
||||
IDirect3D9Ex *d3d9ex;
|
||||
IDirect3DDevice9Ex *device9ex;
|
||||
IDirect3DQuery9 *query9;
|
||||
@@ -89,9 +89,6 @@ static void destroy(struct gl_hwdec *hw)
|
||||
|
||||
if (p->d3d9ex)
|
||||
IDirect3D9Ex_Release(p->d3d9ex);
|
||||
|
||||
if (p->d3d9_dll)
|
||||
FreeLibrary(p->d3d9_dll);
|
||||
}
|
||||
|
||||
static int create(struct gl_hwdec *hw)
|
||||
@@ -99,6 +96,8 @@ static int create(struct gl_hwdec *hw)
|
||||
if (!angle_load())
|
||||
return -1;
|
||||
|
||||
d3d_load_dlls();
|
||||
|
||||
EGLDisplay egl_display = eglGetCurrentDisplay();
|
||||
if (!egl_display)
|
||||
return -1;
|
||||
@@ -118,15 +117,14 @@ static int create(struct gl_hwdec *hw)
|
||||
|
||||
p->egl_display = egl_display;
|
||||
|
||||
p->d3d9_dll = LoadLibraryW(L"d3d9.dll");
|
||||
if (!p->d3d9_dll) {
|
||||
if (!d3d9_dll) {
|
||||
MP_FATAL(hw, "Failed to load \"d3d9.dll\": %s\n",
|
||||
mp_LastError_to_str());
|
||||
goto fail;
|
||||
}
|
||||
|
||||
HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D);
|
||||
Direct3DCreate9Ex = (void *)GetProcAddress(p->d3d9_dll, "Direct3DCreate9Ex");
|
||||
Direct3DCreate9Ex = (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
|
||||
if (!Direct3DCreate9Ex) {
|
||||
MP_FATAL(hw, "Direct3D 9Ex not supported\n");
|
||||
goto fail;
|
||||
|
||||
@@ -185,7 +185,7 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
|
||||
0, 0, hw_image->w, hw_image->h,
|
||||
0, 0, hw_image->w, hw_image->h,
|
||||
NULL, 0,
|
||||
va_get_colorspace_flag(hw_image->params.colorspace));
|
||||
va_get_colorspace_flag(hw_image->params.color.space));
|
||||
CHECK_VA_STATUS(p, "vaPutSurface()");
|
||||
va_unlock(p->ctx);
|
||||
|
||||
|
||||
+26
-146
@@ -21,8 +21,6 @@
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "video/out/bitmap_packer.h"
|
||||
|
||||
#include "formats.h"
|
||||
#include "utils.h"
|
||||
#include "osd.h"
|
||||
@@ -53,20 +51,17 @@ struct mpgl_osd_part {
|
||||
int change_id;
|
||||
GLuint texture;
|
||||
int w, h;
|
||||
GLuint buffer;
|
||||
struct gl_pbo_upload pbo;
|
||||
int num_subparts;
|
||||
int prev_num_subparts;
|
||||
struct sub_bitmap *subparts;
|
||||
struct vertex *vertices;
|
||||
struct bitmap_packer *packer;
|
||||
void *upload;
|
||||
};
|
||||
|
||||
struct mpgl_osd {
|
||||
struct mp_log *log;
|
||||
struct osd_state *osd;
|
||||
GL *gl;
|
||||
GLint max_tex_wh;
|
||||
bool use_pbo;
|
||||
struct mpgl_osd_part *parts[MAX_OSD_PARTS];
|
||||
const struct gl_format *fmt_table[SUBBITMAP_COUNT];
|
||||
@@ -89,21 +84,11 @@ struct mpgl_osd *mpgl_osd_init(GL *gl, struct mp_log *log, struct osd_state *osd
|
||||
.scratch = talloc_zero_size(ctx, 1),
|
||||
};
|
||||
|
||||
gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &ctx->max_tex_wh);
|
||||
|
||||
ctx->fmt_table[SUBBITMAP_LIBASS] = gl_find_unorm_format(gl, 1, 1);
|
||||
ctx->fmt_table[SUBBITMAP_RGBA] = gl_find_unorm_format(gl, 1, 4);
|
||||
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
||||
struct mpgl_osd_part *p = talloc_ptrtype(ctx, p);
|
||||
*p = (struct mpgl_osd_part) {
|
||||
.packer = talloc_struct(p, struct bitmap_packer, {
|
||||
.w_max = ctx->max_tex_wh,
|
||||
.h_max = ctx->max_tex_wh,
|
||||
}),
|
||||
};
|
||||
ctx->parts[n] = p;
|
||||
}
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++)
|
||||
ctx->parts[n] = talloc_zero(ctx, struct mpgl_osd_part);
|
||||
|
||||
for (int n = 0; n < SUBBITMAP_COUNT; n++)
|
||||
ctx->formats[n] = !!ctx->fmt_table[n];
|
||||
@@ -125,9 +110,7 @@ void mpgl_osd_destroy(struct mpgl_osd *ctx)
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
||||
struct mpgl_osd_part *p = ctx->parts[n];
|
||||
gl->DeleteTextures(1, &p->texture);
|
||||
if (gl->DeleteBuffers)
|
||||
gl->DeleteBuffers(1, &p->buffer);
|
||||
talloc_free(p->upload);
|
||||
gl_pbo_upload_uninit(&p->pbo);
|
||||
}
|
||||
talloc_free(ctx);
|
||||
}
|
||||
@@ -137,87 +120,6 @@ void mpgl_osd_set_options(struct mpgl_osd *ctx, bool pbo)
|
||||
ctx->use_pbo = pbo;
|
||||
}
|
||||
|
||||
static bool upload(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
||||
struct sub_bitmaps *imgs, bool pbo)
|
||||
{
|
||||
GL *gl = ctx->gl;
|
||||
bool success = true;
|
||||
const struct gl_format *fmt = ctx->fmt_table[imgs->format];
|
||||
size_t pix_stride = gl_bytes_per_pixel(fmt->format, fmt->type);
|
||||
size_t buffer_size = pix_stride * osd->h * osd->w;
|
||||
|
||||
char *data = NULL;
|
||||
void *texdata = NULL;
|
||||
|
||||
if (pbo) {
|
||||
if (!osd->buffer) {
|
||||
gl->GenBuffers(1, &osd->buffer);
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
|
||||
gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL,
|
||||
GL_DYNAMIC_COPY);
|
||||
}
|
||||
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
|
||||
data = gl->MapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, buffer_size,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
if (!data) {
|
||||
success = false;
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
if (!imgs->packed) {
|
||||
if (!osd->upload)
|
||||
osd->upload = talloc_size(NULL, buffer_size);
|
||||
data = osd->upload;
|
||||
texdata = data;
|
||||
}
|
||||
}
|
||||
|
||||
int copy_w = 0;
|
||||
int copy_h = 0;
|
||||
size_t stride = 0;
|
||||
if (imgs->packed) {
|
||||
copy_w = imgs->packed_w;
|
||||
copy_h = imgs->packed_h;
|
||||
stride = imgs->packed->stride[0];
|
||||
texdata = imgs->packed->planes[0];
|
||||
if (pbo) {
|
||||
memcpy_pic(data, texdata, pix_stride * copy_w, copy_h,
|
||||
osd->w * pix_stride, stride);
|
||||
stride = osd->w * pix_stride;
|
||||
texdata = NULL;
|
||||
}
|
||||
} else {
|
||||
struct pos bb[2];
|
||||
packer_get_bb(osd->packer, bb);
|
||||
copy_w = bb[1].x;
|
||||
copy_h = bb[1].y;
|
||||
stride = osd->w * pix_stride;
|
||||
packer_copy_subbitmaps(osd->packer, imgs, data, pix_stride, stride);
|
||||
}
|
||||
|
||||
if (pbo) {
|
||||
if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) {
|
||||
success = false;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
gl_upload_tex(gl, GL_TEXTURE_2D, fmt->format, fmt->type, texdata, stride,
|
||||
0, 0, copy_w, copy_h);
|
||||
|
||||
if (pbo)
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
done:
|
||||
if (!success) {
|
||||
MP_FATAL(ctx, "Error: can't upload subtitles! "
|
||||
"Remove the 'pbo' suboption.\n");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static int next_pow2(int v)
|
||||
{
|
||||
for (int x = 0; x < 30; x++) {
|
||||
@@ -231,31 +133,12 @@ static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
||||
struct sub_bitmaps *imgs)
|
||||
{
|
||||
GL *gl = ctx->gl;
|
||||
bool ok = false;
|
||||
|
||||
int req_w = 0;
|
||||
int req_h = 0;
|
||||
assert(imgs->packed);
|
||||
|
||||
if (imgs->packed) {
|
||||
req_w = next_pow2(imgs->packed_w);
|
||||
req_h = next_pow2(imgs->packed_h);
|
||||
} else {
|
||||
// assume 2x2 filter on scaling
|
||||
osd->packer->padding = imgs->scaled;
|
||||
int r = packer_pack_from_subbitmaps(osd->packer, imgs);
|
||||
if (r < 0) {
|
||||
MP_ERR(ctx, "OSD bitmaps do not fit on a surface with the maximum "
|
||||
"supported size %dx%d.\n", osd->packer->w_max, osd->packer->h_max);
|
||||
return false;
|
||||
}
|
||||
req_w = osd->packer->w;
|
||||
req_h = osd->packer->h;
|
||||
}
|
||||
|
||||
if (req_w > ctx->max_tex_wh || req_h > ctx->max_tex_wh) {
|
||||
MP_ERR(ctx, "OSD bitmaps do not fit on a surface with the maximum "
|
||||
"supported size %dx%d.\n", ctx->max_tex_wh, ctx->max_tex_wh);
|
||||
return false;
|
||||
}
|
||||
int req_w = next_pow2(imgs->packed_w);
|
||||
int req_h = next_pow2(imgs->packed_h);
|
||||
|
||||
const struct gl_format *fmt = ctx->fmt_table[imgs->format];
|
||||
assert(fmt);
|
||||
@@ -270,6 +153,17 @@ static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
||||
osd->w = FFMAX(32, req_w);
|
||||
osd->h = FFMAX(32, req_h);
|
||||
|
||||
MP_VERBOSE(ctx, "Reallocating OSD texture to %dx%d.\n", osd->w, osd->h);
|
||||
|
||||
GLint max_wh;
|
||||
gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_wh);
|
||||
|
||||
if (osd->w > max_wh || osd->h > max_wh) {
|
||||
MP_ERR(ctx, "OSD bitmaps do not fit on a surface with the maximum "
|
||||
"supported size %dx%d.\n", max_wh, max_wh);
|
||||
goto done;
|
||||
}
|
||||
|
||||
gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, osd->w, osd->h,
|
||||
0, fmt->format, fmt->type, NULL);
|
||||
|
||||
@@ -277,24 +171,17 @@ static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
if (gl->DeleteBuffers)
|
||||
gl->DeleteBuffers(1, &osd->buffer);
|
||||
osd->buffer = 0;
|
||||
|
||||
talloc_free(osd->upload);
|
||||
osd->upload = NULL;
|
||||
}
|
||||
|
||||
bool uploaded = false;
|
||||
if (ctx->use_pbo)
|
||||
uploaded = upload(ctx, osd, imgs, true);
|
||||
if (!uploaded)
|
||||
upload(ctx, osd, imgs, false);
|
||||
gl_pbo_upload_tex(&osd->pbo, gl, ctx->use_pbo, GL_TEXTURE_2D, fmt->format,
|
||||
fmt->type, osd->w, osd->h, imgs->packed->planes[0],
|
||||
imgs->packed->stride[0], 0, 0,
|
||||
imgs->packed_w, imgs->packed_h);
|
||||
ok = true;
|
||||
|
||||
done:
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
return true;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void gen_osd_cb(void *pctx, struct sub_bitmaps *imgs)
|
||||
@@ -319,13 +206,6 @@ static void gen_osd_cb(void *pctx, struct sub_bitmaps *imgs)
|
||||
MP_TARRAY_GROW(osd, osd->subparts, osd->num_subparts);
|
||||
memcpy(osd->subparts, imgs->parts,
|
||||
osd->num_subparts * sizeof(osd->subparts[0]));
|
||||
|
||||
if (!imgs->packed) {
|
||||
for (int n = 0; n < osd->num_subparts; n++) {
|
||||
osd->subparts[n].src_x = osd->packer->result[n].x;
|
||||
osd->subparts[n].src_y = osd->packer->result[n].y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void write_quad(struct vertex *va, struct gl_transform t,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "user_shaders.h"
|
||||
|
||||
@@ -69,6 +70,94 @@ static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE])
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns whether successful. 'result' is left untouched on failure
|
||||
bool eval_szexpr(struct mp_log *log, void *priv,
|
||||
bool (*lookup)(void *priv, struct bstr var, float size[2]),
|
||||
struct szexp expr[MAX_SZEXP_SIZE], float *result)
|
||||
{
|
||||
float stack[MAX_SZEXP_SIZE] = {0};
|
||||
int idx = 0; // points to next element to push
|
||||
|
||||
for (int i = 0; i < MAX_SZEXP_SIZE; i++) {
|
||||
switch (expr[i].tag) {
|
||||
case SZEXP_END:
|
||||
goto done;
|
||||
|
||||
case SZEXP_CONST:
|
||||
// Since our SZEXPs are bound by MAX_SZEXP_SIZE, it should be
|
||||
// impossible to overflow the stack
|
||||
assert(idx < MAX_SZEXP_SIZE);
|
||||
stack[idx++] = expr[i].val.cval;
|
||||
continue;
|
||||
|
||||
case SZEXP_OP1:
|
||||
if (idx < 1) {
|
||||
mp_warn(log, "Stack underflow in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (expr[i].val.op) {
|
||||
case SZEXP_OP_NOT: stack[idx-1] = !stack[idx-1]; break;
|
||||
default: abort();
|
||||
}
|
||||
continue;
|
||||
|
||||
case SZEXP_OP2:
|
||||
if (idx < 2) {
|
||||
mp_warn(log, "Stack underflow in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pop the operands in reverse order
|
||||
float op2 = stack[--idx];
|
||||
float op1 = stack[--idx];
|
||||
float res = 0.0;
|
||||
switch (expr[i].val.op) {
|
||||
case SZEXP_OP_ADD: res = op1 + op2; break;
|
||||
case SZEXP_OP_SUB: res = op1 - op2; break;
|
||||
case SZEXP_OP_MUL: res = op1 * op2; break;
|
||||
case SZEXP_OP_DIV: res = op1 / op2; break;
|
||||
case SZEXP_OP_GT: res = op1 > op2; break;
|
||||
case SZEXP_OP_LT: res = op1 < op2; break;
|
||||
default: abort();
|
||||
}
|
||||
|
||||
if (!isfinite(res)) {
|
||||
mp_warn(log, "Illegal operation in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stack[idx++] = res;
|
||||
continue;
|
||||
|
||||
case SZEXP_VAR_W:
|
||||
case SZEXP_VAR_H: {
|
||||
struct bstr name = expr[i].val.varname;
|
||||
float size[2];
|
||||
|
||||
if (!lookup(priv, name, size)) {
|
||||
mp_warn(log, "Variable %.*s not found in RPN expression!\n",
|
||||
BSTR_P(name));
|
||||
return false;
|
||||
}
|
||||
|
||||
stack[idx++] = (expr[i].tag == SZEXP_VAR_W) ? size[0] : size[1];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
// Return the single stack element
|
||||
if (idx != 1) {
|
||||
mp_warn(log, "Malformed stack after RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = stack[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns false if no more shaders could be parsed
|
||||
bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
|
||||
struct gl_user_shader *out)
|
||||
|
||||
@@ -71,4 +71,9 @@ struct gl_user_shader {
|
||||
bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
|
||||
struct gl_user_shader *out);
|
||||
|
||||
// Evaluate a szexp, given a lookup function for named textures
|
||||
bool eval_szexpr(struct mp_log *log, void *priv,
|
||||
bool (*lookup)(void *priv, struct bstr var, float size[2]),
|
||||
struct szexp expr[MAX_SZEXP_SIZE], float *result);
|
||||
|
||||
#endif
|
||||
@@ -109,8 +109,10 @@ mp_image_t *gl_read_window_contents(GL *gl)
|
||||
mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, vp[2], vp[3]);
|
||||
if (!image)
|
||||
return NULL;
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, gl->main_fb);
|
||||
GLenum obj = gl->main_fb ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
|
||||
gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
gl->ReadBuffer(GL_FRONT);
|
||||
gl->ReadBuffer(obj);
|
||||
//flip image while reading (and also avoid stride-related trouble)
|
||||
for (int y = 0; y < vp[3]; y++) {
|
||||
gl->ReadPixels(vp[0], vp[1] + vp[3] - y - 1, vp[2], 1,
|
||||
@@ -118,6 +120,7 @@ mp_image_t *gl_read_window_contents(GL *gl)
|
||||
image->planes[0] + y * image->stride[0]);
|
||||
}
|
||||
gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -1121,3 +1124,73 @@ void gl_timer_stop(struct gl_timer *timer)
|
||||
if (gl->EndQuery)
|
||||
gl->EndQuery(GL_TIME_ELAPSED);
|
||||
}
|
||||
|
||||
// Upload a texture, going through a PBO. PBO supposedly can facilitate
|
||||
// asynchronous copy from CPU to GPU, so this is an optimization. Note that
|
||||
// changing format/type/tex_w/tex_h or reusing the PBO in the same frame can
|
||||
// ruin performance.
|
||||
// This call is like gl_upload_tex(), plus PBO management/use.
|
||||
// target, format, type, dataptr, stride, x, y, w, h: texture upload params
|
||||
// (see gl_upload_tex())
|
||||
// tex_w, tex_h: maximum size of the used texture
|
||||
// use_pbo: for convenience, if false redirects the call to gl_upload_tex
|
||||
void gl_pbo_upload_tex(struct gl_pbo_upload *pbo, GL *gl, bool use_pbo,
|
||||
GLenum target, GLenum format, GLenum type,
|
||||
int tex_w, int tex_h, const void *dataptr, int stride,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
assert(x >= 0 && y >= 0 && w >= 0 && h >= 0);
|
||||
assert(x + w <= tex_w && y + h <= tex_h);
|
||||
|
||||
if (!use_pbo || !gl->MapBufferRange)
|
||||
goto no_pbo;
|
||||
|
||||
size_t pix_stride = gl_bytes_per_pixel(format, type);
|
||||
size_t buffer_size = pix_stride * tex_w * tex_h;
|
||||
size_t needed_size = pix_stride * w * h;
|
||||
|
||||
if (buffer_size != pbo->buffer_size)
|
||||
gl_pbo_upload_uninit(pbo);
|
||||
|
||||
if (!pbo->buffers[0]) {
|
||||
pbo->gl = gl;
|
||||
pbo->buffer_size = buffer_size;
|
||||
gl->GenBuffers(2, &pbo->buffers[0]);
|
||||
for (int n = 0; n < 2; n++) {
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo->buffers[n]);
|
||||
gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL,
|
||||
GL_DYNAMIC_COPY);
|
||||
}
|
||||
}
|
||||
|
||||
pbo->index = (pbo->index + 1) % 2;
|
||||
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo->buffers[pbo->index]);
|
||||
void *data = gl->MapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, needed_size,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
if (!data)
|
||||
goto no_pbo;
|
||||
|
||||
memcpy_pic(data, dataptr, pix_stride * w, h, pix_stride * w, stride);
|
||||
|
||||
if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) {
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
goto no_pbo;
|
||||
}
|
||||
|
||||
gl_upload_tex(gl, target, format, type, NULL, pix_stride * w, x, y, w, h);
|
||||
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
return;
|
||||
|
||||
no_pbo:
|
||||
gl_upload_tex(gl, target, format, type, dataptr, stride, x, y, w, h);
|
||||
}
|
||||
|
||||
void gl_pbo_upload_uninit(struct gl_pbo_upload *pbo)
|
||||
{
|
||||
if (pbo->gl)
|
||||
pbo->gl->DeleteBuffers(2, &pbo->buffers[0]);
|
||||
*pbo = (struct gl_pbo_upload){0};
|
||||
}
|
||||
@@ -182,4 +182,17 @@ uint64_t gl_timer_last_us(struct gl_timer *timer);
|
||||
uint64_t gl_timer_avg_us(struct gl_timer *timer);
|
||||
uint64_t gl_timer_peak_us(struct gl_timer *timer);
|
||||
|
||||
struct gl_pbo_upload {
|
||||
GL *gl;
|
||||
int index;
|
||||
GLuint buffers[2];
|
||||
size_t buffer_size;
|
||||
};
|
||||
|
||||
void gl_pbo_upload_tex(struct gl_pbo_upload *pbo, GL *gl, bool use_pbo,
|
||||
GLenum target, GLenum format, GLenum type,
|
||||
int tex_w, int tex_h, const void *dataptr, int stride,
|
||||
int x, int y, int w, int h);
|
||||
void gl_pbo_upload_uninit(struct gl_pbo_upload *pbo);
|
||||
|
||||
#endif
|
||||
+155
-281
@@ -41,7 +41,6 @@
|
||||
#include "user_shaders.h"
|
||||
#include "video/out/filter_kernels.h"
|
||||
#include "video/out/aspect.h"
|
||||
#include "video/out/bitmap_packer.h"
|
||||
#include "video/out/dither.h"
|
||||
#include "video/out/vo.h"
|
||||
|
||||
@@ -97,13 +96,13 @@ struct texplane {
|
||||
GLenum gl_format;
|
||||
GLenum gl_type;
|
||||
GLuint gl_texture;
|
||||
int gl_buffer;
|
||||
char swizzle[5];
|
||||
bool flipped;
|
||||
struct gl_pbo_upload pbo;
|
||||
};
|
||||
|
||||
struct video_image {
|
||||
struct texplane planes[4];
|
||||
bool image_flipped;
|
||||
struct mp_image *mpi; // original input image
|
||||
bool hwdec_mapped;
|
||||
};
|
||||
@@ -676,7 +675,7 @@ static int pass_bind(struct gl_video *p, struct img_tex tex)
|
||||
}
|
||||
|
||||
// Rotation by 90° and flipping.
|
||||
static void get_plane_source_transform(struct gl_video *p, int w, int h,
|
||||
static void get_plane_source_transform(struct gl_video *p, struct texplane *t,
|
||||
struct gl_transform *out_tr)
|
||||
{
|
||||
struct gl_transform tr = identity_trans;
|
||||
@@ -689,11 +688,11 @@ static void get_plane_source_transform(struct gl_video *p, int w, int h,
|
||||
// basically, recenter to keep the whole image in view
|
||||
float b[2] = {1, 1};
|
||||
gl_transform_vec(rot, &b[0], &b[1]);
|
||||
tr.t[0] += b[0] < 0 ? w : 0;
|
||||
tr.t[1] += b[1] < 0 ? h : 0;
|
||||
tr.t[0] += b[0] < 0 ? t->w : 0;
|
||||
tr.t[1] += b[1] < 0 ? t->h : 0;
|
||||
|
||||
if (p->image.image_flipped) {
|
||||
struct gl_transform flip = {{{1, 0}, {0, -1}}, {0, h}};
|
||||
if (t->flipped) {
|
||||
struct gl_transform flip = {{{1, 0}, {0, -1}}, {0, t->h}};
|
||||
gl_transform_trans(flip, &tr);
|
||||
}
|
||||
|
||||
@@ -730,7 +729,7 @@ static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
|
||||
|
||||
// The existing code assumes we just have a single tex multiplier for
|
||||
// all of the planes. This may change in the future
|
||||
float tex_mul = 1.0 / mp_get_csp_mul(p->image_params.colorspace,
|
||||
float tex_mul = 1.0 / mp_get_csp_mul(p->image_params.color.space,
|
||||
p->image_desc.component_bits,
|
||||
p->image_desc.component_full_bits);
|
||||
|
||||
@@ -764,7 +763,7 @@ static void pass_get_img_tex(struct gl_video *p, struct video_image *vimg,
|
||||
.components = p->image_desc.components[n],
|
||||
};
|
||||
snprintf(tex[n].swizzle, sizeof(tex[n].swizzle), "%s", t->swizzle);
|
||||
get_plane_source_transform(p, t->w, t->h, &tex[n].transform);
|
||||
get_plane_source_transform(p, t, &tex[n].transform);
|
||||
if (p->image_params.rotate % 180 == 90)
|
||||
MPSWAP(int, tex[n].w, tex[n].h);
|
||||
|
||||
@@ -794,7 +793,7 @@ static void init_video(struct gl_video *p)
|
||||
mp_image_params_guess_csp(&p->image_params);
|
||||
|
||||
int eq_caps = MP_CSP_EQ_CAPS_GAMMA;
|
||||
if (p->image_params.colorspace != MP_CSP_BT_2020_C)
|
||||
if (p->image_params.color.space != MP_CSP_BT_2020_C)
|
||||
eq_caps |= MP_CSP_EQ_CAPS_COLORMATRIX;
|
||||
if (p->image_desc.flags & MP_IMGFLAG_XYZ)
|
||||
eq_caps |= MP_CSP_EQ_CAPS_BRIGHTNESS;
|
||||
@@ -879,7 +878,7 @@ static void uninit_video(struct gl_video *p)
|
||||
struct texplane *plane = &vimg->planes[n];
|
||||
|
||||
gl->DeleteTextures(1, &plane->gl_texture);
|
||||
gl->DeleteBuffers(1, &plane->gl_buffer);
|
||||
gl_pbo_upload_uninit(&plane->pbo);
|
||||
}
|
||||
*vimg = (struct video_image){0};
|
||||
|
||||
@@ -1239,6 +1238,9 @@ static void load_shader(struct gl_video *p, struct bstr body)
|
||||
gl_sc_uniform_f(p->sc, "frame", p->frames_uploaded);
|
||||
gl_sc_uniform_vec2(p->sc, "image_size", (GLfloat[]){p->image_params.w,
|
||||
p->image_params.h});
|
||||
gl_sc_uniform_vec2(p->sc, "target_size",
|
||||
(GLfloat[]){p->dst_rect.x1 - p->dst_rect.x0,
|
||||
p->dst_rect.y1 - p->dst_rect.y0});
|
||||
}
|
||||
|
||||
static const char *get_custom_shader_fn(struct gl_video *p, const char *body)
|
||||
@@ -1542,112 +1544,40 @@ static void user_hook_old(struct gl_video *p, struct img_tex tex,
|
||||
GLSLF("color = %s(HOOKED_raw, HOOKED_pos, HOOKED_size);\n", fn_name);
|
||||
}
|
||||
|
||||
// Returns whether successful. 'result' is left untouched on failure
|
||||
static bool eval_szexpr(struct gl_video *p, struct img_tex tex,
|
||||
struct szexp expr[MAX_SZEXP_SIZE],
|
||||
float *result)
|
||||
struct szexp_ctx {
|
||||
struct gl_video *p;
|
||||
struct img_tex tex;
|
||||
};
|
||||
|
||||
static bool szexp_lookup(void *priv, struct bstr var, float size[2])
|
||||
{
|
||||
float stack[MAX_SZEXP_SIZE] = {0};
|
||||
int idx = 0; // points to next element to push
|
||||
struct szexp_ctx *ctx = priv;
|
||||
struct gl_video *p = ctx->p;
|
||||
|
||||
for (int i = 0; i < MAX_SZEXP_SIZE; i++) {
|
||||
switch (expr[i].tag) {
|
||||
case SZEXP_END:
|
||||
goto done;
|
||||
// The size of OUTPUT is determined. It could be useful for certain
|
||||
// user shaders to skip passes.
|
||||
if (bstr_equals0(var, "OUTPUT")) {
|
||||
size[0] = p->dst_rect.x1 - p->dst_rect.x0;
|
||||
size[1] = p->dst_rect.y1 - p->dst_rect.y0;
|
||||
return true;
|
||||
}
|
||||
|
||||
case SZEXP_CONST:
|
||||
// Since our SZEXPs are bound by MAX_SZEXP_SIZE, it should be
|
||||
// impossible to overflow the stack
|
||||
assert(idx < MAX_SZEXP_SIZE);
|
||||
stack[idx++] = expr[i].val.cval;
|
||||
continue;
|
||||
// HOOKED is a special case
|
||||
if (bstr_equals0(var, "HOOKED")) {
|
||||
size[0] = ctx->tex.w;
|
||||
size[1] = ctx->tex.h;
|
||||
return true;
|
||||
}
|
||||
|
||||
case SZEXP_OP1:
|
||||
if (idx < 1) {
|
||||
MP_WARN(p, "Stack underflow in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (expr[i].val.op) {
|
||||
case SZEXP_OP_NOT: stack[idx-1] = !stack[idx-1]; break;
|
||||
default: abort();
|
||||
}
|
||||
continue;
|
||||
|
||||
case SZEXP_OP2:
|
||||
if (idx < 2) {
|
||||
MP_WARN(p, "Stack underflow in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pop the operands in reverse order
|
||||
float op2 = stack[--idx];
|
||||
float op1 = stack[--idx];
|
||||
float res = 0.0;
|
||||
switch (expr[i].val.op) {
|
||||
case SZEXP_OP_ADD: res = op1 + op2; break;
|
||||
case SZEXP_OP_SUB: res = op1 - op2; break;
|
||||
case SZEXP_OP_MUL: res = op1 * op2; break;
|
||||
case SZEXP_OP_DIV: res = op1 / op2; break;
|
||||
case SZEXP_OP_GT: res = op1 > op2; break;
|
||||
case SZEXP_OP_LT: res = op1 < op2; break;
|
||||
default: abort();
|
||||
}
|
||||
|
||||
if (!isfinite(res)) {
|
||||
MP_WARN(p, "Illegal operation in RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stack[idx++] = res;
|
||||
continue;
|
||||
|
||||
case SZEXP_VAR_W:
|
||||
case SZEXP_VAR_H: {
|
||||
struct bstr name = expr[i].val.varname;
|
||||
struct img_tex var_tex;
|
||||
|
||||
// The size of OUTPUT is determined. It could be useful for certain
|
||||
// user shaders to skip passes.
|
||||
if (bstr_equals0(name, "OUTPUT")) {
|
||||
int vp_w = p->dst_rect.x1 - p->dst_rect.x0;
|
||||
int vp_h = p->dst_rect.y1 - p->dst_rect.y0;
|
||||
stack[idx++] = (expr[i].tag == SZEXP_VAR_W) ? vp_w : vp_h;
|
||||
continue;
|
||||
}
|
||||
|
||||
// HOOKED is a special case
|
||||
if (bstr_equals0(name, "HOOKED")) {
|
||||
var_tex = tex;
|
||||
goto found_tex;
|
||||
}
|
||||
|
||||
for (int o = 0; o < p->saved_tex_num; o++) {
|
||||
if (bstr_equals0(name, p->saved_tex[o].name)) {
|
||||
var_tex = p->saved_tex[o].tex;
|
||||
goto found_tex;
|
||||
}
|
||||
}
|
||||
|
||||
MP_WARN(p, "Texture %.*s not found in RPN expression!\n", BSTR_P(name));
|
||||
return false;
|
||||
|
||||
found_tex:
|
||||
stack[idx++] = (expr[i].tag == SZEXP_VAR_W) ? var_tex.w : var_tex.h;
|
||||
continue;
|
||||
}
|
||||
for (int o = 0; o < p->saved_tex_num; o++) {
|
||||
if (bstr_equals0(var, p->saved_tex[o].name)) {
|
||||
size[0] = p->saved_tex[o].tex.w;
|
||||
size[1] = p->saved_tex[o].tex.h;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
// Return the single stack element
|
||||
if (idx != 1) {
|
||||
MP_WARN(p, "Malformed stack after RPN expression!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = stack[0];
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool user_hook_cond(struct gl_video *p, struct img_tex tex, void *priv)
|
||||
@@ -1656,7 +1586,7 @@ static bool user_hook_cond(struct gl_video *p, struct img_tex tex, void *priv)
|
||||
assert(shader);
|
||||
|
||||
float res = false;
|
||||
eval_szexpr(p, tex, shader->cond, &res);
|
||||
eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->cond, &res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1674,8 +1604,8 @@ static void user_hook(struct gl_video *p, struct img_tex tex,
|
||||
// to do this and display an error message than just crash OpenGL
|
||||
float w = 1.0, h = 1.0;
|
||||
|
||||
eval_szexpr(p, tex, shader->width, &w);
|
||||
eval_szexpr(p, tex, shader->height, &h);
|
||||
eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->width, &w);
|
||||
eval_szexpr(p->log, &(struct szexp_ctx){p, tex}, szexp_lookup, shader->height, &h);
|
||||
|
||||
*trans = (struct gl_transform){{{w / tex.w, 0}, {0, h / tex.h}}};
|
||||
gl_transform_trans(shader->offset, trans);
|
||||
@@ -1983,7 +1913,7 @@ static void pass_convert_yuv(struct gl_video *p)
|
||||
GLSLF("color = color.%s;\n", p->color_swizzle);
|
||||
|
||||
// Pre-colormatrix input gamma correction
|
||||
if (cparams.colorspace == MP_CSP_XYZ)
|
||||
if (cparams.color.space == MP_CSP_XYZ)
|
||||
GLSL(color.rgb = pow(color.rgb, vec3(2.6));) // linear light
|
||||
|
||||
// We always explicitly normalize the range in pass_read_video
|
||||
@@ -1998,7 +1928,7 @@ static void pass_convert_yuv(struct gl_video *p)
|
||||
|
||||
GLSL(color.rgb = mat3(colormatrix) * color.rgb + colormatrix_c;)
|
||||
|
||||
if (p->image_params.colorspace == MP_CSP_BT_2020_C) {
|
||||
if (p->image_params.color.space == MP_CSP_BT_2020_C) {
|
||||
// Conversion for C'rcY'cC'bc via the BT.2020 CL system:
|
||||
// C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0
|
||||
// = (B'-Y'c) / 1.5816 | C'bc > 0
|
||||
@@ -2109,7 +2039,7 @@ static void pass_scale_main(struct gl_video *p)
|
||||
// Pre-conversion, like linear light/sigmoidization
|
||||
GLSLF("// scaler pre-conversion\n");
|
||||
if (p->use_linear) {
|
||||
pass_linearize(p->sc, p->image_params.gamma);
|
||||
pass_linearize(p->sc, p->image_params.color.gamma);
|
||||
pass_opt_hook_point(p, "LINEAR", NULL);
|
||||
}
|
||||
|
||||
@@ -2156,107 +2086,104 @@ static void pass_scale_main(struct gl_video *p)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapts the colors from the given color space to the display device's native
|
||||
// gamut.
|
||||
static void pass_colormanage(struct gl_video *p, float peak_src,
|
||||
enum mp_csp_prim prim_src,
|
||||
enum mp_csp_trc trc_src)
|
||||
// Adapts the colors to the right output color space. (Final pass during
|
||||
// rendering)
|
||||
// If OSD is true, ignore any changes that may have been made to the video
|
||||
// by previous passes (i.e. linear scaling)
|
||||
static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool osd)
|
||||
{
|
||||
GLSLF("// color management\n");
|
||||
enum mp_csp_trc trc_dst = p->opts.target_trc;
|
||||
enum mp_csp_prim prim_dst = p->opts.target_prim;
|
||||
float peak_dst = p->opts.target_brightness;
|
||||
struct mp_colorspace ref = src;
|
||||
|
||||
if (p->use_linear && !osd)
|
||||
src.gamma = MP_CSP_TRC_LINEAR;
|
||||
|
||||
// Figure out the target color space from the options, or auto-guess if
|
||||
// none were set
|
||||
struct mp_colorspace dst = {
|
||||
.gamma = p->opts.target_trc,
|
||||
.primaries = p->opts.target_prim,
|
||||
.nom_peak = mp_csp_trc_nom_peak(p->opts.target_trc, p->opts.target_brightness),
|
||||
};
|
||||
|
||||
if (p->use_lut_3d) {
|
||||
// The 3DLUT is always generated against the original source space
|
||||
enum mp_csp_prim prim_orig = p->image_params.primaries;
|
||||
enum mp_csp_trc trc_orig = p->image_params.gamma;
|
||||
// The 3DLUT is always generated against the video's original source
|
||||
// space, *not* the reference space. (To avoid having to regenerate
|
||||
// the 3DLUT for the OSD on every frame)
|
||||
enum mp_csp_prim prim_orig = p->image_params.color.primaries;
|
||||
enum mp_csp_trc trc_orig = p->image_params.color.gamma;
|
||||
|
||||
// One exception: SMPTE ST.2084 is not implemented by LittleCMS
|
||||
// for technical limitation reasons, so we use a gamma 2.2 input curve
|
||||
// here instead. We could pick any value we want here, the difference
|
||||
// is just coding efficiency.
|
||||
if (trc_orig == MP_CSP_TRC_SMPTE_ST2084)
|
||||
// One exception: HDR is not implemented by LittleCMS for technical
|
||||
// limitation reasons, so we use a gamma 2.2 input curve here instead.
|
||||
// We could pick any value we want here, the difference is just coding
|
||||
// efficiency.
|
||||
if (trc_orig == MP_CSP_TRC_SMPTE_ST2084 ||
|
||||
trc_orig == MP_CSP_TRC_ARIB_STD_B67 ||
|
||||
trc_orig == MP_CSP_TRC_V_LOG)
|
||||
{
|
||||
trc_orig = MP_CSP_TRC_GAMMA22;
|
||||
}
|
||||
|
||||
if (gl_video_get_lut3d(p, prim_orig, trc_orig)) {
|
||||
prim_dst = prim_orig;
|
||||
trc_dst = trc_orig;
|
||||
dst.primaries = prim_orig;
|
||||
dst.gamma = trc_orig;
|
||||
}
|
||||
}
|
||||
|
||||
// When auto-guessing the output color params, just pick the source color
|
||||
// params to preserve the authentic "look and feel" of wrong/naive players.
|
||||
// Some exceptions apply to source spaces that even hardcore technoluddites
|
||||
// would probably not enjoy viewing unaltered
|
||||
if (prim_dst == MP_CSP_PRIM_AUTO) {
|
||||
prim_dst = prim_src;
|
||||
if (dst.primaries == MP_CSP_PRIM_AUTO) {
|
||||
// The vast majority of people are on sRGB or BT.709 displays, so pick
|
||||
// this as the default output color space.
|
||||
dst.primaries = MP_CSP_PRIM_BT_709;
|
||||
|
||||
// Avoid outputting very wide gamut content automatically, since the
|
||||
// majority target audience has standard gamut displays
|
||||
if (prim_dst == MP_CSP_PRIM_BT_2020 || prim_dst == MP_CSP_PRIM_PRO_PHOTO)
|
||||
prim_dst = MP_CSP_PRIM_BT_709;
|
||||
if (ref.primaries == MP_CSP_PRIM_BT_601_525 ||
|
||||
ref.primaries == MP_CSP_PRIM_BT_601_625)
|
||||
{
|
||||
// Since we auto-pick BT.601 and BT.709 based on the dimensions,
|
||||
// combined with the fact that they're very similar to begin with,
|
||||
// and to avoid confusing the average user, just don't adapt BT.601
|
||||
// content automatically at all.
|
||||
dst.primaries = ref.gamma;
|
||||
}
|
||||
}
|
||||
|
||||
if (trc_dst == MP_CSP_TRC_AUTO) {
|
||||
trc_dst = trc_src;
|
||||
// Avoid outputting linear light at all costs. First try
|
||||
// falling back to the image gamma (e.g. in the case that the input
|
||||
// was linear light due to linear-scaling)
|
||||
if (trc_dst == MP_CSP_TRC_LINEAR)
|
||||
trc_dst = p->image_params.gamma;
|
||||
if (dst.gamma == MP_CSP_TRC_AUTO) {
|
||||
// Most people seem to complain when the image is darker or brighter
|
||||
// than what they're "used to", so just avoid changing the gamma
|
||||
// altogether by default. The only exceptions to this rule apply to
|
||||
// very unusual TRCs, which even hardcode technoluddites would probably
|
||||
// not enjoy viewing unaltered.
|
||||
dst.gamma = ref.gamma;
|
||||
|
||||
// Failing that, pick gamma 2.2 as a reasonable default. This is also
|
||||
// picked as a default for outputting HDR content
|
||||
if (trc_dst == MP_CSP_TRC_LINEAR || trc_dst == MP_CSP_TRC_SMPTE_ST2084)
|
||||
trc_dst = MP_CSP_TRC_GAMMA22;
|
||||
// Avoid outputting linear light or HDR content "by default". For these
|
||||
// just pick gamma 2.2 as a default, since it's a good estimate for
|
||||
// the response of typical displays
|
||||
if (dst.gamma == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(dst.gamma))
|
||||
dst.gamma = MP_CSP_TRC_GAMMA22;
|
||||
}
|
||||
|
||||
if (!peak_src) {
|
||||
// If the source has no information known, it's display-referred
|
||||
// (and should be treated relative to the specified desired peak_dst)
|
||||
peak_src = peak_dst;
|
||||
// For the src peaks, the correct brightness metadata may be present for
|
||||
// sig_peak, nom_peak, both, or neither. To handle everything in a generic
|
||||
// way, it's important to never automatically infer a sig_peak that is
|
||||
// below the nom_peak (since we don't know what bits the image contains,
|
||||
// doing so would potentially badly clip). The only time in which this
|
||||
// may be the case is when the mastering metadata explicitly says so, i.e.
|
||||
// the sig_peak was already set. So to simplify the logic as much as
|
||||
// possible, make sure the nom_peak is present and correct first, and just
|
||||
// set sig_peak = nom_peak if missing.
|
||||
if (!src.nom_peak) {
|
||||
// For display-referred colorspaces, we treat it as relative to
|
||||
// target_brightness
|
||||
src.nom_peak = mp_csp_trc_nom_peak(src.gamma, p->opts.target_brightness);
|
||||
}
|
||||
|
||||
// All operations from here on require linear light as a starting point,
|
||||
// so we linearize even if trc_src == trc_dst when one of the other
|
||||
// operations needs it
|
||||
bool need_gamma = trc_src != trc_dst || prim_src != prim_dst ||
|
||||
peak_src != peak_dst;
|
||||
if (need_gamma)
|
||||
pass_linearize(p->sc, trc_src);
|
||||
if (!src.sig_peak)
|
||||
src.sig_peak = src.nom_peak;
|
||||
|
||||
// Adapt and tone map for a different reference peak brightness
|
||||
if (peak_src != peak_dst)
|
||||
{
|
||||
GLSLF("// HDR tone mapping\n");
|
||||
float rel_peak = peak_src / peak_dst;
|
||||
// Normalize such that 1 is the target brightness (and values above
|
||||
// 1 are out of range)
|
||||
GLSLF("color.rgb *= vec3(%f);\n", rel_peak);
|
||||
// Tone map back down to the range [0,1]
|
||||
pass_tone_map(p->sc, rel_peak, p->opts.hdr_tone_mapping,
|
||||
p->opts.tone_mapping_param);
|
||||
}
|
||||
MP_DBG(p, "HDR src nom: %f sig: %f, dst: %f\n",
|
||||
src.nom_peak, src.sig_peak, dst.nom_peak);
|
||||
|
||||
// Adapt to the right colorspace if necessary
|
||||
if (prim_src != prim_dst) {
|
||||
struct mp_csp_primaries csp_src = mp_get_csp_primaries(prim_src),
|
||||
csp_dst = mp_get_csp_primaries(prim_dst);
|
||||
float m[3][3] = {{0}};
|
||||
mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m);
|
||||
gl_sc_uniform_mat3(p->sc, "cms_matrix", true, &m[0][0]);
|
||||
GLSL(color.rgb = cms_matrix * color.rgb;)
|
||||
}
|
||||
|
||||
if (need_gamma) {
|
||||
// If the target encoding function has a fixed peak, we need to
|
||||
// un-normalize back to the encoding signal range
|
||||
if (trc_dst == MP_CSP_TRC_SMPTE_ST2084)
|
||||
GLSLF("color.rgb *= vec3(%f);\n", peak_dst / 10000);
|
||||
|
||||
pass_delinearize(p->sc, trc_dst);
|
||||
}
|
||||
// Adapt from src to dst as necessary
|
||||
pass_color_map(p->sc, src, dst, p->opts.hdr_tone_mapping,
|
||||
p->opts.tone_mapping_param);
|
||||
|
||||
if (p->use_lut_3d) {
|
||||
gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT);
|
||||
@@ -2397,11 +2324,15 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
// Subtitle color management, they're assumed to be display-referred
|
||||
// sRGB by default
|
||||
// When subtitles need to be color managed, assume they're in sRGB
|
||||
// (for lack of anything saner to do)
|
||||
if (cms) {
|
||||
pass_colormanage(p, p->opts.target_brightness,
|
||||
MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB);
|
||||
static const struct mp_colorspace csp_srgb = {
|
||||
.primaries = MP_CSP_PRIM_BT_709,
|
||||
.gamma = MP_CSP_TRC_SRGB,
|
||||
};
|
||||
|
||||
pass_colormanage(p, csp_srgb, true);
|
||||
}
|
||||
gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd));
|
||||
gl_sc_gen_shader_and_reset(p->sc);
|
||||
@@ -2503,7 +2434,7 @@ static void pass_render_frame(struct gl_video *p)
|
||||
rect.mt *= scale[1]; rect.mb *= scale[1];
|
||||
// We should always blend subtitles in non-linear light
|
||||
if (p->use_linear) {
|
||||
pass_delinearize(p->sc, p->image_params.gamma);
|
||||
pass_delinearize(p->sc, p->image_params.color.gamma);
|
||||
p->use_linear = false;
|
||||
}
|
||||
finish_pass_fbo(p, &p->blend_subs_fbo, p->texture_w, p->texture_h,
|
||||
@@ -2532,8 +2463,7 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo)
|
||||
GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));)
|
||||
}
|
||||
|
||||
pass_colormanage(p, p->image_params.peak, p->image_params.primaries,
|
||||
p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.gamma);
|
||||
pass_colormanage(p, p->image_params.color, false);
|
||||
|
||||
// Draw checkerboard pattern to indicate transparency
|
||||
if (p->has_alpha && p->opts.alpha_mode == ALPHA_BLEND_TILES) {
|
||||
@@ -2564,7 +2494,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
|
||||
if (t->still)
|
||||
gl_video_reset_surfaces(p);
|
||||
|
||||
// First of all, figure out if we have a frame availble at all, and draw
|
||||
// First of all, figure out if we have a frame available at all, and draw
|
||||
// it manually + reset the queue if not
|
||||
if (p->surfaces[p->surface_now].pts == MP_NOPTS_VALUE) {
|
||||
if (!gl_video_upload_image(p, t->current))
|
||||
@@ -2577,7 +2507,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
|
||||
}
|
||||
|
||||
// Find the right frame for this instant
|
||||
if (t->current&& t->current->pts != MP_NOPTS_VALUE) {
|
||||
if (t->current && t->current->pts != MP_NOPTS_VALUE) {
|
||||
int next = fbosurface_wrap(p->surface_now + 1);
|
||||
while (p->surfaces[next].pts != MP_NOPTS_VALUE &&
|
||||
p->surfaces[next].pts > p->surfaces[p->surface_now].pts &&
|
||||
@@ -2615,7 +2545,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
|
||||
// this should be done before the step where we find the right frame, but
|
||||
// it only barely matters at the very beginning of playback, and this way
|
||||
// makes the code much more linear.
|
||||
int surface_dst = fbosurface_wrap(p->surface_idx+1);
|
||||
int surface_dst = fbosurface_wrap(p->surface_idx + 1);
|
||||
for (int i = 0; i < t->num_frames; i++) {
|
||||
// Avoid overwriting data we might still need
|
||||
if (surface_dst == surface_bse - 1)
|
||||
@@ -2634,7 +2564,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
|
||||
vp_w, vp_h, FBOTEX_FUZZY);
|
||||
p->surfaces[surface_dst].pts = f->pts;
|
||||
p->surface_idx = surface_dst;
|
||||
surface_dst = fbosurface_wrap(surface_dst+1);
|
||||
surface_dst = fbosurface_wrap(surface_dst + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2645,7 +2575,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
|
||||
// end of playback or start of playback.
|
||||
bool valid = true;
|
||||
for (int i = surface_bse, ii; valid && i != surface_end; i = ii) {
|
||||
ii = fbosurface_wrap(i+1);
|
||||
ii = fbosurface_wrap(i + 1);
|
||||
if (p->surfaces[i].pts == MP_NOPTS_VALUE ||
|
||||
p->surfaces[ii].pts == MP_NOPTS_VALUE)
|
||||
{
|
||||
@@ -2737,6 +2667,11 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
|
||||
GL *gl = p->gl;
|
||||
struct video_image *vimg = &p->image;
|
||||
|
||||
if (fbo && !(gl->mpgl_caps & MPGL_CAP_FB)) {
|
||||
MP_FATAL(p, "Rendering to FBO requested, but no FBO extension found!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p->broken_frame = false;
|
||||
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
@@ -2773,7 +2708,7 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
|
||||
goto done;
|
||||
pass_render_frame(p);
|
||||
|
||||
// For the non-interplation case, we draw to a single "cache"
|
||||
// For the non-interpolation case, we draw to a single "cache"
|
||||
// FBO to speed up subsequent re-draws (if any exist)
|
||||
int dest_fbo = fbo;
|
||||
if (frame->num_vsyncs > 1 && frame->display_synced &&
|
||||
@@ -2781,7 +2716,7 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
|
||||
{
|
||||
fbotex_change(&p->output_fbo, p->gl, p->log,
|
||||
p->vp_w, abs(p->vp_h),
|
||||
p->opts.fbo_format, 0);
|
||||
p->opts.fbo_format, FBOTEX_FUZZY);
|
||||
dest_fbo = p->output_fbo.fbo;
|
||||
p->output_fbo_valid = true;
|
||||
}
|
||||
@@ -2880,54 +2815,6 @@ struct voctrl_performance_data gl_video_perfdata(struct gl_video *p)
|
||||
};
|
||||
}
|
||||
|
||||
static bool unmap_image(struct gl_video *p, struct mp_image *mpi)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
bool ok = true;
|
||||
struct video_image *vimg = &p->image;
|
||||
for (int n = 0; n < p->plane_count; n++) {
|
||||
struct texplane *plane = &vimg->planes[n];
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
|
||||
ok = gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER) && ok;
|
||||
mpi->planes[n] = NULL; // PBO offset 0
|
||||
}
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool map_image(struct gl_video *p, struct mp_image *mpi)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
|
||||
if (!p->opts.pbo)
|
||||
return false;
|
||||
|
||||
struct video_image *vimg = &p->image;
|
||||
|
||||
for (int n = 0; n < p->plane_count; n++) {
|
||||
struct texplane *plane = &vimg->planes[n];
|
||||
mpi->stride[n] = mp_image_plane_w(mpi, n) * p->image_desc.bytes[n];
|
||||
size_t buffer_size = mp_image_plane_h(mpi, n) * mpi->stride[n];
|
||||
if (!plane->gl_buffer) {
|
||||
gl->GenBuffers(1, &plane->gl_buffer);
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
|
||||
gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size,
|
||||
NULL, GL_DYNAMIC_DRAW);
|
||||
}
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
|
||||
mpi->planes[n] = gl->MapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
|
||||
buffer_size, GL_MAP_WRITE_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
if (!mpi->planes[n]) {
|
||||
unmap_image(p, mpi);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
memset(mpi->bufs, 0, sizeof(mpi->bufs));
|
||||
return true;
|
||||
}
|
||||
|
||||
// This assumes nv12, with textures set to GL_NEAREST filtering.
|
||||
static void reinterleave_vdpau(struct gl_video *p, struct gl_hwdec_frame *frame)
|
||||
{
|
||||
@@ -3024,32 +2911,19 @@ static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi)
|
||||
|
||||
gl_timer_start(p->upload_timer);
|
||||
|
||||
mp_image_t pbo_mpi = *mpi;
|
||||
bool pbo = map_image(p, &pbo_mpi);
|
||||
if (pbo) {
|
||||
mp_image_copy(&pbo_mpi, mpi);
|
||||
if (unmap_image(p, &pbo_mpi)) {
|
||||
mpi = &pbo_mpi;
|
||||
} else {
|
||||
MP_FATAL(p, "Video PBO upload failed. Disabling PBOs.\n");
|
||||
pbo = false;
|
||||
p->opts.pbo = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vimg->image_flipped = mpi->stride[0] < 0;
|
||||
for (int n = 0; n < p->plane_count; n++) {
|
||||
struct texplane *plane = &vimg->planes[n];
|
||||
if (pbo)
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
|
||||
gl->ActiveTexture(GL_TEXTURE0 + n);
|
||||
|
||||
plane->flipped = mpi->stride[0] < 0;
|
||||
|
||||
gl->BindTexture(plane->gl_target, plane->gl_texture);
|
||||
gl_upload_tex(gl, plane->gl_target, plane->gl_format, plane->gl_type,
|
||||
mpi->planes[n], mpi->stride[n], 0, 0, plane->w, plane->h);
|
||||
gl_pbo_upload_tex(&plane->pbo, gl, p->opts.pbo, plane->gl_target,
|
||||
plane->gl_format, plane->gl_type, plane->w, plane->h,
|
||||
mpi->planes[n], mpi->stride[n],
|
||||
0, 0, plane->w, plane->h);
|
||||
gl->BindTexture(plane->gl_target, 0);
|
||||
}
|
||||
gl->ActiveTexture(GL_TEXTURE0);
|
||||
if (pbo)
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
gl_timer_stop(p->upload_timer);
|
||||
|
||||
|
||||
@@ -227,13 +227,31 @@ static const float HDR_M1 = 2610./4096 * 1./4,
|
||||
HDR_C2 = 2413./4096 * 32,
|
||||
HDR_C3 = 2392./4096 * 32;
|
||||
|
||||
// Linearize (expand), given a TRC as input
|
||||
// Common constants for ARIB STD-B67 (Hybrid Log-gamma)
|
||||
static const float B67_A = 0.17883277,
|
||||
B67_B = 0.28466892,
|
||||
B67_C = 0.55991073;
|
||||
|
||||
// Common constants for Panasonic V-Log
|
||||
static const float VLOG_B = 0.00873,
|
||||
VLOG_C = 0.241514,
|
||||
VLOG_D = 0.598206,
|
||||
VLOG_R = 46.085527; // nominal peak
|
||||
|
||||
// Linearize (expand), given a TRC as input. This corresponds to the EOTF
|
||||
// in ITU-R terminology.
|
||||
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
||||
{
|
||||
if (trc == MP_CSP_TRC_LINEAR)
|
||||
return;
|
||||
|
||||
// Note that this clamp may technically violate the definition of
|
||||
// ITU-R BT.2100, which allows for sub-blacks and super-whites to be
|
||||
// displayed on the display where such would be possible. That said, the
|
||||
// problem is that not all gamma curves are well-defined on the values
|
||||
// outside this range, so we ignore it and just clip anyway for sanity.
|
||||
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
|
||||
|
||||
switch (trc) {
|
||||
case MP_CSP_TRC_SRGB:
|
||||
GLSL(color.rgb = mix(color.rgb / vec3(12.92),
|
||||
@@ -265,12 +283,34 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
||||
HDR_C1, HDR_C2, HDR_C3);
|
||||
GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", HDR_M1);
|
||||
break;
|
||||
case MP_CSP_TRC_ARIB_STD_B67:
|
||||
GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n"
|
||||
" exp((color.rgb - vec3(%f)) / vec3(%f)) + vec3(%f),\n"
|
||||
" lessThan(vec3(0.5), color.rgb));\n",
|
||||
B67_C, B67_A, B67_B);
|
||||
// Since the ARIB function's signal value of 1.0 corresponds to
|
||||
// a peak of 12.0, we need to renormalize to prevent GL textures
|
||||
// from clipping. (In general, mpv's internal conversions always
|
||||
// assume 1.0 is the maximum brightness, not the reference peak)
|
||||
GLSL(color.rgb /= vec3(12.0);)
|
||||
break;
|
||||
case MP_CSP_TRC_V_LOG:
|
||||
GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) / vec3(5.6), \n"
|
||||
" pow(vec3(10.0), (color.rgb - vec3(%f)) / vec3(%f)) \n"
|
||||
" - vec3(%f), \n"
|
||||
" lessThanEqual(vec3(0.181), color.rgb)); \n",
|
||||
VLOG_D, VLOG_C, VLOG_B);
|
||||
// Same deal as with the B67 function, renormalize to texture range
|
||||
GLSLF("color.rgb /= vec3(%f);\n", VLOG_R);
|
||||
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Delinearize (compress), given a TRC as output
|
||||
// Delinearize (compress), given a TRC as output. This corresponds to the
|
||||
// inverse EOTF (not the OETF) in ITU-R terminology.
|
||||
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
||||
{
|
||||
if (trc == MP_CSP_TRC_LINEAR)
|
||||
@@ -308,15 +348,32 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
||||
HDR_C1, HDR_C2, HDR_C3);
|
||||
GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", HDR_M2);
|
||||
break;
|
||||
case MP_CSP_TRC_ARIB_STD_B67:
|
||||
GLSL(color.rgb *= vec3(12.0);)
|
||||
GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n"
|
||||
" vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n"
|
||||
" lessThan(vec3(1.0), color.rgb));\n",
|
||||
B67_A, B67_B, B67_C);
|
||||
break;
|
||||
case MP_CSP_TRC_V_LOG:
|
||||
GLSLF("color.rgb *= vec3(%f);\n", VLOG_R);
|
||||
GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n"
|
||||
" vec3(%f) * log(color.rgb + vec3(%f)) \n"
|
||||
" + vec3(%f), \n"
|
||||
" lessThanEqual(vec3(0.01), color.rgb)); \n",
|
||||
VLOG_C / M_LN10, VLOG_B, VLOG_D);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Tone map from a known peak brightness to the range [0,1]
|
||||
void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
enum tone_mapping algo, float param)
|
||||
static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
|
||||
enum tone_mapping algo, float param)
|
||||
{
|
||||
GLSLF("// HDR tone mapping\n");
|
||||
|
||||
switch (algo) {
|
||||
case TONE_MAPPING_CLIP:
|
||||
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
|
||||
@@ -326,7 +383,7 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
float contrast = isnan(param) ? 0.5 : param,
|
||||
offset = (1.0 - contrast) / contrast;
|
||||
GLSLF("color.rgb = color.rgb / (color.rgb + vec3(%f));\n", offset);
|
||||
GLSLF("color.rgb *= vec3(%f);\n", (peak + offset) / peak);
|
||||
GLSLF("color.rgb *= vec3(%f);\n", (ref_peak + offset) / ref_peak);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -337,20 +394,20 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
A, C*B, D*E, A, B, D*F, E/F);
|
||||
GLSLHF("}\n");
|
||||
|
||||
GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", peak);
|
||||
GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", ref_peak);
|
||||
break;
|
||||
}
|
||||
|
||||
case TONE_MAPPING_GAMMA: {
|
||||
float gamma = isnan(param) ? 1.8 : param;
|
||||
GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n",
|
||||
peak, 1.0/gamma);
|
||||
ref_peak, 1.0/gamma);
|
||||
break;
|
||||
}
|
||||
|
||||
case TONE_MAPPING_LINEAR: {
|
||||
float coeff = isnan(param) ? 1.0 : param;
|
||||
GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / peak);
|
||||
GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / ref_peak);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -359,6 +416,61 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
}
|
||||
}
|
||||
|
||||
// Map colors from one source space to another. These source spaces
|
||||
// must be known (i.e. not MP_CSP_*_AUTO), as this function won't perform
|
||||
// any auto-guessing.
|
||||
void pass_color_map(struct gl_shader_cache *sc,
|
||||
struct mp_colorspace src, struct mp_colorspace dst,
|
||||
enum tone_mapping algo, float tone_mapping_param)
|
||||
{
|
||||
GLSLF("// color mapping\n");
|
||||
|
||||
// All operations from here on require linear light as a starting point,
|
||||
// so we linearize even if src.gamma == dst.gamma when one of the other
|
||||
// operations needs it
|
||||
bool need_gamma = src.gamma != dst.gamma ||
|
||||
src.primaries != dst.primaries ||
|
||||
src.nom_peak != dst.nom_peak ||
|
||||
src.sig_peak > dst.nom_peak;
|
||||
|
||||
if (need_gamma)
|
||||
pass_linearize(sc, src.gamma);
|
||||
|
||||
// NOTE: When src.gamma = MP_CSP_TRC_ARIB_STD_B67, we would technically
|
||||
// need to apply the reference OOTF as part of the EOTF (which is what we
|
||||
// implement with pass_linearize), since HLG considers OOTF to be part of
|
||||
// the display's EOTF (as opposed to the camera's OETF). But since this is
|
||||
// stupid, complicated, arbitrary, and more importantly depends on the
|
||||
// target display's signal peak (which is != the nom_peak in the case of
|
||||
// HDR displays, and mpv already has enough target-specific display
|
||||
// options), we just ignore its implementation entirely. (Plus, it doesn't
|
||||
// even really make sense with tone mapping to begin with.) But just in
|
||||
// case somebody ends up complaining about HLG looking different from a
|
||||
// reference HLG display, this comment might be why.
|
||||
|
||||
// Stretch the signal value to renormalize to the dst nominal peak
|
||||
if (src.nom_peak != dst.nom_peak)
|
||||
GLSLF("color.rgb *= vec3(%f);\n", src.nom_peak / dst.nom_peak);
|
||||
|
||||
// Tone map to prevent clipping when the source signal peak exceeds the
|
||||
// encodable range.
|
||||
if (src.sig_peak > dst.nom_peak)
|
||||
pass_tone_map(sc, src.sig_peak / dst.nom_peak, algo, tone_mapping_param);
|
||||
|
||||
// Adapt to the right colorspace if necessary
|
||||
if (src.primaries != dst.primaries) {
|
||||
struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries),
|
||||
csp_dst = mp_get_csp_primaries(dst.primaries);
|
||||
float m[3][3] = {{0}};
|
||||
mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m);
|
||||
gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]);
|
||||
GLSL(color.rgb = cms_matrix * color.rgb;)
|
||||
}
|
||||
|
||||
if (need_gamma)
|
||||
pass_delinearize(sc, dst.gamma);
|
||||
}
|
||||
|
||||
// Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post.
|
||||
// Obtain random numbers by calling rand(h), followed by h = permute(h) to
|
||||
// update the state. Assumes the texture was hooked.
|
||||
|
||||
@@ -38,8 +38,9 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
|
||||
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
|
||||
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
|
||||
|
||||
void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
enum tone_mapping algo, float param);
|
||||
void pass_color_map(struct gl_shader_cache *sc,
|
||||
struct mp_colorspace src, struct mp_colorspace dst,
|
||||
enum tone_mapping algo, float tone_mapping_param);
|
||||
|
||||
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
|
||||
AVLFG *lfg);
|
||||
|
||||
+71
-86
@@ -38,7 +38,6 @@
|
||||
#include "common/common.h"
|
||||
#include "w32_common.h"
|
||||
#include "sub/osd.h"
|
||||
#include "bitmap_packer.h"
|
||||
|
||||
// shaders generated by fxc.exe from d3d_shader_yuv.hlsl
|
||||
#include "d3d_shader_420p.h"
|
||||
@@ -105,7 +104,6 @@ struct osdpart {
|
||||
struct d3dtex texture;
|
||||
int num_vertices;
|
||||
vertex_osd *vertices;
|
||||
struct bitmap_packer *packer;
|
||||
};
|
||||
|
||||
enum shaders {
|
||||
@@ -822,7 +820,7 @@ static bool resize_d3d(d3d_priv *priv)
|
||||
MP_VERBOSE(priv, "resize_d3d %dx%d called.\n",
|
||||
priv->vo->dwidth, priv->vo->dheight);
|
||||
|
||||
/* Make sure that backbuffer is large enough to accomodate the new
|
||||
/* Make sure that backbuffer is large enough to accommodate the new
|
||||
viewport dimensions. Grow it if necessary. */
|
||||
|
||||
bool backbuf_resize = priv->vo->dwidth > priv->cur_backbuf_width ||
|
||||
@@ -1215,13 +1213,8 @@ static int preinit(struct vo *vo)
|
||||
priv->vo = vo;
|
||||
priv->log = vo->log;
|
||||
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
||||
struct osdpart *osd = talloc_ptrtype(priv, osd);
|
||||
*osd = (struct osdpart) {
|
||||
.packer = talloc_zero(osd, struct bitmap_packer),
|
||||
};
|
||||
priv->osd[n] = osd;
|
||||
}
|
||||
for (int n = 0; n < MAX_OSD_PARTS; n++)
|
||||
priv->osd[n] = talloc_zero(priv, struct osdpart);
|
||||
|
||||
priv->d3d9_dll = LoadLibraryA("d3d9.dll");
|
||||
if (!priv->d3d9_dll) {
|
||||
@@ -1536,34 +1529,45 @@ error_exit:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static D3DCOLOR ass_to_d3d_color(uint32_t color)
|
||||
{
|
||||
uint32_t r = (color >> 24) & 0xff;
|
||||
uint32_t g = (color >> 16) & 0xff;
|
||||
uint32_t b = (color >> 8) & 0xff;
|
||||
uint32_t a = 0xff - (color & 0xff);
|
||||
return D3DCOLOR_ARGB(a, r, g, b);
|
||||
}
|
||||
|
||||
static int next_pow2(int v)
|
||||
{
|
||||
for (int x = 0; x < 30; x++) {
|
||||
if ((1 << x) >= v)
|
||||
return 1 << x;
|
||||
}
|
||||
return INT_MAX;
|
||||
}
|
||||
|
||||
static bool upload_osd(d3d_priv *priv, struct osdpart *osd,
|
||||
struct sub_bitmaps *imgs)
|
||||
{
|
||||
D3DFORMAT fmt = priv->osd_fmt_table[imgs->format];
|
||||
|
||||
osd->packer->w_max = priv->max_texture_width;
|
||||
osd->packer->h_max = priv->max_texture_height;
|
||||
assert(imgs->packed);
|
||||
|
||||
osd->packer->padding = imgs->scaled; // assume 2x2 filter on scaling
|
||||
int r = packer_pack_from_subbitmaps(osd->packer, imgs);
|
||||
if (r < 0) {
|
||||
MP_ERR(priv, "OSD bitmaps do not fit on "
|
||||
"a surface with the maximum supported size %dx%d.\n",
|
||||
osd->packer->w_max, osd->packer->h_max);
|
||||
return false;
|
||||
}
|
||||
osd->change_id = imgs->change_id;
|
||||
osd->num_vertices = 0;
|
||||
|
||||
if (osd->packer->w > osd->texture.tex_w
|
||||
|| osd->packer->h > osd->texture.tex_h
|
||||
if (imgs->packed_w > osd->texture.tex_w
|
||||
|| imgs->packed_h > osd->texture.tex_h
|
||||
|| osd->format != imgs->format)
|
||||
{
|
||||
osd->format = imgs->format;
|
||||
|
||||
int new_w = osd->packer->w;
|
||||
int new_h = osd->packer->h;
|
||||
int new_w = next_pow2(imgs->packed_w);
|
||||
int new_h = next_pow2(imgs->packed_h);
|
||||
d3d_fix_texture_size(priv, &new_w, &new_h);
|
||||
|
||||
MP_DBG(priv, "reallocate OSD surface.\n");
|
||||
MP_DBG(priv, "reallocate OSD surface to %dx%d.\n", new_w, new_h);
|
||||
|
||||
d3dtex_release(priv, &osd->texture);
|
||||
d3dtex_allocate(priv, &osd->texture, fmt, new_w, new_h);
|
||||
@@ -1572,9 +1576,7 @@ static bool upload_osd(d3d_priv *priv, struct osdpart *osd,
|
||||
return false; // failed to allocate
|
||||
}
|
||||
|
||||
struct pos bb[2];
|
||||
packer_get_bb(osd->packer, bb);
|
||||
RECT dirty_rc = { bb[0].x, bb[0].y, bb[1].x, bb[1].y };
|
||||
RECT dirty_rc = { 0, 0, imgs->packed_w, imgs->packed_h };
|
||||
|
||||
D3DLOCKED_RECT locked_rect;
|
||||
|
||||
@@ -1586,15 +1588,50 @@ static bool upload_osd(d3d_priv *priv, struct osdpart *osd,
|
||||
}
|
||||
|
||||
int ps = fmt == D3DFMT_A8 ? 1 : 4;
|
||||
packer_copy_subbitmaps(osd->packer, imgs, locked_rect.pBits, ps,
|
||||
locked_rect.Pitch);
|
||||
memcpy_pic(locked_rect.pBits, imgs->packed->planes[0], ps * imgs->packed_w,
|
||||
imgs->packed_h, locked_rect.Pitch, imgs->packed->stride[0]);
|
||||
|
||||
if (FAILED(IDirect3DTexture9_UnlockRect(osd->texture.system, 0))) {
|
||||
MP_ERR(priv, "OSD texture unlock failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return d3dtex_update(priv, &osd->texture);
|
||||
if (!d3dtex_update(priv, &osd->texture))
|
||||
return false;
|
||||
|
||||
// We need 2 primitives per quad which makes 6 vertices.
|
||||
osd->num_vertices = imgs->num_parts * 6;
|
||||
MP_TARRAY_GROW(osd, osd->vertices, osd->num_vertices);
|
||||
|
||||
float tex_w = osd->texture.tex_w;
|
||||
float tex_h = osd->texture.tex_h;
|
||||
|
||||
for (int n = 0; n < imgs->num_parts; n++) {
|
||||
struct sub_bitmap *b = &imgs->parts[n];
|
||||
|
||||
D3DCOLOR color = imgs->format == SUBBITMAP_LIBASS
|
||||
? ass_to_d3d_color(b->libass.color)
|
||||
: D3DCOLOR_ARGB(255, 255, 255, 255);
|
||||
|
||||
float x0 = b->x;
|
||||
float y0 = b->y;
|
||||
float x1 = b->x + b->dw;
|
||||
float y1 = b->y + b->dh;
|
||||
float tx0 = b->src_x / tex_w;
|
||||
float ty0 = b->src_y / tex_h;
|
||||
float tx1 = (b->src_x + b->w) / tex_w;
|
||||
float ty1 = (b->src_y + b->h) / tex_h;
|
||||
|
||||
vertex_osd *v = &osd->vertices[n * 6];
|
||||
v[0] = (vertex_osd) { x0, y0, 0, color, tx0, ty0 };
|
||||
v[1] = (vertex_osd) { x1, y0, 0, color, tx1, ty0 };
|
||||
v[2] = (vertex_osd) { x0, y1, 0, color, tx0, ty1 };
|
||||
v[3] = (vertex_osd) { x1, y1, 0, color, tx1, ty1 };
|
||||
v[4] = v[2];
|
||||
v[5] = v[1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct osdpart *generate_osd(d3d_priv *priv, struct sub_bitmaps *imgs)
|
||||
@@ -1604,24 +1641,10 @@ static struct osdpart *generate_osd(d3d_priv *priv, struct sub_bitmaps *imgs)
|
||||
|
||||
struct osdpart *osd = priv->osd[imgs->render_index];
|
||||
|
||||
if (imgs->change_id != osd->change_id) {
|
||||
if (!upload_osd(priv, osd, imgs))
|
||||
osd->packer->count = 0;
|
||||
if (imgs->change_id != osd->change_id)
|
||||
upload_osd(priv, osd, imgs);
|
||||
|
||||
osd->change_id = imgs->change_id;
|
||||
osd->num_vertices = 0;
|
||||
}
|
||||
|
||||
return osd->packer->count ? osd : NULL;
|
||||
}
|
||||
|
||||
static D3DCOLOR ass_to_d3d_color(uint32_t color)
|
||||
{
|
||||
uint32_t r = (color >> 24) & 0xff;
|
||||
uint32_t g = (color >> 16) & 0xff;
|
||||
uint32_t b = (color >> 8) & 0xff;
|
||||
uint32_t a = 0xff - (color & 0xff);
|
||||
return D3DCOLOR_ARGB(a, r, g, b);
|
||||
return osd->num_vertices ? osd : NULL;
|
||||
}
|
||||
|
||||
static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
|
||||
@@ -1632,44 +1655,6 @@ static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
|
||||
if (!osd)
|
||||
return;
|
||||
|
||||
if (osd->packer->count && !osd->num_vertices) {
|
||||
// We need 2 primitives per quad which makes 6 vertices (we could reduce
|
||||
// the number of vertices by using an indexed vertex array, but it's
|
||||
// probably not worth doing)
|
||||
osd->num_vertices = osd->packer->count * 6;
|
||||
osd->vertices = talloc_realloc(osd, osd->vertices, vertex_osd,
|
||||
osd->num_vertices);
|
||||
|
||||
float tex_w = osd->texture.tex_w;
|
||||
float tex_h = osd->texture.tex_h;
|
||||
|
||||
for (int n = 0; n < osd->packer->count; n++) {
|
||||
struct sub_bitmap *b = &imgs->parts[n];
|
||||
struct pos p = osd->packer->result[n];
|
||||
|
||||
D3DCOLOR color = imgs->format == SUBBITMAP_LIBASS
|
||||
? ass_to_d3d_color(b->libass.color)
|
||||
: D3DCOLOR_ARGB(255, 255, 255, 255);
|
||||
|
||||
float x0 = b->x;
|
||||
float y0 = b->y;
|
||||
float x1 = b->x + b->dw;
|
||||
float y1 = b->y + b->dh;
|
||||
float tx0 = p.x / tex_w;
|
||||
float ty0 = p.y / tex_h;
|
||||
float tx1 = (p.x + b->w) / tex_w;
|
||||
float ty1 = (p.y + b->h) / tex_h;
|
||||
|
||||
vertex_osd *v = &osd->vertices[n * 6];
|
||||
v[0] = (vertex_osd) { x0, y0, 0, color, tx0, ty0 };
|
||||
v[1] = (vertex_osd) { x1, y0, 0, color, tx1, ty0 };
|
||||
v[2] = (vertex_osd) { x0, y1, 0, color, tx0, ty1 };
|
||||
v[3] = (vertex_osd) { x1, y1, 0, color, tx1, ty1 };
|
||||
v[4] = v[2];
|
||||
v[5] = v[1];
|
||||
}
|
||||
}
|
||||
|
||||
d3d_begin_scene(priv);
|
||||
|
||||
IDirect3DDevice9_SetRenderState(priv->d3d_device,
|
||||
|
||||
+103
-88
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
@@ -34,8 +36,6 @@
|
||||
#include "sub/osd.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int have_first_packet;
|
||||
@@ -155,20 +155,12 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
|
||||
vc->codec->height = height;
|
||||
vc->codec->pix_fmt = pix_fmt;
|
||||
|
||||
encode_lavc_set_csp(vo->encode_lavc_ctx, vc->codec, params->colorspace);
|
||||
encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->codec, params->colorlevels);
|
||||
encode_lavc_set_csp(vo->encode_lavc_ctx, vc->codec, params->color.space);
|
||||
encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->codec, params->color.levels);
|
||||
|
||||
if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->codec) < 0)
|
||||
goto error;
|
||||
|
||||
vc->buffer_size = 6 * width * height + 200;
|
||||
if (vc->buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
vc->buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
if (vc->buffer_size < sizeof(AVPicture))
|
||||
vc->buffer_size = sizeof(AVPicture);
|
||||
|
||||
vc->buffer = talloc_size(vc, vc->buffer_size);
|
||||
|
||||
done:
|
||||
pthread_mutex_unlock(&vo->encode_lavc_ctx->lock);
|
||||
return 0;
|
||||
@@ -194,82 +186,120 @@ static int query_format(struct vo *vo, int format)
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void write_packet(struct vo *vo, int size, AVPacket *packet)
|
||||
static void write_packet(struct vo *vo, AVPacket *packet)
|
||||
{
|
||||
struct priv *vc = vo->priv;
|
||||
|
||||
if (size < 0) {
|
||||
MP_ERR(vo, "error encoding\n");
|
||||
packet->stream_index = vc->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
} else {
|
||||
MP_VERBOSE(vo, "codec did not provide pts\n");
|
||||
packet->pts = av_rescale_q(vc->lastipts,
|
||||
vc->worst_time_base,
|
||||
vc->stream->time_base);
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
} else {
|
||||
// HACK: libavformat calculates dts wrong if the initial packet
|
||||
// duration is not set, but ONLY if the time base is "high" and if we
|
||||
// have b-frames!
|
||||
if (!packet->duration)
|
||||
if (!vc->have_first_packet)
|
||||
if (vc->codec->has_b_frames
|
||||
|| vc->codec->max_b_frames)
|
||||
if (vc->stream->time_base.num * 1000LL <=
|
||||
vc->stream->time_base.den)
|
||||
packet->duration = FFMAX(1, av_rescale_q(1,
|
||||
vc->codec->time_base, vc->stream->time_base));
|
||||
}
|
||||
|
||||
if (encode_lavc_write_frame(vo->encode_lavc_ctx,
|
||||
vc->stream, packet) < 0) {
|
||||
MP_ERR(vo, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
vc->stream->time_base.num,
|
||||
vc->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
packet->stream_index = vc->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
} else {
|
||||
MP_VERBOSE(vo, "codec did not provide pts\n");
|
||||
packet->pts = av_rescale_q(vc->lastipts, vc->worst_time_base,
|
||||
vc->stream->time_base);
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
vc->codec->time_base,
|
||||
vc->stream->time_base);
|
||||
} else {
|
||||
// HACK: libavformat calculates dts wrong if the initial packet
|
||||
// duration is not set, but ONLY if the time base is "high" and if we
|
||||
// have b-frames!
|
||||
if (!packet->duration)
|
||||
if (!vc->have_first_packet)
|
||||
if (vc->codec->has_b_frames
|
||||
|| vc->codec->max_b_frames)
|
||||
if (vc->stream->time_base.num * 1000LL <=
|
||||
vc->stream->time_base.den)
|
||||
packet->duration = FFMAX(1, av_rescale_q(1,
|
||||
vc->codec->time_base, vc->stream->time_base));
|
||||
}
|
||||
|
||||
if (encode_lavc_write_frame(vo->encode_lavc_ctx,
|
||||
vc->stream, packet) < 0) {
|
||||
MP_ERR(vo, "error writing\n");
|
||||
return;
|
||||
}
|
||||
|
||||
vc->have_first_packet = 1;
|
||||
}
|
||||
vc->have_first_packet = 1;
|
||||
}
|
||||
|
||||
static int encode_video(struct vo *vo, AVFrame *frame, AVPacket *packet)
|
||||
static void encode_video_and_write(struct vo *vo, AVFrame *frame)
|
||||
{
|
||||
struct priv *vc = vo->priv;
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_video2(vc->codec, packet,
|
||||
frame, &got_packet);
|
||||
int size = (status < 0) ? status : got_packet ? packet->size : 0;
|
||||
AVPacket packet = {0};
|
||||
|
||||
if (frame)
|
||||
MP_DBG(vo, "got pts %f; out size: %d\n",
|
||||
frame->pts * (double) vc->codec->time_base.num /
|
||||
(double) vc->codec->time_base.den, size);
|
||||
|
||||
if (got_packet)
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int status = avcodec_send_frame(vc->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(vo, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
vc->codec->time_base.num,
|
||||
vc->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(vc->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(vo, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(vo, "sent image frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(vo, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
vc->codec->time_base.num,
|
||||
vc->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
encode_lavc_write_stats(vo->encode_lavc_ctx, vc->codec);
|
||||
return size;
|
||||
write_packet(vo, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
#else
|
||||
av_init_packet(&packet);
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_video2(vc->codec, &packet, frame, &got_packet);
|
||||
if (status < 0) {
|
||||
MP_ERR(vo, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
vc->codec->time_base.num,
|
||||
vc->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
if (!got_packet) {
|
||||
return;
|
||||
}
|
||||
encode_lavc_write_stats(vo->encode_lavc_ctx, vc->codec);
|
||||
write_packet(vo, &packet);
|
||||
av_packet_unref(&packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
|
||||
{
|
||||
struct priv *vc = vo->priv;
|
||||
struct encode_lavc_context *ectx = vo->encode_lavc_ctx;
|
||||
int size;
|
||||
AVCodecContext *avc;
|
||||
int64_t frameipts;
|
||||
double nextpts;
|
||||
@@ -398,7 +428,6 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
|
||||
// we have a valid image in lastimg
|
||||
while (vc->lastimg && vc->lastipts < frameipts) {
|
||||
int64_t thisduration = vc->harddup ? 1 : (frameipts - vc->lastipts);
|
||||
AVPacket packet;
|
||||
|
||||
// we will ONLY encode this frame if it can be encoded at at least
|
||||
// vc->mindeltapts after the last encoded frame!
|
||||
@@ -417,20 +446,13 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
|
||||
// this is a nop, unless the worst time base is the STREAM time base
|
||||
frame->pts = av_rescale_q(vc->lastipts + skipframes,
|
||||
vc->worst_time_base, avc->time_base);
|
||||
|
||||
frame->pict_type = 0; // keep this at unknown/undefined
|
||||
|
||||
frame->quality = avc->global_quality;
|
||||
encode_video_and_write(vo, frame);
|
||||
av_frame_free(&frame);
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = vc->buffer;
|
||||
packet.size = vc->buffer_size;
|
||||
size = encode_video(vo, frame, &packet);
|
||||
write_packet(vo, size, &packet);
|
||||
++vc->lastdisplaycount;
|
||||
vc->lastencodedipts = vc->lastipts + skipframes;
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
vc->lastipts += thisduration;
|
||||
@@ -439,14 +461,7 @@ static void draw_image_unlocked(struct vo *vo, mp_image_t *mpi)
|
||||
|
||||
if (!mpi) {
|
||||
// finish encoding
|
||||
do {
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
packet.data = vc->buffer;
|
||||
packet.size = vc->buffer_size;
|
||||
size = encode_video(vo, NULL, &packet);
|
||||
write_packet(vo, size, &packet);
|
||||
} while (size > 0);
|
||||
encode_video_and_write(vo, NULL);
|
||||
} else {
|
||||
if (frameipts >= vc->lastframeipts) {
|
||||
if (vc->lastframeipts != AV_NOPTS_VALUE && vc->lastdisplaycount != 1)
|
||||
|
||||
@@ -305,8 +305,10 @@ static int control(struct vo *vo, uint32_t request, void *data)
|
||||
struct mp_image *screen = gl_read_window_contents(p->gl);
|
||||
// set image parameters according to the display, if possible
|
||||
if (screen) {
|
||||
screen->params.primaries = p->renderer_opts->target_prim;
|
||||
screen->params.gamma = p->renderer_opts->target_trc;
|
||||
screen->params.color = (struct mp_colorspace) {
|
||||
.primaries = p->renderer_opts->target_prim,
|
||||
.gamma = p->renderer_opts->target_trc,
|
||||
};
|
||||
if (p->glctx->flip_v)
|
||||
mp_image_vflip(screen);
|
||||
}
|
||||
|
||||
+1
-1
@@ -529,7 +529,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
|
||||
input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H);
|
||||
input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h};
|
||||
input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h};
|
||||
input->format->es->video.color_space = map_csp(params->colorspace);
|
||||
input->format->es->video.color_space = map_csp(params->color.space);
|
||||
|
||||
if (mmal_port_format_commit(input))
|
||||
return -1;
|
||||
|
||||
+26
-8
@@ -32,8 +32,9 @@
|
||||
#include "common/msg.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "video/mp_image_pool.h"
|
||||
#include "sub/osd.h"
|
||||
#include "video/sws_utils.h"
|
||||
#include "sub/img_convert.h"
|
||||
#include "sub/osd.h"
|
||||
#include "x11_common.h"
|
||||
|
||||
#include "video/mp_image.h"
|
||||
@@ -58,7 +59,6 @@ struct vaapi_osd_part {
|
||||
int change_id;
|
||||
struct vaapi_osd_image image;
|
||||
struct vaapi_subpic subpic;
|
||||
struct osd_conv_cache *conv_cache;
|
||||
};
|
||||
|
||||
#define MAX_OUTPUT_SURFACES 2
|
||||
@@ -225,7 +225,7 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
|
||||
}
|
||||
}
|
||||
|
||||
int flags = va_get_colorspace_flag(p->image_params.colorspace) |
|
||||
int flags = va_get_colorspace_flag(p->image_params.color.space) |
|
||||
p->scaling | VA_FRAME_PICTURE;
|
||||
status = vaPutSurface(p->display,
|
||||
surface,
|
||||
@@ -336,8 +336,6 @@ static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
|
||||
if (imgs->change_id != part->change_id) {
|
||||
part->change_id = imgs->change_id;
|
||||
|
||||
osd_scale_rgba(part->conv_cache, imgs);
|
||||
|
||||
struct mp_rect bb;
|
||||
if (!mp_sub_bitmaps_bb(imgs, &bb))
|
||||
goto error;
|
||||
@@ -365,6 +363,25 @@ static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
|
||||
for (int n = 0; n < imgs->num_parts; n++) {
|
||||
struct sub_bitmap *sub = &imgs->parts[n];
|
||||
|
||||
struct mp_image src = {0};
|
||||
mp_image_setfmt(&src, IMGFMT_BGRA);
|
||||
mp_image_set_size(&src, sub->w, sub->h);
|
||||
src.planes[0] = sub->bitmap;
|
||||
src.stride[0] = sub->stride;
|
||||
|
||||
struct mp_image *bmp = &src;
|
||||
|
||||
struct mp_image *tmp = NULL;
|
||||
if (sub->dw != sub->w || sub->dh != sub->h) {
|
||||
tmp = mp_image_alloc(IMGFMT_BGRA, sub->dw, sub->dh);
|
||||
if (!tmp)
|
||||
goto error;
|
||||
|
||||
mp_image_swscale(tmp, &src, mp_sws_fast_flags);
|
||||
|
||||
bmp = tmp;
|
||||
}
|
||||
|
||||
// Note: nothing guarantees that the sub-bitmaps don't overlap.
|
||||
// But in all currently existing cases, they don't.
|
||||
// We simply hope that this won't change, and nobody will
|
||||
@@ -373,8 +390,10 @@ static void draw_osd_cb(void *pctx, struct sub_bitmaps *imgs)
|
||||
size_t dst = (sub->y - bb.y0) * vaimg.stride[0] +
|
||||
(sub->x - bb.x0) * 4;
|
||||
|
||||
memcpy_pic(vaimg.planes[0] + dst, sub->bitmap, sub->w * 4, sub->h,
|
||||
vaimg.stride[0], sub->stride);
|
||||
memcpy_pic(vaimg.planes[0] + dst, bmp->planes[0], sub->dw * 4,
|
||||
sub->dh, vaimg.stride[0], bmp->stride[0]);
|
||||
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
if (!va_image_unmap(p->mpvaapi, &img->image))
|
||||
@@ -630,7 +649,6 @@ static int preinit(struct vo *vo)
|
||||
struct vaapi_osd_part *part = &p->osd_parts[n];
|
||||
part->image.image.image_id = VA_INVALID_ID;
|
||||
part->image.subpic_id = VA_INVALID_ID;
|
||||
part->conv_cache = talloc_steal(vo, osd_conv_cache_new());
|
||||
}
|
||||
|
||||
int max_display_attrs = vaMaxNumDisplayAttributes(p->display);
|
||||
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user