summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Herkt <lachs0r@srsfckn.biz>2017-02-12 01:01:56 +0100
committerMartin Herkt <lachs0r@srsfckn.biz>2017-02-12 01:01:56 +0100
commit35aa705c3ece8293652ffcf449c71fe80b96e722 (patch)
tree7c0fb34ec96204cbcd867a973b2476689919a5b4
parent10a005df0c981050afc35184a42173bea7ea2527 (diff)
parent3739d1318fdb658bb6037bfe06bb6cefb3b50a09 (diff)
downloadmpv-35aa705c3ece8293652ffcf449c71fe80b96e722.tar.bz2
mpv-35aa705c3ece8293652ffcf449c71fe80b96e722.tar.xz
Merge branch 'master' into release/current
-rw-r--r--DOCS/contribute.md10
-rw-r--r--DOCS/edl-mpv.rst43
-rw-r--r--DOCS/interface-changes.rst18
-rw-r--r--DOCS/man/input.rst19
-rw-r--r--DOCS/man/libmpv.rst59
-rw-r--r--DOCS/man/lua.rst5
-rw-r--r--DOCS/man/mpv.rst6
-rw-r--r--DOCS/man/options.rst219
-rw-r--r--DOCS/mplayer-changes.rst5
-rw-r--r--TOOLS/__init__.py0
-rwxr-xr-xTOOLS/file2string.py14
-rwxr-xr-xTOOLS/matroska.py21
-rw-r--r--audio/decode/ad.h7
-rw-r--r--audio/decode/ad_lavc.c87
-rw-r--r--audio/decode/ad_spdif.c95
-rw-r--r--audio/decode/dec_audio.c21
-rw-r--r--audio/decode/dec_audio.h2
-rw-r--r--audio/filter/af_lavfi.c8
-rw-r--r--audio/filter/af_lavrresample.c3
-rw-r--r--audio/out/ao_oss.c18
-rw-r--r--audio/out/ao_wasapi.c12
-rw-r--r--audio/out/ao_wasapi.h2
-rw-r--r--audio/out/ao_wasapi_changenotify.c2
-rw-r--r--audio/out/ao_wasapi_utils.c41
-rw-r--r--audio/out/push.c7
-rw-r--r--common/av_common.c128
-rw-r--r--common/av_common.h5
-rw-r--r--common/av_log.c8
-rw-r--r--common/codecs.c69
-rw-r--r--common/encode_lavc.c2
-rw-r--r--common/recorder.c384
-rw-r--r--common/recorder.h21
-rw-r--r--demux/cue.c2
-rw-r--r--demux/demux.c137
-rw-r--r--demux/demux.h6
-rw-r--r--demux/demux_disc.c28
-rw-r--r--demux/demux_edl.c152
-rw-r--r--demux/demux_lavf.c54
-rw-r--r--demux/demux_mf.c5
-rw-r--r--demux/demux_mkv.c30
-rw-r--r--demux/demux_mkv_timeline.c2
-rw-r--r--demux/demux_playlist.c2
-rw-r--r--demux/demux_timeline.c180
-rw-r--r--demux/demux_tv.c7
-rw-r--r--demux/ebml.c2
-rw-r--r--demux/packet.c24
-rw-r--r--demux/packet.h2
-rw-r--r--demux/timeline.c4
-rw-r--r--demux/timeline.h4
-rw-r--r--etc/input.conf2
-rw-r--r--etc/restore-old-bindings.conf4
-rw-r--r--input/input.c10
-rw-r--r--input/input.h3
-rw-r--r--libmpv/client.h3
-rw-r--r--libmpv/opengl_cb.h9
-rw-r--r--misc/charset_conv.c89
-rw-r--r--misc/charset_conv.h1
-rw-r--r--options/options.c30
-rw-r--r--options/options.h11
-rw-r--r--options/parse_configfile.c3
-rw-r--r--osdep/atomic.h19
-rw-r--r--osdep/mpv.rc2
-rw-r--r--osdep/windows_utils.c8
-rw-r--r--osdep/windows_utils.h4
-rw-r--r--player/audio.c2
-rw-r--r--player/client.c19
-rw-r--r--player/command.c162
-rw-r--r--player/core.h40
-rw-r--r--player/external_files.c21
-rw-r--r--player/lavfi.c26
-rw-r--r--player/loadfile.c287
-rw-r--r--player/lua.c2
-rw-r--r--player/lua/defaults.lua10
-rw-r--r--player/lua/osc.lua62
-rw-r--r--player/lua/ytdl_hook.lua90
-rw-r--r--player/main.c13
-rw-r--r--player/misc.c71
-rw-r--r--player/osd.c18
-rw-r--r--player/playloop.c6
-rw-r--r--player/screenshot.c16
-rw-r--r--player/scripting.c44
-rw-r--r--player/video.c34
-rw-r--r--stream/cache.c4
-rw-r--r--stream/stream.c67
-rw-r--r--stream/stream.h26
-rw-r--r--stream/stream_avdevice.c2
-rw-r--r--stream/stream_bluray.c3
-rw-r--r--stream/stream_cdda.c1
-rw-r--r--stream/stream_dvb.c3
-rw-r--r--stream/stream_dvd.c1
-rw-r--r--stream/stream_dvdnav.c3
-rw-r--r--stream/stream_edl.c2
-rw-r--r--stream/stream_file.c4
-rw-r--r--stream/stream_lavf.c1
-rw-r--r--stream/stream_mf.c1
-rw-r--r--stream/stream_tv.c1
-rw-r--r--stream/tv.c5
-rw-r--r--stream/tvi_dummy.c5
-rw-r--r--sub/ass_mp.c3
-rw-r--r--sub/dec_sub.c14
-rw-r--r--sub/dec_sub.h3
-rw-r--r--sub/osd.c2
-rw-r--r--sub/osd.h11
-rw-r--r--sub/osd_dummy.c8
-rw-r--r--sub/osd_libass.c28
-rw-r--r--sub/osd_state.h6
-rw-r--r--sub/sd_ass.c4
-rw-r--r--sub/sd_lavc.c48
-rw-r--r--ta/ta_talloc.h2
-rw-r--r--video/decode/dec_video.c100
-rw-r--r--video/decode/dec_video.h3
-rw-r--r--video/decode/hw_cuda.c (renamed from video/decode/cuda.c)41
-rw-r--r--video/decode/hw_d3d11va.c (renamed from video/decode/d3d11va.c)2
-rw-r--r--video/decode/hw_dxva2.c (renamed from video/decode/dxva2.c)47
-rw-r--r--video/decode/hw_vaapi.c171
-rw-r--r--video/decode/hw_vaapi_old.c (renamed from video/decode/vaapi.c)166
-rw-r--r--video/decode/hw_vdpau.c (renamed from video/decode/vdpau.c)56
-rw-r--r--video/decode/hw_videotoolbox.c (renamed from video/decode/videotoolbox.c)0
-rw-r--r--video/decode/lavc.h25
-rw-r--r--video/decode/vd.h9
-rw-r--r--video/decode/vd_lavc.c362
-rw-r--r--video/filter/vf.c17
-rw-r--r--video/filter/vf.h7
-rw-r--r--video/filter/vf_lavfi.c90
-rw-r--r--video/hwdec.h9
-rw-r--r--video/mp_image.c20
-rw-r--r--video/mp_image_pool.c64
-rw-r--r--video/mp_image_pool.h3
-rw-r--r--video/out/cocoa/events_view.m19
-rw-r--r--video/out/cocoa/window.h5
-rw-r--r--video/out/cocoa/window.m87
-rw-r--r--video/out/cocoa_common.m194
-rw-r--r--video/out/drm_common.c2
-rw-r--r--video/out/opengl/angle_dynamic.h7
-rw-r--r--video/out/opengl/context.c2
-rw-r--r--video/out/opengl/context.h5
-rw-r--r--video/out/opengl/context_angle.c897
-rw-r--r--video/out/opengl/context_drm_egl.c2
-rw-r--r--video/out/opengl/context_dxinterop.c23
-rw-r--r--video/out/opengl/context_w32.c14
-rw-r--r--video/out/opengl/context_wayland.c3
-rw-r--r--video/out/opengl/context_x11.c23
-rw-r--r--video/out/opengl/context_x11egl.c34
-rw-r--r--video/out/opengl/egl_helpers.c70
-rw-r--r--video/out/opengl/egl_helpers.h16
-rw-r--r--video/out/opengl/hwdec.c63
-rw-r--r--video/out/opengl/hwdec.h8
-rw-r--r--video/out/opengl/hwdec_cuda.c158
-rw-r--r--video/out/opengl/hwdec_vaegl.c177
-rw-r--r--video/out/opengl/hwdec_vaglx.c2
-rw-r--r--video/out/opengl/hwdec_vdpau.c3
-rw-r--r--video/out/opengl/video.c11
-rw-r--r--video/out/vo.c9
-rw-r--r--video/out/vo_drm.c4
-rw-r--r--video/out/vo_opengl.c22
-rw-r--r--video/out/vo_opengl_cb.c36
-rw-r--r--video/out/vo_vaapi.c10
-rw-r--r--video/out/w32_common.c171
-rw-r--r--video/out/win_state.c21
-rw-r--r--video/out/win_state.h2
-rw-r--r--video/out/x11_common.c31
-rw-r--r--video/out/x11_common.h3
-rw-r--r--video/sws_utils.c2
-rw-r--r--video/vaapi.c315
-rw-r--r--video/vaapi.h10
-rw-r--r--video/vdpau.c66
-rw-r--r--video/vdpau.h1
-rw-r--r--waftools/checks/custom.py30
-rw-r--r--waftools/checks/generic.py23
-rw-r--r--waftools/generators/sources.py57
-rw-r--r--wscript191
-rw-r--r--wscript_build.py70
172 files changed, 5105 insertions, 2347 deletions
diff --git a/DOCS/contribute.md b/DOCS/contribute.md
index 9fc9d8d8bc..994e2040c1 100644
--- a/DOCS/contribute.md
+++ b/DOCS/contribute.md
@@ -101,18 +101,16 @@ mpv uses C99 with K&R formatting, with some exceptions.
do_something();
}
```
-- If the body of an if statement uses braces, the else branch should also
- use braces (and reverse).
+- If the if has an else branch, both branches should use braces, even if they're
+ technically redundant.
Example:
```C
if (a) {
- // do something
- something();
- something_else();
- } else {
one_line();
+ } else {
+ one_other_line();
}
```
- If an if condition spans multiple physical lines, then put the opening brace
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index b0f7238307..7c9f64e160 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -51,7 +51,8 @@ The rest of the lines belong to one of these classes:
1) An empty or commented line. A comment starts with ``#``, which must be the
first character in the line. The rest of the line (up until the next line
break) is ignored. An empty line has 0 bytes between two line feed bytes.
-2) A segment entry in all other cases.
+2) A header entry if the line starts with ``!``.
+3) A segment entry in all other cases.
Each segment entry consists of a list of named or unnamed parameters.
Parameters are separated with ``,``. Named parameters consist of a name,
@@ -63,8 +64,8 @@ Syntax::
segment_entry ::= <param> ( <param> ',' )*
param ::= [ <name> '=' ] ( <value> | '%' <number> '%' <valuebytes> )
-The ``name`` string can consist of any characters, except ``=%,;\n``. The
-``value`` string can consist of any characters except of ``,;\n``.
+The ``name`` string can consist of any characters, except ``=%,;\n!``. The
+``value`` string can consist of any characters except of ``,;\n!``.
The construct starting with ``%`` allows defining any value with arbitrary
contents inline, where ``number`` is an integer giving the number of bytes in
@@ -94,6 +95,42 @@ to ``20``, ``param3`` to ``value,escaped``, ``param4`` to ``value2``.
Instead of line breaks, the character ``;`` can be used. Line feed bytes and
``;`` are treated equally.
+Header entries start with ``!`` as first character after a line break. Header
+entries affect all other file entries in the EDL file. Their format is highly
+implementation specific. They should generally follow the file header, and come
+before any file entries.
+
+MP4 DASH
+========
+
+This is a header that helps implementing DASH, although it only provides a low
+level mechanism.
+
+If this header is set, the given url designates an mp4 init fragment. It's
+downloaded, and every URL in the EDL is prefixed with the init fragment on the
+byte stream level. This is mostly for use by mpv's internal ytdl support. The
+ytdl script will call youtube-dl, which in turn actually processes DASH
+manifests. It may work only for this very specific purpose and fail to be
+useful in other scenarios. It can be removed or changed in incompatible ways
+at any times.
+
+Example::
+
+ !mp4_dash,init=url
+
+The ``url`` is encoded as parameter value as defined in the general EDL syntax.
+It's expected to point to an "initialization fragment", which will be prefixed
+to every entry in the EDL on the byte stream level.
+
+The current implementation will
+
+- ignore stream start times
+- use durations as hint for seeking only
+- not adjust source timestamps
+- open and close segments (i.e. fragments) as needed
+- not add segment boundaries as chapter points
+- require full compatibility between all segments (same codec etc.)
+
Timestamp format
================
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index a6133a3848..3f21bf8063 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -19,6 +19,24 @@ Interface changes
::
+ --- mpv 0.24.0 ---
+ - deprecate --hwdec-api and replace it with --opengl-hwdec-interop.
+ The new option accepts both --hwdec values, as well as named backends.
+ A minor difference is that --hwdec-api=no (which used to be the default)
+ now actually does not preload any interop layer, while the new default
+ ("") uses the value of --hwdec.
+ - drop deprecated --ad/--vd features
+ - drop deprecated --sub-codepage syntax
+ - rename properties:
+ - "drop-frame-count" to "decoder-frame-drop-count"
+ - "vo-drop-frame-count" to "frame-drop-count"
+ The old names still work, but are deprecated.
+ - remove the --stream-capture option and property. No replacement.
+ (--record-file might serve as alternative)
+ - add --sub-justify
+ - add --sub-ass-justify
+ - internally there's a different way to enable the demuxer cache now
+ it can be auto-enabled even if the stream cache remains disabled
--- mpv 0.23.0 ---
- remove deprecated vf_vdpaurb (use "--hwdec=vdpau-copy" instead)
- the following properties now have new semantics:
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 70a05dcaef..1399ef74e1 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -940,15 +940,19 @@ Property list
Total A-V sync correction done. Unavailable if audio or video is
disabled.
-``drop-frame-count``
+``decoder-frame-drop-count``
Video frames dropped by decoder, because video is too far behind audio (when
using ``--framedrop=decoder``). Sometimes, this may be incremented in other
situations, e.g. when video packets are damaged, or the decoder doesn't
follow the usual rules. Unavailable if video is disabled.
-``vo-drop-frame-count``
+ ``drop-frame-count`` is a deprecated alias.
+
+``frame-drop-count``
Frames dropped by VO (when using ``--framedrop=vo``).
+ ``vo-drop-frame-count`` is a deprecated alias.
+
``mistimed-frame-count``
Number of video frames that were not timed correctly in display-sync mode
for the sake of keeping A/V sync. This does not include external
@@ -1331,8 +1335,8 @@ Property list
This is known only once the VO has opened (and possibly later). With some
VOs (like ``opengl``), this might be never known in advance, but only when
the decoder attempted to create the hw decoder successfully. (Using
- ``--hwdec-preload`` can load it eagerly.) If there are multiple drivers
- loaded, they will be separated by ``,``.
+ ``--opengl-hwdec-interop`` can load it eagerly.) If there are multiple
+ drivers loaded, they will be separated by ``,``.
If no VO is active or no interop driver is known, this property is
unavailable.
@@ -1341,6 +1345,9 @@ Property list
multiple interop drivers for the same hardware decoder, depending on
platform and VO.
+ This is somewhat similar to the ``--opengl-hwdec-interop`` option, but
+ it returns the actually loaded backend, not the value of this option.
+
``video-format``
Video format as string.
@@ -1545,10 +1552,6 @@ Property list
This property is experimental and might be removed in the future.
-``stream-capture`` (RW)
- A filename, see ``--stream-capture``. Setting this will start capture using
- the given filename. Setting it to an empty string will stop it.
-
``tv-brightness``, ``tv-contrast``, ``tv-saturation``, ``tv-hue`` (RW)
TV stuff.
diff --git a/DOCS/man/libmpv.rst b/DOCS/man/libmpv.rst
index d3e78aa5bc..909a2bb447 100644
--- a/DOCS/man/libmpv.rst
+++ b/DOCS/man/libmpv.rst
@@ -14,3 +14,62 @@ mpv, further documentation is spread over a few places:
- http://mpv.io/manual/master/#list-of-input-commands
- http://mpv.io/manual/master/#properties
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
+
+C PLUGINS
+=========
+
+You can write C plugins for mpv. These use the libmpv API, although they do not
+use the libmpv library itself.
+
+Currently, they must be explicitly enabled at build time with
+``--enable-cplugins``. They are available on Linux/BSD platforms only.
+
+C plugins location
+------------------
+
+C plugins are put into the mpv scripts directory in its config directory
+(see the `FILES`_ section for details). They must have a ``.so`` file extension.
+They can also be explicitly loaded with the ``--script`` option.
+
+API
+---
+
+A C plugin must export the following function::
+
+ int mpv_open_cplugin(mpv_handle *handle)
+
+The plugin function will be called on loading time. This function does not
+return as long as your plugin is loaded (it runs in its own thread). The
+``handle`` will be deallocated as soon as the plugin function returns.
+
+The return value is interpreted as error status. A value of ``0`` is
+interpreted as success, while ``-1`` signals an error. In the latter case,
+the player prints an uninformative error message that loading failed.
+
+Return values other than ``0`` and ``-1`` are reserved, and trigger undefined
+behavior.
+
+Within the plugin function, you can call libmpv API functions. The ``handle``
+is created by ``mpv_create_client()`` (or actually an internal equivalent),
+and belongs to you. You can call ``mpv_wait_event()`` to wait for things
+happening, and so on.
+
+Note that the player might block until your plugin calls ``mpv_wait_event()``
+for the first time. This gives you a chance to install initial hooks etc.
+before playback begins.
+
+The details are quite similar to Lua scripts.
+
+Linkage to libmpv
+-----------------
+
+The current implementation requires that your plugins are **not** linked against
+libmpv. What your plugins uses are not symbols from a libmpv binary, but
+symbols from the mpv host binary.
+
+Examples
+--------
+
+See:
+
+- https://github.com/mpv-player/mpv-examples/tree/master/cplugins
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index 7d91090ca8..e2f47a862a 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -451,6 +451,11 @@ are useful only in special situations.
multiple properties at once, you might not want to act on each property
change, but only when all change notifications have been received.
+``mp.unregister_idle(fn)``
+ Undo ``mp.register_idle(fn)``. This removes all idle handlers that
+ are equal to the ``fn`` parameter. This uses normal Lua ``==`` comparison,
+ so be careful when dealing with closures.
+
``mp.enable_messages(level)``
Set the minimum log level of which mpv message output to receive. These
messages are normally printed to the terminal. By calling this function,
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index 7108306008..db0382893d 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -140,7 +140,7 @@ L
Toggle infinite looping.
Ctrl + and Ctrl -
- Adjust audio delay by +/- 0.1 seconds.
+ Adjust audio delay (A/V sync) by +/- 0.1 seconds.
u
Switch between applying no style overrides to SSA/ASS subtitles, and
@@ -673,6 +673,10 @@ PROTOCOLS
either aliases to documented protocols, or are just redirections to
protocols implemented and documented in FFmpeg.
+ ``data:`` is supported in FFmpeg (not in Libav), but needs to be in the
+ format ``data://``. This is done to avoid ambiguity with filenames. You
+ can also prefix it with ``lavf://`` or ``ffmpeg://``.
+
``ytdl://...``
By default, the youtube-dl hook script (enabled by default for mpv CLI)
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index eab9f8fefd..af9a3b913d 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -640,8 +640,8 @@ Video
``--opengl-backend=dxinterop`` (Windows only)
:dxva2-copy: copies video back to system RAM (Windows only)
:d3d11va: requires ``--vo=opengl`` with ``--opengl-backend=angle``
- (Windows only)
- :d3d11va-copy: copies video back to system RAM (Windows only)
+ (Windows 8+ only)
+ :d3d11va-copy: copies video back to system RAM (Windows 8+ only)
:mediacodec: copies video back to system RAM (Android only)
:rpi: requires ``--vo=opengl`` (Raspberry Pi only - default if available)
:rpi-copy: copies video back to system RAM (Raspberry Pi only)
@@ -669,6 +669,14 @@ Video
forcing it with ``--opengl-backend=x11``, but the vaapi/GLX interop is
said to be slower than ``vaapi-copy``.
+ The ``cuda`` and ``cuda-copy`` modes provides deinterlacing in the decoder
+ which is useful as there is no other deinterlacing mechanism in the opengl
+ output path. To use this deinterlacing you must pass the option:
+ ``vd-lavc-o=deint=[weave|bob|adaptive]``.
+ Pass ``weave`` (or leave the option unset) to not attempt any
+ deinterlacing. ``cuda`` should always be preferred unless the ``opengl``
+ vo is not being used or filters are required.
+
Most video filters will not work with hardware decoding as they are
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
@@ -719,18 +727,6 @@ Video
affect this additionally. This can give incorrect results even with
completely ordinary video sources.
- ``cuda`` is usually safe. Interlaced content can be deinterlaced by
- the decoder, which is useful as there is no other deinterlacing
- mechanism in the opengl output path. To use this deinterlacing you
- must pass the option: ``vd-lavc-o=deint=[weave|bob|adaptive]``. Pass
- ``weave`` to not attempt any deinterlacing.
- 10 and 12bit HEVC is available if the hardware supports it and a
- sufficiently new driver (> 375.xx) is used.
-
- ``cuda-copy`` has the same behaviour as ``cuda`` - including the ability
- to deinterlace inside the decoder. However, traditional deinterlacing
- filters can be used in this case.
-
``rpi`` always uses the hardware overlay renderer, even with
``--vo=opengl``.
@@ -746,12 +742,12 @@ Video
In particular, ``auto-copy`` will only select safe modes
(although potentially slower than other methods).
-``--hwdec-preload=<api>``
+``--opengl-hwdec-interop=<name>``
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
hardware decoding OpenGL interop context, but without actually enabling
hardware decoding itself (like ``--hwdec`` does).
- If set to ``no`` (default), the ``--hwdec`` option is used.
+ If set to an empty string (default), the ``--hwdec`` option is used.
For ``opengl``, if set, do not create the interop context on demand, but
when the VO is created.
@@ -762,6 +758,19 @@ Video
to temporarily set the ``hwdec`` option just during OpenGL context
initialization with ``mpv_opengl_cb_init_gl()``.
+ See ``--opengl-hwdec-interop=help`` for accepted values. This lists the
+ interop backend, with the ``--hwdec`` alias after it in ``[...]``. Consider
+ all values except the proper interop backend name, ``auto``, and ``no`` as
+ silently deprecated and subject to change. Also, if you use this in
+ application code (e.g. via libmpv), any value other than ``auto`` and ``no``
+ should be avoided, as backends can change.
+
+ Currently the option sets a single value. It is possible that the option
+ type changes to a list in the future.
+
+ The old alias ``--hwdec-preload`` has different behavior if the option value
+ is ``no``.
+
``--videotoolbox-format=<name>``
Set the internal pixel format used by ``--hwdec=videotoolbox`` on OSX. The
choice of the format can influence performance considerably. On the other
@@ -1132,41 +1141,31 @@ Audio
multichannel PCM, and mpv supports lossless DTS-HD decoding via
FFmpeg's new DCA decoder (based on libdcadec).
-``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
+``--ad=<decoder1,decoder2,...[-]>``
Specify a priority list of audio decoders to be used, according to their
decoder name. When determining which decoder to use, the first decoder that
matches the audio format is selected. If that is unavailable, the next
decoder is used. Finally, it tries all other decoders that are not
explicitly selected or rejected by the option.
- Specifying family names is deprecated. Entries like ``family:*`` prioritize
- all decoders of the given family.
-
``-`` at the end of the list suppresses fallback on other available
decoders not on the ``--ad`` list. ``+`` in front of an entry forces the
decoder. Both of these should not normally be used, because they break
normal decoder auto-selection! Both of these methods are deprecated.
- ``-`` in front of an entry disables selection of the decoder. This is
- deprecated.
-
.. admonition:: Examples
``--ad=mp3float``
Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3
decoders.
- ``--ad=lavc:mp3float``
- Prefer the FFmpeg/Libav ``mp3float`` decoder over all other MP3
- decoders. (Using deprecated family syntax.)
-
``--ad=help``
List all available decoders.
.. admonition:: Warning
Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with
- this option is deprecated. Use ``--audio-spdif`` instead.
+ this option is not possible. Use ``--audio-spdif`` instead.
``--volume=<value>``
Set the startup volume. 0 means silence, 100 means no volume reduction or
@@ -1727,6 +1726,13 @@ Subtitles
Disabled by default.
+``--image-subs-video-resolution=<yes|no>``
+ Override the image subtitle resolution with the video resolution
+ (default: no). Normally, the subtitle canvas is fit into the video canvas
+ (e.g. letterboxed). Setting this option uses the video size as subtitle
+ canvas size. Can be useful to test broken subtitles, which often happen
+ when the video was trancoded, while attempting to keep the old subtitles.
+
``--sub-ass``, ``--no-sub-ass``
Render ASS subtitles natively (enabled by default).
@@ -1781,8 +1787,9 @@ Subtitles
subtitles are interpreted as UTF-8 with "Latin 1" as fallback for bytes
which are not valid UTF-8 sequences. iconv is never involved in this mode.
- This option changed in mpv 0.23.0. The old syntax is still emulated to some
- degree.
+ This option changed in mpv 0.23.0. Support for the old syntax was fully
+ removed in mpv 0.24.0.
+
``--sub-fix-timing``, ``--no-sub-fix-timing``
By default, subtitle timing is adjusted to remove minor gaps or overlaps
@@ -1965,6 +1972,18 @@ Subtitles
Vertical position (default: ``bottom``).
Details see ``--sub-align-x``.
+``--sub-justify=<auto|left|center|right>``
+ Control how multi line subs are justified irrespective of where they
+ are aligned (default: ``auto`` which justifies as defined by
+ ``--sub-align-y``).
+ Left justification is recommended to make the subs easier to read
+ as it is easier for the eyes.
+
+``--sub-ass-justify=<yes|no>``
+ Applies justification as defined by ``--sub-justify`` on ASS subtitles
+ if ``--sub-ass-style-override`` is not set to ``no``.
+ Default: ``no``.
+
``--sub-shadow-color=<color>``
See ``--sub-color``. Color used for sub text shadow.
@@ -2101,6 +2120,9 @@ Window
Enabled by default.
+``--snap-window``
+ (Windows only) Snap the player window to screen edges.
+
``--ontop``
Makes the player window stay on top of other windows.
@@ -2339,7 +2361,7 @@ Window
- ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777``
``--hidpi-window-scale``, ``--no-hidpi-window-scale``
- (OS X only)
+ (OS X and X11 only)
Scale the window size according to the backing scale factor (default: yes).
On regular HiDPI resolutions the window opens with double the size but appears
as having the same size as on none-HiDPI resolutions. This is the default OS X
@@ -2724,6 +2746,27 @@ Demuxer
(This value tends to be fuzzy, because many file formats don't store linear
timestamps.)
+``--prefetch-playlist=<yes|no>``
+ Prefetch next playlist entry while playback of the current entry is ending
+ (default: no). This merely opens the URL of the next playlist entry as soon
+ as the current URL is fully read.
+
+ This does **not** work with URLs resolved by the ``youtube-dl`` wrapper,
+ and it won't.
+
+ This does not affect HLS (``.m3u8`` URLs) - HLS prefetching depends on the
+ demuxer cache settings and is on by default.
+
+ This can give subtly wrong results if per-file options are used, or if
+ options are changed in the time window between prefetching start and next
+ file played.
+
+ This can occasionally make wrong prefetching decisions. For example, it
+ can't predict whether you go backwards in the playlist, and assumes you
+ won't edit the playlist.
+
+ Highly experimental.
+
``--force-seekable=<yes|no>``
If the player thinks that the media is not seekable (e.g. playing from a
pipe, or it's an http stream with a server that doesn't support range
@@ -4288,16 +4331,70 @@ The following video options are currently all specific to ``--vo=opengl`` and
Windows only.
-``--opengl-dcomposition=<yes|no>``
- Allows DirectComposition when using the ANGLE backend (default: yes).
- DirectComposition implies flip-model presentation, which can improve
- rendering efficiency on Windows 8+ by avoiding a copy of the video frame.
- mpv uses it by default where possible, but it can cause poor behaviour with
- some drivers, such as a black screen or graphical corruption when leaving
- full-screen mode. Use "no" to disable it.
+``--angle-d3d11-feature-level=<11_0|10_1|10_0|9_3>``
+ Selects a specific feature level when using the ANGLE backend with D3D11.
+ By default, the highest available feature level is used. This option can be
+ used to select a lower feature level, which is mainly useful for debugging.
+ Note that OpenGL ES 3.0 is only supported at feature level 10_1 or higher.
+ Most extended OpenGL features will not work at lower feature levels
+ (similar to ``--opengl-dumb-mode``).
Windows with ANGLE only.
+``--angle-d3d11-warp=<yes|no|auto>``
+ Use WARP (Windows Advanced Rasterization Platform) when using the ANGLE
+ backend with D3D11 (default: auto). This is a high performance software
+ renderer. By default, it is used when the Direct3D hardware does not
+ support Direct3D 11 feature level 9_3. While the extended OpenGL features
+ will work with WARP, they can be very slow.
+
+ Windows with ANGLE only.
+
+``--angle-egl-windowing=<yes|no|auto>``
+ Use ANGLE's built in EGL windowing functions to create a swap chain
+ (default: auto). If this is set to ``no`` and the D3D11 renderer is in use,
+ ANGLE's built in swap chain will not be used and a custom swap chain that
+ is optimized for video rendering will be created instead. If set to
+ ``auto``, a custom swap chain will be used for D3D11 and the built in swap
+ chain will be used for D3D9. This option is mainly for debugging purposes,
+ in case the custom swap chain has poor performance or does not work.
+
+ If set to ``yes``, the ``--angle-max-frame-latency`` and
+ ``--angle-swapchain-length`` options will have no effect.
+
+ Windows with ANGLE only.
+
+``--angle-max-frame-latency=<1-16>``
+ Sets the maximum number of frames that the system is allowed to queue for
+ rendering with the ANGLE backend (default: 3). Lower values should make
+ VSync timing more accurate, but a value of ``1`` requires powerful
+ hardware, since the CPU will not be able to "render ahead" of the GPU.
+
+ Windows with ANGLE only.
+
+``--angle-renderer=<d3d9|d3d11|auto>``
+ Forces a specific renderer when using the ANGLE backend (default: auto). In
+ auto mode this will pick D3D11 for systems that support Direct3D 11 feature
+ level 9_3 or higher, and D3D9 otherwise. This option is mainly for
+ debugging purposes. Normally there is no reason to force a specific
+ renderer, though ``--angle-renderer=d3d9`` may give slightly better
+ performance on old hardware. Note that the D3D9 renderer only supports
+ OpenGL ES 2.0, so most extended OpenGL features will not work if this
+ renderer is selected (similar to ``--opengl-dumb-mode``).
+
+ Windows with ANGLE only.
+
+``--angle-swapchain-length=<2-16>``
+ Sets the number of buffers in the D3D11 presentation queue when using the
+ ANGLE backend (default: 6). At least 2 are required, since one is the back
+ buffer that mpv renders to and the other is the front buffer that is
+ presented by the DWM. Additional buffers can improve performance, because
+ for example, mpv will not have to wait on the DWM to release the front
+ buffer before rendering a new frame to it. For this reason, Microsoft
+ recommends at least 4.
+
+ Windows 8+ with ANGLE only.
+
``--opengl-sw``
Continue even if a software renderer is detected.
@@ -4322,6 +4419,9 @@ The following video options are currently all specific to ``--vo=opengl`` and
work.
x11
X11/GLX
+ x11probe
+ For internal autoprobing, equivalent to ``x11`` otherwise. Don't use
+ directly, it could be removed without warning as autoprobing is changed.
wayland
Wayland/EGL
drm
@@ -4561,6 +4661,7 @@ The following video options are currently all specific to ``--vo=opengl`` and
unavailable, it silently falls back on a normal framebuffer. Note that
if you set the ``--opengl-fbo-format`` option to a non-default value, a
format with alpha must be specified, or this won't work.
+ This does not work on X11 with EGL and Mesa (freedesktop bug 67676).
no
Ignore alpha component.
@@ -4716,21 +4817,10 @@ Miscellaneous
Input file type for ``mf://`` (available: jpeg, png, tga, sgi). By default,
this is guessed from the file extension.
-``--stream-capture=<filename>``
- Allows capturing the primary stream (not additional audio tracks or other
- kind of streams) into the given file. Capturing can also be started and
- stopped by changing the filename with the ``stream-capture`` property.
- Generally this will not produce usable results for anything else than MPEG
- or raw streams, unless capturing includes the file headers and is not
- interrupted. Note that, due to cache latencies, captured data may begin and
- end somewhat delayed compared to what you see displayed.
-
- The destination file is always appended. (Before mpv 0.8.0, the file was
- overwritten.)
-
-``--stream-dump=<filename>``
- Same as ``--stream-capture``, but do not start playback. Instead, the entire
- file is dumped.
+``--stream-dump=<destination-filename>``
+ Instead of playing a file, read its byte stream and write it to the given
+ destination file. The destination is overwritten. Can be useful to test
+ network-related behavior.
``--stream-lavf-o=opt1=value1,opt2=value2,...``
Set AVOptions on streams opened with libavformat. Unknown or misspelled
@@ -4773,6 +4863,29 @@ Miscellaneous
This does not affect playlist expansion, redirection, or other loading of
referenced files like with ordered chapters.
+``--record-file=<file>``
+ Record the current stream to the given target file. The target file will
+ always be overwritten without asking.
+
+ This remuxes the source stream without reencoding, which makes this a
+ highly fragile and experimental feature. It's entirely possible that this
+ writes files which are broken, not standards compliant, not playable with
+ all players (including mpv), or incomplete.
+
+ The target file format is determined by the file extension of the target
+ filename. It is recommended to use the same target container as the source
+ container if possible, and preferring Matroska as fallback.
+
+ Seeking during stream recording, or enabling/disabling stream recording
+ during playback, can cut off data, or produce "holes" in the output file.
+ These are technical restrictions. In particular, video data or subtitles
+ which were read ahead can produce such holes, which might cause playback
+ problems with various players (including mpv).
+
+ The behavior of this option might changed in the future, such as changing
+ it to a template (similar to ``--screenshot-template``), being renamed,
+ removed, or anything else, until it is declared semi-stable.
+
``--lavfi-complex=<string>``
Set a "complex" libavfilter filter, which means a single filter graph can
take input from multiple source audio and video tracks. The graph can result
diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst
index 397bb47c9e..df33f66e3f 100644
--- a/DOCS/mplayer-changes.rst
+++ b/DOCS/mplayer-changes.rst
@@ -8,7 +8,10 @@ behaves. Although there are still many similarities to its ancestors, **mpv**
should generally be treated as a completely different program.
.. admonition:: Warning
- This document is not updated anymore, and is incomplete and outdated.
+
+ This document is **not updated** anymore, and is **incomplete** and
+ **outdated**. If you look for old option replacements, always check with
+ the current mpv manpage, as the options could have changed meanwhile.
General Changes from MPlayer to mpv
-----------------------------------
diff --git a/TOOLS/__init__.py b/TOOLS/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/TOOLS/__init__.py
diff --git a/TOOLS/file2string.py b/TOOLS/file2string.py
index 6cdd1a72ae..90c9e03368 100755
--- a/TOOLS/file2string.py
+++ b/TOOLS/file2string.py
@@ -5,23 +5,27 @@
# of every string, so code using the string may need to remove that to get
# the exact contents of the original file.
+from __future__ import unicode_literals
import sys
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
def pord(c):
return ord(c) if type(c) == str else c
-def main(infile):
+def file2string(infilename, infile, outfile):
+ outfile.write("// Generated from %s\n\n" % infilename)
+
conv = ['\\' + ("%03o" % c) for c in range(256)]
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
+
for c in safe_chars:
conv[ord(c)] = c
for c, esc in ("\nn", "\tt", r"\\", '""'):
conv[ord(c)] = '\\' + esc
for line in infile:
- sys.stdout.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
+ outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
-with open(sys.argv[1], 'rb') as infile:
- sys.stdout.write("// Generated from %s\n\n" % sys.argv[1])
- main(infile)
+if __name__ == "__main__":
+ with open(sys.argv[1], 'rb') as infile:
+ file2string(sys.argv[1], infile, sys.stdout)
diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py
index 6e843560da..cf55db42a4 100755
--- a/TOOLS/matroska.py
+++ b/TOOLS/matroska.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
Generate C definitions for parsing Matroska files.
Can also be used to directly parse Matroska files and display their contents.
@@ -69,6 +69,12 @@ elements_matroska = (
'BlockDuration, 9b, uint',
'ReferenceBlock*, fb, sint',
'DiscardPadding, 75A2, sint',
+ 'BlockAdditions, 75A1, sub', (
+ 'BlockMore*, A6, sub', (
+ 'BlockAddID, EE, uint',
+ 'BlockAdditional, A5, binary',
+ ),
+ ),
),
'SimpleBlock*, a3, binary',
),
@@ -271,6 +277,7 @@ def parse_elems(l, namespace):
for el in l:
if isinstance(el, str):
name, hexid, eltype = [x.strip() for x in el.split(',')]
+ hexid = hexid.lower()
multiple = name.endswith('*')
name = name.strip('*')
new = MatroskaElement(name, hexid, eltype, namespace)
@@ -285,8 +292,8 @@ parse_elems(elements_ebml, 'EBML')
parse_elems(elements_matroska, 'MATROSKA')
def printf(out, *args):
- out.write(' '.join([str(x) for x in args]))
- out.write('\n')
+ out.write(u' '.join([str(x) for x in args]))
+ out.write(u'\n')
def generate_C_header(out):
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
@@ -400,10 +407,6 @@ def read_float(s, length):
def parse_one(s, depth, parent, maxlen):
elid = hexlify(read_id(s)).decode('ascii')
elem = elementd.get(elid)
- if parent is not None and elid not in parent.subids and elid not in ('ec', 'bf'):
- print('Unexpected:', elid)
- if 1:
- raise NotImplementedError
size, length = read_vint(s)
this_length = len(elid) / 2 + size + length
if elem is not None:
@@ -442,7 +445,7 @@ def parse_one(s, depth, parent, maxlen):
else:
raise NotImplementedError
else:
- print(depth, 'Unknown element:', elid, 'size:', length)
+ print(" " * depth, '[' + elid + '] Unknown element! size:', length)
read(s, length)
return this_length
@@ -455,6 +458,8 @@ if __name__ == "__main__":
elif sys.argv[1] == '--generate-definitions':
generate_C_definitions(sys.stdout)
else:
+ if sys.version_info.major < 3:
+ raise Exception("Dumping requires Python 3.")
s = open(sys.argv[1], "rb")
while 1:
start = s.tell()
diff --git a/audio/decode/ad.h b/audio/decode/ad.h
index 771ceb7e88..bbb050eb4c 100644
--- a/audio/decode/ad.h
+++ b/audio/decode/ad.h
@@ -35,8 +35,11 @@ struct ad_functions {
int (*init)(struct dec_audio *da, const char *decoder);
void (*uninit)(struct dec_audio *da);
int (*control)(struct dec_audio *da, int cmd, void *arg);
- int (*decode_packet)(struct dec_audio *da, struct demux_packet *pkt,
- struct mp_audio **out);
+ // Return whether or not the packet has been consumed.
+ bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
+ // Return whether decoding is still going on (false if EOF was reached).
+ // Never returns false & *out set, but can return true with !*out.
+ bool (*receive_frame)(struct dec_audio *da, struct mp_audio **out);
};
enum ad_ctrl {
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index c4d3a2ae7b..7f3abfd612 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -45,7 +45,6 @@ struct priv {
uint32_t skip_samples, trim_samples;
bool preroll_done;
double next_pts;
- bool needs_reset;
AVRational codec_timebase;
};
@@ -116,26 +115,18 @@ static int init(struct dec_audio *da, const char *decoder)
av_opt_set_double(lavc_context, "drc_scale", opts->ac3drc,
AV_OPT_SEARCH_CHILDREN);
-#if HAVE_AVFRAME_SKIP_SAMPLES
+#if LIBAVCODEC_VERSION_MICRO >= 100
// Let decoder add AV_FRAME_DATA_SKIP_SAMPLES.
av_opt_set(lavc_context, "flags2", "+skip_manual", AV_OPT_SEARCH_CHILDREN);
#endif
mp_set_avopts(da->log, lavc_context, opts->avopts);
- lavc_context->codec_tag = c->codec_tag;
- lavc_context->sample_rate = c->samplerate;
- lavc_context->bit_rate = c->bitrate;
- lavc_context->block_align = c->block_align;
- lavc_context->bits_per_coded_sample = c->bits_per_coded_sample;
- lavc_context->channels = c->channels.num;
- if (!mp_chmap_is_unknown(&c->channels))
- lavc_context->channel_layout = mp_chmap_to_lavc(&c->channels);
-
- // demux_mkv
- mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
-
- mp_set_lav_codec_headers(lavc_context, c);
+ if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
+ MP_ERR(da, "Could not set decoder parameters.\n");
+ uninit(da);
+ return 0;
+ }
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
@@ -177,14 +168,12 @@ static int control(struct dec_audio *da, int cmd, void *arg)
ctx->trim_samples = 0;
ctx->preroll_done = false;
ctx->next_pts = MP_NOPTS_VALUE;
- ctx->needs_reset = false;
return CONTROL_TRUE;
}
return CONTROL_UNKNOWN;
}
-static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
- struct mp_audio **out)
+static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
{
struct priv *priv = da->priv;
AVCodecContext *avctx = priv->avctx;
@@ -195,41 +184,48 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
priv->next_pts = mpkt->pts;
- int in_len = mpkt ? mpkt->len : 0;
-
AVPacket pkt;
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
- int got_frame = 0;
- av_frame_unref(priv->avframe);
+ int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
- if (priv->needs_reset)
- control(da, ADCTRL_RESET, NULL);
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ return false;
- int ret = avcodec_send_packet(avctx, &pkt);
- if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
- if (ret >= 0 && mpkt)
- mpkt->len = 0;
- ret = avcodec_receive_frame(avctx, priv->avframe);
- if (ret >= 0)
- got_frame = 1;
- if (ret == AVERROR_EOF)
- priv->needs_reset = true;
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
- ret = 0;
- }
- if (ret < 0) {
+ if (ret < 0)
+ MP_ERR(da, "Error decoding audio.\n");
+ return true;
+}
+
+static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
+{
+ struct priv *priv = da->priv;
+ AVCodecContext *avctx = priv->avctx;
+
+ int ret = avcodec_receive_frame(avctx, priv->avframe);
+
+ if (ret == AVERROR_EOF) {
+ // If flushing was initialized earlier and has ended now, make it start
+ // over in case we get new packets at some point in the future.
+ control(da, ADCTRL_RESET, NULL);
+ return false;
+ } else if (ret < 0 && ret != AVERROR(EAGAIN)) {
MP_ERR(da, "Error decoding audio.\n");
- return -1;
}
- if (!got_frame)
- return 0;
+
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ if (priv->avframe->flags & AV_FRAME_FLAG_DISCARD)
+ av_frame_unref(priv->avframe);
+#endif
+
+ if (!priv->avframe->buf[0])
+ return true;
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
if (!mpframe)
- return -1;
+ return true;
struct mp_chmap lavc_chmap = mpframe->channels;
if (lavc_chmap.num != avctx->channels)
@@ -247,7 +243,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
if (mpframe->pts != MP_NOPTS_VALUE)
priv->next_pts = mpframe->pts + mpframe->samples / (double)mpframe->rate;
-#if HAVE_AVFRAME_SKIP_SAMPLES
+#if LIBAVCODEC_VERSION_MICRO >= 100
AVFrameSideData *sd =
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
if (sd && sd->size >= 10) {
@@ -279,8 +275,8 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
av_frame_unref(priv->avframe);
- MP_DBG(da, "Decoded %d -> %d samples\n", in_len, mpframe->samples);
- return 0;
+ MP_DBG(da, "Decoded %d samples\n", mpframe->samples);
+ return true;
}
static void add_decoders(struct mp_decoder_list *list)
@@ -294,5 +290,6 @@ const struct ad_functions ad_lavc = {
.init = init,
.uninit = uninit,
.control = control,
- .decode_packet = decode_packet,
+ .send_packet = send_packet,
+ .receive_frame = receive_frame,
};
diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c
index 30c7883bf4..e38c9e5077 100644
--- a/audio/decode/ad_spdif.c
+++ b/audio/decode/ad_spdif.c
@@ -42,6 +42,8 @@ struct spdifContext {
bool use_dts_hd;
struct mp_audio fmt;
struct mp_audio_pool *pool;
+ bool got_eof;
+ struct demux_packet *queued_packet;
};
static int write_packet(void *p, uint8_t *buf, int buf_size)
@@ -71,6 +73,7 @@ static void uninit(struct dec_audio *da)
av_freep(&lavf_ctx->pb->buffer);
av_freep(&lavf_ctx->pb);
avformat_free_context(lavf_ctx);
+ talloc_free(spdif_ctx->queued_packet);
spdif_ctx->lavf_ctx = NULL;
}
}
@@ -90,13 +93,34 @@ static int init(struct dec_audio *da, const char *decoder)
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
}
-static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
+static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
+ int *out_profile, int *out_rate)
{
struct spdifContext *spdif_ctx = da->priv;
int profile = FF_PROFILE_UNKNOWN;
AVCodecContext *ctx = NULL;
AVFrame *frame = NULL;
+ AVCodecParserContext *parser = av_parser_init(spdif_ctx->codec_id);
+ if (parser) {
+ // Don't make it wait for the next frame.
+ parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+
+ ctx = avcodec_alloc_context3(NULL);
+
+ uint8_t *d = NULL;
+ int s = 0;
+ av_parser_parse2(parser, ctx, &d, &s, pkt->data, pkt->size, 0, 0, 0);
+ *out_profile = profile = ctx->profile;
+ *out_rate = ctx->sample_rate;
+
+ av_free(ctx);
+ av_parser_close(parser);
+ }
+
+ if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS)
+ return;
+
AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
if (!codec)
goto done;
@@ -120,7 +144,8 @@ static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
if (avcodec_receive_frame(ctx, frame) < 0)
goto done;
- profile = ctx->profile;
+ *out_profile = profile = ctx->profile;
+ *out_rate = ctx->sample_rate;
done:
av_frame_free(&frame);
@@ -130,8 +155,6 @@ done:
if (profile == FF_PROFILE_UNKNOWN)
MP_WARN(da, "Failed to parse codec profile.\n");
-
- return profile;
}
static int init_filter(struct dec_audio *da, AVPacket *pkt)
@@ -139,8 +162,9 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
struct spdifContext *spdif_ctx = da->priv;
int profile = FF_PROFILE_UNKNOWN;
- if (spdif_ctx->codec_id == AV_CODEC_ID_DTS)
- profile = determine_codec_profile(da, pkt);
+ int c_rate = 0;
+ determine_codec_params(da, pkt, &profile, &c_rate);
+ MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate);
AVFormatContext *lavf_ctx = avformat_alloc_context();
if (!lavf_ctx)
@@ -186,7 +210,7 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
break;
case AV_CODEC_ID_AC3:
sample_format = AF_FORMAT_S_AC3;
- samplerate = 48000;
+ samplerate = c_rate > 0 ? c_rate : 48000;
num_channels = 2;
break;
case AV_CODEC_ID_DTS: {
@@ -243,44 +267,72 @@ fail:
return -1;
}
-static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
- struct mp_audio **out)
+
+static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
{
struct spdifContext *spdif_ctx = da->priv;
- spdif_ctx->out_buffer_len = 0;
+ if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
+ return false;
- if (!mpkt)
- return 0;
+ spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
+ spdif_ctx->got_eof = !mpkt;
+ return true;
+}
- double pts = mpkt->pts;
+static bool receive_frame(struct dec_audio *da, struct mp_audio **out)
+{
+ struct spdifContext *spdif_ctx = da->priv;
+
+ if (spdif_ctx->got_eof) {
+ spdif_ctx->got_eof = false;
+ return false;
+ }
+
+ if (!spdif_ctx->queued_packet)
+ return true;
+
+ double pts = spdif_ctx->queued_packet->pts;
AVPacket pkt;
- mp_set_av_packet(&pkt, mpkt, NULL);
- mpkt->len = 0; // will be fully consumed
+ mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
pkt.pts = pkt.dts = 0;
if (!spdif_ctx->lavf_ctx) {
if (init_filter(da, &pkt) < 0)
- return -1;
+ goto done;
}
+ spdif_ctx->out_buffer_len = 0;
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
avio_flush(spdif_ctx->lavf_ctx->pb);
- if (ret < 0)
- return -1;
+ if (ret < 0) {
+ MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
+ goto done;
+ }
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
*out = mp_audio_pool_get(spdif_ctx->pool, &spdif_ctx->fmt, samples);
if (!*out)
- return -1;
+ goto done;
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
(*out)->pts = pts;
- return 0;
+done:
+ talloc_free(spdif_ctx->queued_packet);
+ spdif_ctx->queued_packet = NULL;
+ return true;
}
static int control(struct dec_audio *da, int cmd, void *arg)
{
+ struct spdifContext *spdif_ctx = da->priv;
+ switch (cmd) {
+ case ADCTRL_RESET:
+ talloc_free(spdif_ctx->queued_packet);
+ spdif_ctx->queued_packet = NULL;
+ spdif_ctx->got_eof = false;
+ return CONTROL_TRUE;
+ }
return CONTROL_UNKNOWN;
}
@@ -344,5 +396,6 @@ const struct ad_functions ad_spdif = {
.init = init,
.uninit = uninit,
.control = control,
- .decode_packet = decode_packet,
+ .send_packet = send_packet,
+ .receive_frame = receive_frame,
};
diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c
index 9f28302bd5..5a2735ef20 100644
--- a/audio/decode/dec_audio.c
+++ b/audio/decode/dec_audio.c
@@ -27,6 +27,7 @@
#include "common/codecs.h"
#include "common/msg.h"
+#include "common/recorder.h"
#include "misc/bstr.h"
#include "stream/stream.h"
@@ -201,7 +202,7 @@ static void fix_audio_pts(struct dec_audio *da)
void audio_work(struct dec_audio *da)
{
- if (da->current_frame)
+ if (da->current_frame || !da->ad_driver)
return;
if (!da->packet && !da->new_segment &&
@@ -217,30 +218,28 @@ void audio_work(struct dec_audio *da)
da->packet = NULL;
}
- bool had_input_packet = !!da->packet;
- bool had_packet = da->packet || da->new_segment;
+ if (da->ad_driver->send_packet(da, da->packet)) {
+ if (da->recorder_sink)
+ mp_recorder_feed_packet(da->recorder_sink, da->packet);
- int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
- if (ret < 0 || (da->packet && da->packet->len == 0)) {
talloc_free(da->packet);
da->packet = NULL;
}
+ bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
+
if (da->current_frame && !mp_audio_config_valid(da->current_frame)) {
talloc_free(da->current_frame);
da->current_frame = NULL;
}
- da->current_state = DATA_OK;
- if (!da->current_frame) {
+ da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
+ if (!progress)
da->current_state = DATA_EOF;
- if (had_packet)
- da->current_state = DATA_AGAIN;
- }
fix_audio_pts(da);
- bool segment_end = !da->current_frame && !had_input_packet;
+ bool segment_end = da->current_state == DATA_EOF;
if (da->current_frame) {
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
diff --git a/audio/decode/dec_audio.h b/audio/decode/dec_audio.h
index ebe7c8ae5b..02447d6742 100644
--- a/audio/decode/dec_audio.h
+++ b/audio/decode/dec_audio.h
@@ -37,6 +37,8 @@ struct dec_audio {
bool try_spdif;
+ struct mp_recorder_sink *recorder_sink;
+
// For free use by the ad_driver
void *priv;
diff --git a/audio/filter/af_lavfi.c b/audio/filter/af_lavfi.c
index bc4a687487..55fb7cb0dc 100644
--- a/audio/filter/af_lavfi.c
+++ b/audio/filter/af_lavfi.c
@@ -246,7 +246,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
{
-#if HAVE_AVFRAME_METADATA
+#if LIBAVUTIL_VERSION_MICRO >= 100
struct priv *p = af->priv;
if (!p->metadata)
p->metadata = talloc_zero(p, struct mp_tags);
@@ -266,6 +266,12 @@ static int filter_frame(struct af_instance *af, struct mp_audio *data)
if (!p->graph)
goto error;
+ if (!data) {
+ if (p->eof)
+ return 0;
+ p->eof = true;
+ }
+
if (data) {
frame = mp_audio_to_avframe_and_unref(data);
data = NULL;
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index 828be66247..47c374b227 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -36,6 +36,9 @@
#include "common/common.h"
#include "config.h"
+#define HAVE_LIBSWRESAMPLE HAVE_IS_FFMPEG
+#define HAVE_LIBAVRESAMPLE HAVE_IS_LIBAV
+
#if HAVE_LIBAVRESAMPLE
#include <libavresample/avresample.h>
#elif HAVE_LIBSWRESAMPLE
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
index c0446eb2aa..7e73644b8f 100644
--- a/audio/out/ao_oss.c
+++ b/audio/out/ao_oss.c
@@ -279,14 +279,21 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
int format = ao->format;
struct mp_chmap channels = ao->channels;
+ const char *device = PATH_DEV_DSP;
+ if (ao->device)
+ device = ao->device;
+ if (p->dsp && p->dsp[0])
+ device = p->dsp;
+
+ MP_VERBOSE(ao, "using '%s' dsp device\n", device);
#ifdef __linux__
- p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
+ p->audio_fd = open(device, O_WRONLY | O_NONBLOCK);
#else
- p->audio_fd = open(p->dsp, O_WRONLY);
+ p->audio_fd = open(device, O_WRONLY);
#endif
if (p->audio_fd < 0) {
MP_ERR(ao, "Can't open audio device %s: %s\n",
- p->dsp, mp_strerror(errno));
+ device, mp_strerror(errno));
goto fail;
}
@@ -443,9 +450,8 @@ static int init(struct ao *ao)
p->oss_mixer_channel = SOUND_MIXER_PCM;
}
- MP_VERBOSE(ao, "using '%s' dsp device\n", p->dsp);
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
- MP_VERBOSE(ao, "using '%s' mixer device\n", mixer_channels[p->oss_mixer_channel]);
+ MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]);
ao->format = af_fmt_from_planar(ao->format);
@@ -643,8 +649,6 @@ const struct ao_driver audio_out_oss = {
.buffersize = -1,
.outburst = 512,
.oss_mixer_channel = SOUND_MIXER_PCM,
-
- .dsp = PATH_DEV_DSP,
.oss_mixer_device = PATH_DEV_MIXER,
},
.options = (const struct m_option[]) {
diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c
index b2e035d3dc..3a4c341387 100644
--- a/audio/out/ao_wasapi.c
+++ b/audio/out/ao_wasapi.c
@@ -255,9 +255,9 @@ static void uninit(struct ao *ao)
"while waiting for audio thread to terminate\n");
}
- SAFE_RELEASE(state->hInitDone, CloseHandle(state->hInitDone));
- SAFE_RELEASE(state->hWake, CloseHandle(state->hWake));
- SAFE_RELEASE(state->hAudioThread,CloseHandle(state->hAudioThread));
+ SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
+ SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
+ SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
wasapi_change_uninit(ao);
@@ -305,7 +305,7 @@ static int init(struct ao *ao)
}
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
- SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone));
+ SAFE_DESTROY(state->hInitDone,CloseHandle(state->hInitDone));
if (FAILED(state->init_ret)) {
if (!ao->probing)
MP_FATAL(ao, "Received failure from audio thread\n");
@@ -418,10 +418,10 @@ static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg)
do {
IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
- SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
+ SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp);
} while (lstrcmpW(title, tmp));
- SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
+ SAFE_DESTROY(tmp, CoTaskMemFree(tmp));
talloc_free(title);
return CONTROL_OK;
}
diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h
index 65f16d11c1..8f6e38867c 100644
--- a/audio/out/ao_wasapi.h
+++ b/audio/out/ao_wasapi.h
@@ -47,7 +47,7 @@ void wasapi_change_uninit(struct ao* ao);
#define EXIT_ON_ERROR(hres) \
do { if (FAILED(hres)) { goto exit_label; } } while(0)
-#define SAFE_RELEASE(unk, release) \
+#define SAFE_DESTROY(unk, release) \
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
#define mp_format_res_str(hres) \
diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c
index e1b3a9d604..b9806e0adb 100644
--- a/audio/out/ao_wasapi_changenotify.c
+++ b/audio/out/ao_wasapi_changenotify.c
@@ -242,5 +242,5 @@ void wasapi_change_uninit(struct ao *ao)
change->pEnumerator, (IMMNotificationClient *)change);
}
- SAFE_RELEASE(change->pEnumerator, IMMDeviceEnumerator_Release(change->pEnumerator));
+ SAFE_RELEASE(change->pEnumerator);
}
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index 4667b57ae8..a449dbec10 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -539,8 +539,7 @@ static void init_session_display(struct wasapi_state *state) {
return;
exit_label:
// if we got here then the session control is useless - release it
- SAFE_RELEASE(state->pSessionControl,
- IAudioSessionControl_Release(state->pSessionControl));
+ SAFE_RELEASE(state->pSessionControl);
MP_WARN(state, "Error setting audio session display name: %s\n",
mp_HRESULT_to_str(hr));
return;
@@ -571,10 +570,8 @@ static void init_volume_control(struct wasapi_state *state)
return;
exit_label:
state->vol_hw_support = 0;
- SAFE_RELEASE(state->pEndpointVolume,
- IAudioEndpointVolume_Release(state->pEndpointVolume));
- SAFE_RELEASE(state->pAudioVolume,
- ISimpleAudioVolume_Release(state->pAudioVolume));
+ SAFE_RELEASE(state->pEndpointVolume);
+ SAFE_RELEASE(state->pAudioVolume);
MP_WARN(state, "Error setting up volume control: %s\n",
mp_HRESULT_to_str(hr));
}
@@ -707,7 +704,7 @@ exit_label:
if (FAILED(hr))
mp_warn(l, "Failed getting device name: %s\n", mp_HRESULT_to_str(hr));
PropVariantClear(&devname);
- SAFE_RELEASE(pProps, IPropertyStore_Release(pProps));
+ SAFE_RELEASE(pProps);
return namestr ? namestr : talloc_strdup(talloc_ctx, "");
}
@@ -722,7 +719,7 @@ static struct device_desc *get_device_desc(struct mp_log *l, IMMDevice *pDevice)
struct device_desc *d = talloc_zero(NULL, struct device_desc);
d->deviceID = talloc_memdup(d, deviceID,
(wcslen(deviceID) + 1) * sizeof(wchar_t));
- SAFE_RELEASE(deviceID, CoTaskMemFree(deviceID));
+ SAFE_DESTROY(deviceID, CoTaskMemFree(deviceID));
char *full_id = mp_to_utf8(NULL, d->deviceID);
bstr id = bstr0(full_id);
@@ -745,8 +742,8 @@ static void destroy_enumerator(struct enumerator *e)
{
if (!e)
return;
- SAFE_RELEASE(e->pDevices, IMMDeviceCollection_Release(e->pDevices));
- SAFE_RELEASE(e->pEnumerator, IMMDeviceEnumerator_Release(e->pEnumerator));
+ SAFE_RELEASE(e->pDevices);
+ SAFE_RELEASE(e->pEnumerator);
talloc_free(e);
}
@@ -782,7 +779,7 @@ static struct device_desc *device_desc_for_num(struct enumerator *e, UINT i)
return NULL;
}
struct device_desc *d = get_device_desc(e->log, pDevice);
- SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
+ SAFE_RELEASE(pDevice);
return d;
}
@@ -797,7 +794,7 @@ static struct device_desc *default_device_desc(struct enumerator *e)
return NULL;
}
struct device_desc *d = get_device_desc(e->log, pDevice);
- SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice));
+ SAFE_RELEASE(pDevice);
return d;
}
@@ -834,7 +831,7 @@ static HRESULT load_device(struct mp_log *l,
exit_label:
if (FAILED(hr))
mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr));
- SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
+ SAFE_RELEASE(pEnumerator);
return hr;
}
@@ -908,7 +905,7 @@ LPWSTR wasapi_find_deviceID(struct ao *ao)
BSTR_P(device), d->id, d->name);
}
}
- SAFE_RELEASE(d, talloc_free(d));
+ SAFE_DESTROY(d, talloc_free(d));
}
if (!deviceID)
@@ -969,13 +966,13 @@ void wasapi_thread_uninit(struct ao *ao)
if (state->pAudioClient)
IAudioClient_Stop(state->pAudioClient);
- SAFE_RELEASE(state->pRenderClient, IAudioRenderClient_Release(state->pRenderClient));
- SAFE_RELEASE(state->pAudioClock, IAudioClock_Release(state->pAudioClock));
- SAFE_RELEASE(state->pAudioVolume, ISimpleAudioVolume_Release(state->pAudioVolume));
- SAFE_RELEASE(state->pEndpointVolume, IAudioEndpointVolume_Release(state->pEndpointVolume));
- SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl));
- SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient));
- SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice));
- SAFE_RELEASE(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
+ SAFE_RELEASE(state->pRenderClient);
+ SAFE_RELEASE(state->pAudioClock);
+ SAFE_RELEASE(state->pAudioVolume);
+ SAFE_RELEASE(state->pEndpointVolume);
+ SAFE_RELEASE(state->pSessionControl);
+ SAFE_RELEASE(state->pAudioClient);
+ SAFE_RELEASE(state->pDevice);
+ SAFE_DESTROY(state->hTask, AvRevertMmThreadCharacteristics(state->hTask));
MP_DBG(ao, "Thread uninit done\n");
}
diff --git a/audio/out/push.c b/audio/out/push.c
index a4a6808d7f..a722d19ea2 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -251,12 +251,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
if (got_data) {
p->still_playing = true;
p->expected_end_time = 0;
- }
- // If we don't have new data, the decoder thread basically promises it
- // will send new data as soon as it's available.
- if (got_data)
+ // If we don't have new data, the decoder thread basically promises it
+ // will send new data as soon as it's available.
wakeup_playthread(ao);
+ }
pthread_mutex_unlock(&p->lock);
return write_samples;
}
diff --git a/common/av_common.c b/common/av_common.c
index f2f43498e3..5c58f3fea8 100644
--- a/common/av_common.c
+++ b/common/av_common.c
@@ -33,6 +33,7 @@
#include "common/msg.h"
#include "demux/packet.h"
#include "demux/stheader.h"
+#include "video/fmt-conversion.h"
#include "av_common.h"
#include "codecs.h"
@@ -41,7 +42,7 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
if (size) {
av_free(avctx->extradata);
avctx->extradata_size = 0;
- avctx->extradata = av_mallocz(size + FF_INPUT_BUFFER_PADDING_SIZE);
+ avctx->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!avctx->extradata)
return -1;
avctx->extradata_size = size;
@@ -50,35 +51,81 @@ int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size)
return 0;
}
-// Copy the codec-related fields from st into avctx. This does not set the
-// codec itself, only codec related header data provided by libavformat.
-// The goal is to initialize a new decoder with the header data provided by
-// libavformat, and unlike avcodec_copy_context(), allow the user to create
-// a clean AVCodecContext for a manually selected AVCodec.
-// This is strictly for decoding only.
-void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
+enum AVMediaType mp_to_av_stream_type(int type)
{
- mp_lavc_set_extradata(avctx, st->extradata, st->extradata_size);
- avctx->codec_tag = st->codec_tag;
- avctx->bit_rate = st->bit_rate;
- avctx->width = st->width;
- avctx->height = st->height;
- avctx->pix_fmt = st->pix_fmt;
- avctx->chroma_sample_location = st->chroma_sample_location;
- avctx->sample_rate = st->sample_rate;
- avctx->channels = st->channels;
- avctx->block_align = st->block_align;
- avctx->channel_layout = st->channel_layout;
- avctx->bits_per_coded_sample = st->bits_per_coded_sample;
- avctx->has_b_frames = st->has_b_frames;
+ switch (type) {
+ case STREAM_VIDEO: return AVMEDIA_TYPE_VIDEO;
+ case STREAM_AUDIO: return AVMEDIA_TYPE_AUDIO;
+ case STREAM_SUB: return AVMEDIA_TYPE_SUBTITLE;
+ default: return AVMEDIA_TYPE_UNKNOWN;
+ }
}
-// This only copies ffmpeg-native codec parameters. Parameters produced by
-// other demuxers must be handled manually.
-void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
+AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c)
{
- if (c->lav_codecpar)
- avcodec_parameters_to_context(avctx, c->lav_codecpar);
+ AVCodecParameters *avp = avcodec_parameters_alloc();
+ if (!avp)
+ return NULL;
+
+ // If we have lavf demuxer params, they overwrite by definition any others.
+ if (c->lav_codecpar) {
+ if (avcodec_parameters_copy(avp, c->lav_codecpar) < 0)
+ goto error;
+ return avp;
+ }
+
+ avp->codec_type = mp_to_av_stream_type(c->type);
+ avp->codec_id = mp_codec_to_av_codec_id(c->codec);
+ avp->codec_tag = c->codec_tag;
+ if (c->extradata_size) {
+ avp->extradata =
+ av_mallocz(c->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+ if (!avp->extradata)
+ goto error;
+ avp->extradata_size = c->extradata_size;
+ memcpy(avp->extradata, c->extradata, avp->extradata_size);
+ }
+ avp->bits_per_coded_sample = c->bits_per_coded_sample;
+
+ // Video only
+ avp->width = c->disp_w;
+ avp->height = c->disp_h;
+ if (c->codec && strcmp(c->codec, "mp-rawvideo") == 0) {
+ avp->format = imgfmt2pixfmt(c->codec_tag);
+ avp->codec_tag = 0;
+ }
+
+ // Audio only
+ avp->sample_rate = c->samplerate;
+ avp->bit_rate = c->bitrate;
+ avp->block_align = c->block_align;
+ avp->channels = c->channels.num;
+ if (!mp_chmap_is_unknown(&c->channels))
+ avp->channel_layout = mp_chmap_to_lavc(&c->channels);
+
+ return avp;
+error:
+ avcodec_parameters_free(&avp);
+ return NULL;
+}
+
+// Set avctx codec headers for decoding. Returns <0 on failure.
+int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
+{
+ enum AVMediaType codec_type = avctx->codec_type;
+ enum AVCodecID codec_id = avctx->codec_id;
+ AVCodecParameters *avp = mp_codec_params_to_av(c);
+ if (!avp)
+ return -1;
+
+ int r = avcodec_parameters_to_context(avctx, avp) < 0 ? -1 : 0;
+ avcodec_parameters_free(&avp);
+
+ if (avctx->codec_type != AVMEDIA_TYPE_UNKNOWN)
+ avctx->codec_type = codec_type;
+ if (avctx->codec_id != AV_CODEC_ID_NONE)
+ avctx->codec_id = codec_id;
+ return r;
}
// Pick a "good" timebase, which will be used to convert double timestamps
@@ -108,37 +155,24 @@ AVRational mp_get_codec_timebase(struct mp_codec_params *c)
return tb;
}
-// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
-union pts { int64_t i; double d; };
+static AVRational get_def_tb(AVRational *tb)
+{
+ return tb && tb->num > 0 && tb->den > 0 ? *tb : AV_TIME_BASE_Q;
+}
// Convert the mpv style timestamp (seconds as double) to a libavcodec style
// timestamp (integer units in a given timebase).
-//
-// If the given timebase is NULL or invalid, pass through the mpv timestamp by
-// reinterpret casting them to int64_t. In this case, the timestamps will be
-// non-sense for libavcodec, but we expect that it doesn't interpret them,
-// and treats them as opaque.
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
{
- assert(sizeof(int64_t) >= sizeof(double));
- if (tb && tb->num > 0 && tb->den > 0) {
- return mp_pts == MP_NOPTS_VALUE ?
- AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
- }
- // The + 0.0 is to squash possible negative zero mp_pts, which would
- // happen to end up as AV_NOPTS_VALUE.
- return (union pts){.d = mp_pts + 0.0}.i;
+ AVRational b = get_def_tb(tb);
+ return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(b));
}
// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.)
double mp_pts_from_av(int64_t av_pts, AVRational *tb)
{
- assert(sizeof(int64_t) >= sizeof(double));
- if (tb && tb->num > 0 && tb->den > 0)
- return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb);
- // Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as
- // non-sense (usually negative zero) when unwrapped to double.
- return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d;
+ AVRational b = get_def_tb(tb);
+ return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(b);
}
// Set dst from mpkt. Note that dst is not refcountable.
diff --git a/common/av_common.h b/common/av_common.h
index 4b13dcdd0c..1d30fab71f 100644
--- a/common/av_common.h
+++ b/common/av_common.h
@@ -31,8 +31,9 @@ struct AVDictionary;
struct mp_log;
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
-void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
-void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
+enum AVMediaType mp_to_av_stream_type(int type);
+AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c);
+int mp_set_avctx_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
diff --git a/common/av_log.c b/common/av_log.c
index e2a4c3316e..c779fe9c8d 100644
--- a/common/av_log.c
+++ b/common/av_log.c
@@ -42,10 +42,10 @@
#include <libavdevice/avdevice.h>
#endif
-#if HAVE_LIBAVRESAMPLE
+#if HAVE_IS_LIBAV
#include <libavresample/avresample.h>
#endif
-#if HAVE_LIBSWRESAMPLE
+#if HAVE_IS_FFMPEG
#include <libswresample/swresample.h>
#endif
@@ -197,10 +197,10 @@ bool print_libav_versions(struct mp_log *log, int v)
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
-#if HAVE_LIBAVRESAMPLE
+#if HAVE_IS_LIBAV
{"libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()},
#endif
-#if HAVE_LIBSWRESAMPLE
+#if HAVE_IS_FFMPEG
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
#endif
};
diff --git a/common/codecs.c b/common/codecs.c
index 5e744da5de..7985501869 100644
--- a/common/codecs.c
+++ b/common/codecs.c
@@ -33,26 +33,6 @@ void mp_add_decoder(struct mp_decoder_list *list, const char *family,
MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry);
}
-static void mp_add_decoder_entry(struct mp_decoder_list *list,
- struct mp_decoder_entry *entry)
-{
- mp_add_decoder(list, entry->family, entry->codec, entry->decoder,
- entry->desc);
-}
-
-static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
- bstr family, bstr decoder)
-{
- for (int n = 0; n < list->num_entries; n++) {
- struct mp_decoder_entry *cur = &list->entries[n];
- if (bstr_equals0(decoder, cur->decoder)) {
- if (bstr_equals0(family, "*") || bstr_equals0(family, cur->family))
- return cur;
- }
- }
- return NULL;
-}
-
// Add entry, but only if it's not yet on the list, and if the codec matches.
// If codec == NULL, don't compare codecs.
static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
@@ -60,8 +40,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
{
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
return;
- if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder)))
- mp_add_decoder_entry(to, entry);
+ mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
}
// Select a decoder from the given list for the given codec. The selection
@@ -71,10 +50,8 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
// The selection string corresponds to --vd/--ad directly, and has the
// following syntax:
// selection = [<entry> ("," <entry>)*]
-// entry = [<family> ":"] <decoder> // prefer decoder
-// entry = <family> ":*" // prefer all decoders
-// entry = "+" [<family> ":"] <decoder> // force a decoder
-// entry = "-" [<family> ":"] <decoder> // exclude a decoder
+// entry = <decoder> // prefer decoder
+// entry = "-" <decoder> // exclude a decoder
// entry = "-" // don't add fallback decoders
// Forcing a decoder means it's added even if the codec mismatches.
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
@@ -83,7 +60,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
const char *selection)
{
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
- struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
if (!codec)
codec = "unknown";
bool stop = false;
@@ -96,28 +72,15 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
stop = true;
break;
}
- bool force = bstr_eatstart0(&entry, "+");
- bool exclude = !force && bstr_eatstart0(&entry, "-");
- if (exclude || force)
- mp_warn(log, "Forcing or excluding codecs is deprecated.\n");
- struct mp_decoder_list *dest = exclude ? remove : list;
- bstr family, decoder;
- if (bstr_split_tok(entry, ":", &family, &decoder)) {
- mp_warn(log, "Codec family selection is deprecated. "
- "Pass the codec name directly.\n");
- } else {
- family = bstr0("*");
- decoder = entry;
+ if (bstr_find0(entry, ":") >= 0) {
+ mp_err(log, "Codec family selection was removed. "
+ "Pass the codec name directly.\n");
+ break;
}
- if (bstr_equals0(decoder, "*")) {
- for (int n = 0; n < all->num_entries; n++) {
- struct mp_decoder_entry *cur = &all->entries[n];
- if (bstr_equals0(family, cur->family))
- add_new(dest, cur, codec);
- }
- } else {
- add_new(dest, find_decoder(all, family, decoder),
- force ? NULL : codec);
+ for (int n = 0; n < all->num_entries; n++) {
+ struct mp_decoder_entry *cur = &all->entries[n];
+ if (bstr_equals0(entry, cur->decoder))
+ add_new(list, cur, codec);
}
}
if (!stop) {
@@ -125,16 +88,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
for (int n = 0; n < all->num_entries; n++)
add_new(list, &all->entries[n], codec);
}
- for (int n = 0; n < remove->num_entries; n++) {
- struct mp_decoder_entry *ex = &remove->entries[n];
- struct mp_decoder_entry *del =
- find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
- if (del) {
- int index = del - &list->entries[0];
- MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
- }
- }
- talloc_free(remove);
return list;
}
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index 7e116e3b0c..ee870a015a 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -473,7 +473,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
if (!(*bytebuf = stream_open(buf, ctx->global))) {
MP_WARN(ctx, "%s: could not open '%s', "
"disabling 2-pass encoding at pass 2\n", prefix, buf);
- codec->flags &= ~CODEC_FLAG_PASS2;
+ codec->flags &= ~AV_CODEC_FLAG_PASS2;
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
} else {
struct bstr content = stream_read_complete(*bytebuf, NULL,
diff --git a/common/recorder.c b/common/recorder.c
new file mode 100644
index 0000000000..d8f937b902
--- /dev/null
+++ b/common/recorder.c
@@ -0,0 +1,384 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+
+#include <libavformat/avformat.h>
+
+#include "common/av_common.h"
+#include "common/common.h"
+#include "common/global.h"
+#include "common/msg.h"
+#include "demux/packet.h"
+#include "demux/stheader.h"
+
+#include "recorder.h"
+
+#define PTS_ADD(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
+
+// Maximum number of packets we buffer at most to attempt to resync streams.
+// Essentially, this should be higher than the highest supported keyframe
+// interval.
+#define QUEUE_MAX_PACKETS 256
+// Number of packets we should buffer at least to determine timestamps (due to
+// codec delay and frame reordering, and potentially lack of DTS).
+// Keyframe flags can trigger this earlier.
+#define QUEUE_MIN_PACKETS 16
+
+struct mp_recorder {
+ struct mpv_global *global;
+ struct mp_log *log;
+
+ struct mp_recorder_sink **streams;
+ int num_streams;
+
+ bool opened; // mux context is valid
+ bool muxing; // we're currently recording (instead of preparing)
+ bool muxing_from_start; // no discontinuity at start
+ bool dts_warning;
+
+ // The start timestamp of the currently recorded segment (the timestamp of
+ // the first packet of the incoming packet stream).
+ double base_ts;
+ // The output packet timestamp corresponding to base_ts. It's the timestamp
+ // of the first packet of the current segment written to the output.
+ double rebase_ts;
+
+ AVFormatContext *mux;
+};
+
+struct mp_recorder_sink {
+ struct mp_recorder *owner;
+ struct sh_stream *sh;
+ AVStream *av_stream;
+ double max_out_pts;
+ bool discont;
+ bool proper_eof;
+ struct demux_packet **packets;
+ int num_packets;
+};
+
+static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
+{
+ enum AVMediaType av_type = mp_to_av_stream_type(sh->type);
+ if (av_type == AVMEDIA_TYPE_UNKNOWN)
+ return -1;
+
+ struct mp_recorder_sink *rst = talloc(priv, struct mp_recorder_sink);
+ *rst = (struct mp_recorder_sink) {
+ .owner = priv,
+ .sh = sh,
+ .av_stream = avformat_new_stream(priv->mux, NULL),
+ .max_out_pts = MP_NOPTS_VALUE,
+ };
+
+ if (!rst->av_stream)
+ return -1;
+
+ AVCodecParameters *avp = mp_codec_params_to_av(sh->codec);
+ if (!avp)
+ return -1;
+
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ // We don't know the delay, so make something up. If the format requires
+ // DTS, the result will probably be broken. FFmpeg provides nothing better
+ // yet (unless you demux with libavformat, which contains tons of hacks
+ // that try to determine a PTS).
+ if (!sh->codec->lav_codecpar)
+ avp->video_delay = 16;
+#endif
+
+ if (avp->codec_id == AV_CODEC_ID_NONE)
+ return -1;
+
+ if (avcodec_parameters_copy(rst->av_stream->codecpar, avp) < 0)
+ return -1;
+
+ rst->av_stream->time_base = mp_get_codec_timebase(sh->codec);
+
+ MP_TARRAY_APPEND(priv, priv->streams, priv->num_streams, rst);
+ return 0;
+}
+
+struct mp_recorder *mp_recorder_create(struct mpv_global *global,
+ const char *target_file,
+ struct sh_stream **streams,
+ int num_streams)
+{
+ struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder);
+
+ priv->global = global;
+ priv->log = mp_log_new(priv, global->log, "recorder");
+
+ if (!num_streams) {
+ MP_ERR(priv, "No streams.\n");
+ goto error;
+ }
+
+ priv->mux = avformat_alloc_context();
+ if (!priv->mux)
+ goto error;
+
+ priv->mux->oformat = av_guess_format(NULL, target_file, NULL);
+ if (!priv->mux->oformat) {
+ MP_ERR(priv, "Output format not found.\n");
+ goto error;
+ }
+
+ if (avio_open2(&priv->mux->pb, target_file, AVIO_FLAG_WRITE, NULL, NULL) < 0) {
+ MP_ERR(priv, "Failed opening output file.\n");
+ goto error;
+ }
+
+ for (int n = 0; n < num_streams; n++) {
+ if (add_stream(priv, streams[n]) < 0) {
+ MP_ERR(priv, "Can't mux one of the input streams.\n");
+ goto error;
+ }
+ }
+
+ // Not sure how to write this in a "standard" way. It appears only mkv
+ // and mp4 support this directly.
+ char version[200];
+ snprintf(version, sizeof(version), "%s experimental stream recording "
+ "feature (can generate broken files - please report bugs)",
+ mpv_version);
+ av_dict_set(&priv->mux->metadata, "encoding_tool", version, 0);
+
+ if (avformat_write_header(priv->mux, NULL) < 0) {
+ MP_ERR(priv, "Write header failed.\n");
+ goto error;
+ }
+
+ priv->opened = true;
+ priv->muxing_from_start = true;
+
+ priv->base_ts = MP_NOPTS_VALUE;
+ priv->rebase_ts = 0;
+
+ MP_WARN(priv, "This is an experimental feature. Output files might be "
+ "broken or not play correctly with various players "
+ "(including mpv itself).\n");
+
+ return priv;
+
+error:
+ mp_recorder_destroy(priv);
+ return NULL;
+}
+
+static void flush_packets(struct mp_recorder *priv)
+{
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ for (int i = 0; i < rst->num_packets; i++)
+ talloc_free(rst->packets[i]);
+ rst->num_packets = 0;
+ }
+}
+
+static void mux_packet(struct mp_recorder_sink *rst,
+ struct demux_packet *pkt)
+{
+ struct mp_recorder *priv = rst->owner;
+ struct demux_packet mpkt = *pkt;
+
+ double diff = priv->rebase_ts - priv->base_ts;
+ mpkt.pts = PTS_ADD(mpkt.pts, diff);
+ mpkt.dts = PTS_ADD(mpkt.dts, diff);
+
+ rst->max_out_pts = MPMAX(rst->max_out_pts, pkt->pts);
+
+ AVPacket avpkt;
+ mp_set_av_packet(&avpkt, &mpkt, &rst->av_stream->time_base);
+
+ avpkt.stream_index = rst->av_stream->index;
+
+ if (avpkt.duration < 0 && rst->sh->type != STREAM_SUB)
+ avpkt.duration = 0;
+
+ AVPacket *new_packet = av_packet_clone(&avpkt);
+ if (!new_packet) {
+ MP_ERR(priv, "Failed to allocate packet.\n");
+ return;
+ }
+
+ if (av_interleaved_write_frame(priv->mux, new_packet) < 0)
+ MP_ERR(priv, "Failed writing packet.\n");
+}
+
+// Write all packets that currently can be written.
+static void mux_packets(struct mp_recorder_sink *rst, bool force)
+{
+ struct mp_recorder *priv = rst->owner;
+ if (!priv->muxing || !rst->num_packets)
+ return;
+
+ int safe_count = 0;
+ for (int n = 0; n < rst->num_packets; n++) {
+ if (rst->packets[n]->keyframe)
+ safe_count = n;
+ }
+ if (force)
+ safe_count = rst->num_packets;
+
+ for (int n = 0; n < safe_count; n++) {
+ mux_packet(rst, rst->packets[n]);
+ talloc_free(rst->packets[n]);
+ }
+
+ // Remove packets[0..safe_count]
+ memmove(&rst->packets[0], &rst->packets[safe_count],
+ (rst->num_packets - safe_count) * sizeof(rst->packets[0]));
+ rst->num_packets -= safe_count;
+}
+
+// If there was a discontinuity, check whether we can resume muxing (and from
+// where).
+static void check_restart(struct mp_recorder *priv)
+{
+ if (priv->muxing)
+ return;
+
+ double min_ts = INFINITY;
+ double rebase_ts = 0;
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ int min_packets = rst->sh->type == STREAM_VIDEO ? QUEUE_MIN_PACKETS : 1;
+
+ rebase_ts = MPMAX(rebase_ts, rst->max_out_pts);
+
+ if (rst->num_packets < min_packets) {
+ if (!rst->proper_eof && rst->sh->type != STREAM_SUB)
+ return;
+ continue;
+ }
+
+ for (int i = 0; i < min_packets; i++)
+ min_ts = MPMIN(min_ts, rst->packets[i]->pts);
+ }
+
+ // Subtitle only stream (wait longer) or stream without any PTS (fuck it).
+ if (!isfinite(min_ts))
+ return;
+
+ priv->rebase_ts = rebase_ts;
+ priv->base_ts = min_ts;
+
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ rst->max_out_pts = min_ts;
+ }
+
+ priv->muxing = true;
+
+ if (!priv->muxing_from_start)
+ MP_WARN(priv, "Discontinuity at timestamp %f.\n", priv->rebase_ts);
+}
+
+void mp_recorder_destroy(struct mp_recorder *priv)
+{
+ if (priv->opened) {
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ if (!rst->proper_eof)
+ continue;
+ mux_packets(rst, true);
+ }
+
+ if (av_write_trailer(priv->mux) < 0)
+ MP_ERR(priv, "Writing trailer failed.\n");
+ }
+
+ if (priv->mux) {
+ if (avio_closep(&priv->mux->pb) < 0)
+ MP_ERR(priv, "Closing file failed\n");
+
+ avformat_free_context(priv->mux);
+ }
+
+ flush_packets(priv);
+ talloc_free(priv);
+}
+
+// This is called on a seek, or when recording was started mid-stream.
+void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
+{
+ flush_packets(priv);
+
+ for (int n = 0; n < priv->num_streams; n++) {
+ struct mp_recorder_sink *rst = priv->streams[n];
+ rst->discont = true;
+ rst->proper_eof = false;
+ }
+
+ priv->muxing = false;
+ priv->muxing_from_start = false;
+}
+
+// Get a stream for writing. The pointer is valid until mp_recorder is
+// destroyed. The stream is the index referencing the stream passed to
+// mp_recorder_create().
+struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream)
+{
+ assert(stream >= 0 && stream < r->num_streams);
+ return r->streams[stream];
+}
+
+// Pass a packet to the given stream. The function does not own the packet, but
+// can create a new reference to it if it needs to retain it. Can be NULL to
+// signal proper end of stream.
+void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
+ struct demux_packet *pkt)
+{
+ struct mp_recorder *priv = rst->owner;
+
+ if (!pkt) {
+ rst->proper_eof = true;
+ check_restart(priv);
+ mux_packets(rst, false);
+ return;
+ }
+
+ if (pkt->dts == MP_NOPTS_VALUE && !priv->dts_warning) {
+ // No, FFmpeg has no actually usable helpers to generate correct DTS.
+ // No, FFmpeg doesn't tell us which formats need DTS at all.
+ // No, we can not shut up the FFmpeg warning, which will follow.
+ MP_WARN(priv, "Source stream misses DTS on at least some packets!\n"
+ "If the target file format requires DTS, the written\n"
+ "file will be invalid.\n");
+ priv->dts_warning = true;
+ }
+
+ if (rst->discont && !pkt->keyframe)
+ return;
+ rst->discont = false;
+
+ if (rst->num_packets >= QUEUE_MAX_PACKETS) {
+ MP_ERR(priv, "Stream %d has too many queued packets; dropping.\n",
+ rst->av_stream->index);
+ return;
+ }
+
+ pkt = demux_copy_packet(pkt);
+ if (!pkt)
+ return;
+ MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt);
+
+ check_restart(priv);
+ mux_packets(rst, false);
+}
diff --git a/common/recorder.h b/common/recorder.h
new file mode 100644
index 0000000000..a6c8635c01
--- /dev/null
+++ b/common/recorder.h
@@ -0,0 +1,21 @@
+#ifndef MP_RECORDER_H_
+#define MP_RECORDER_H_
+
+struct mp_recorder;
+struct mpv_global;
+struct demux_packet;
+struct sh_stream;
+struct mp_recorder_sink;
+
+struct mp_recorder *mp_recorder_create(struct mpv_global *global,
+ const char *target_file,
+ struct sh_stream **streams,
+ int num_streams);
+void mp_recorder_destroy(struct mp_recorder *r);
+void mp_recorder_mark_discontinuity(struct mp_recorder *r);
+
+struct mp_recorder_sink *mp_recorder_get_sink(struct mp_recorder *r, int stream);
+void mp_recorder_feed_packet(struct mp_recorder_sink *s,
+ struct demux_packet *pkt);
+
+#endif
diff --git a/demux/cue.c b/demux/cue.c
index 69f30f4871..9dbde71062 100644
--- a/demux/cue.c
+++ b/demux/cue.c
@@ -70,7 +70,7 @@ static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
return CUE_EMPTY;
for (int n = 0; cue_command_strings[n].command != -1; n++) {
struct bstr name = bstr0(cue_command_strings[n].text);
- if (bstr_startswith(line, name)) {
+ if (bstr_case_startswith(line, name)) {
struct bstr rest = bstr_cut(line, name.len);
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
continue;
diff --git a/demux/demux.c b/demux/demux.c
index 18c9b3b5c1..0c45083e2d 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -205,6 +205,9 @@ struct demux_stream {
struct demux_packet *head;
struct demux_packet *tail;
+ struct demux_packet *attached_picture;
+ bool attached_picture_added;
+
// for closed captions (demuxer_feed_caption)
struct sh_stream *cc;
};
@@ -243,6 +246,7 @@ static void ds_flush(struct demux_stream *ds)
ds->last_pos = -1;
ds->last_dts = MP_NOPTS_VALUE;
ds->correct_dts = ds->correct_pos = true;
+ ds->attached_picture_added = false;
}
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
@@ -291,6 +295,8 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
if (!sh->codec->codec)
sh->codec->codec = "";
+ sh->ds->attached_picture = sh->attached_picture;
+
sh->index = in->num_streams;
if (sh->ff_index < 0)
sh->ff_index = sh->index;
@@ -655,7 +661,9 @@ static bool read_packet(struct demux_internal *in)
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
}
- bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
+ bool eof = true;
+ if (demux->desc->fill_buffer && !demux_cancel_test(demux))
+ eof = demux->desc->fill_buffer(demux) <= 0;
update_cache(in);
pthread_mutex_lock(&in->lock);
@@ -677,28 +685,6 @@ static bool read_packet(struct demux_internal *in)
return true;
}
-// must be called locked; may temporarily unlock
-static void ds_get_packets(struct demux_stream *ds)
-{
- const char *t = stream_type_name(ds->type);
- struct demux_internal *in = ds->in;
- MP_DBG(in, "reading packet for %s\n", t);
- in->eof = false; // force retry
- while (ds->selected && !ds->head) {
- ds->active = true;
- // Note: the following code marks EOF if it can't continue
- if (in->threading) {
- MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
- pthread_cond_signal(&in->wakeup);
- pthread_cond_wait(&in->wakeup, &in->lock);
- } else {
- read_packet(in);
- }
- if (ds->eof)
- break;
- }
-}
-
static void execute_trackswitch(struct demux_internal *in)
{
in->tracks_switched = false;
@@ -777,6 +763,13 @@ static void *demux_thread(void *pctx)
static struct demux_packet *dequeue_packet(struct demux_stream *ds)
{
+ if (ds->attached_picture) {
+ ds->eof = true;
+ if (ds->attached_picture_added)
+ return NULL;
+ ds->attached_picture_added = true;
+ return demux_copy_packet(ds->attached_picture);
+ }
if (!ds->head)
return NULL;
struct demux_packet *pkt = ds->head;
@@ -820,16 +813,23 @@ static struct demux_packet *dequeue_packet(struct demux_stream *ds)
return pkt;
}
+// Whether to avoid actively demuxing new packets to find a new packet on the
+// given stream.
+// Attached pictures (cover art) should never actively read.
// Sparse packets (Subtitles) interleaved with other non-sparse packets (video,
// audio) should never be read actively, meaning the demuxer thread does not
// try to exceed default readahead in order to find a new packet.
-static bool use_lazy_subtitle_reading(struct demux_stream *ds)
+static bool use_lazy_packet_reading(struct demux_stream *ds)
{
+ if (ds->attached_picture)
+ return true;
if (ds->type != STREAM_SUB)
return false;
+ // Subtitles are only lazily read if there's at least 1 other actively read
+ // stream.
for (int n = 0; n < ds->in->num_streams; n++) {
struct demux_stream *s = ds->in->streams[n]->ds;
- if (s->type != STREAM_SUB && s->selected && !s->eof)
+ if (s->type != STREAM_SUB && s->selected && !s->eof && !s->attached_picture)
return true;
}
return false;
@@ -843,12 +843,29 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh)
struct demux_stream *ds = sh ? sh->ds : NULL;
struct demux_packet *pkt = NULL;
if (ds) {
- pthread_mutex_lock(&ds->in->lock);
- if (!use_lazy_subtitle_reading(ds))
- ds_get_packets(ds);
+ struct demux_internal *in = ds->in;
+ pthread_mutex_lock(&in->lock);
+ if (!use_lazy_packet_reading(ds)) {
+ const char *t = stream_type_name(ds->type);
+ MP_DBG(in, "reading packet for %s\n", t);
+ in->eof = false; // force retry
+ while (ds->selected && !ds->head) {
+ ds->active = true;
+ // Note: the following code marks EOF if it can't continue
+ if (in->threading) {
+ MP_VERBOSE(in, "waiting for demux thread (%s)\n", t);
+ pthread_cond_signal(&in->wakeup);
+ pthread_cond_wait(&in->wakeup, &in->lock);
+ } else {
+ read_packet(in);
+ }
+ if (ds->eof)
+ break;
+ }
+ }
pkt = dequeue_packet(ds);
- pthread_cond_signal(&ds->in->wakeup); // possibly read more
- pthread_mutex_unlock(&ds->in->lock);
+ pthread_cond_signal(&in->wakeup); // possibly read more
+ pthread_mutex_unlock(&in->lock);
}
return pkt;
}
@@ -874,7 +891,7 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
if (ds->in->threading) {
pthread_mutex_lock(&ds->in->lock);
*out_pkt = dequeue_packet(ds);
- if (use_lazy_subtitle_reading(ds)) {
+ if (use_lazy_packet_reading(ds)) {
r = *out_pkt ? 1 : -1;
} else {
r = *out_pkt ? 1 : ((ds->eof || !ds->selected) ? -1 : 0);
@@ -1231,8 +1248,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
.events = DEMUX_EVENT_ALL,
};
demuxer->seekable = stream->seekable;
- if (demuxer->stream->uncached_stream &&
- !demuxer->stream->uncached_stream->seekable)
+ if (demuxer->stream->underlying && !demuxer->stream->underlying->seekable)
demuxer->seekable = false;
struct demux_internal *in = demuxer->in = talloc_ptrtype(demuxer, in);
@@ -1249,9 +1265,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
pthread_mutex_init(&in->lock, NULL);
pthread_cond_init(&in->wakeup, NULL);
- if (stream->uncached_stream)
- in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
-
*in->d_thread = *demuxer;
*in->d_buffer = *demuxer;
@@ -1290,7 +1303,8 @@ static struct demuxer *open_given_type(struct mpv_global *global,
demux_init_cache(demuxer);
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
demux_update(demuxer);
- stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD, &(int){false});
+ stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD,
+ &(int){params ? params->initial_readahead : false});
if (!(params && params->disable_timeline)) {
struct timeline *tl = timeline_load(global, log, demuxer);
if (tl) {
@@ -1299,11 +1313,15 @@ static struct demuxer *open_given_type(struct mpv_global *global,
struct demuxer *sub =
open_given_type(global, log, &demuxer_desc_timeline, stream,
&params2, DEMUX_CHECK_FORCE);
- if (sub)
- return sub;
- timeline_destroy(tl);
+ if (sub) {
+ demuxer = sub;
+ } else {
+ timeline_destroy(tl);
+ }
}
}
+ if (demuxer->is_network || stream->caching)
+ in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
return demuxer;
}
@@ -1382,12 +1400,6 @@ struct demuxer *demux_open_url(const char *url,
cancel, global);
if (!s)
return NULL;
- if (params->allow_capture) {
- char *f;
- mp_read_option_raw(global, "stream-capture", &m_option_type_string, &f);
- stream_set_capture_file(s, f);
- talloc_free(f);
- }
if (!params->disable_cache)
stream_enable_cache_defaults(&s);
struct demuxer *d = demux_open(s, params, global);
@@ -1724,6 +1736,7 @@ static void thread_demux_control(void *p)
int demux_control(demuxer_t *demuxer, int cmd, void *arg)
{
struct demux_internal *in = demuxer->in;
+ assert(demuxer == in->d_user);
if (in->threading) {
pthread_mutex_lock(&in->lock);
@@ -1735,37 +1748,29 @@ int demux_control(demuxer_t *demuxer, int cmd, void *arg)
int r = 0;
struct demux_control_args args = {demuxer, cmd, arg, &r};
- demux_run_on_thread(demuxer, thread_demux_control, &args);
-
- return r;
-}
-
-int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg)
-{
- struct demux_ctrl_stream_ctrl c = {ctrl, arg, STREAM_UNSUPPORTED};
- demux_control(demuxer, DEMUXER_CTRL_STREAM_CTRL, &c);
- return c.res;
-}
-
-void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx)
-{
- struct demux_internal *in = demuxer->in;
- assert(demuxer == in->d_user);
-
if (in->threading) {
MP_VERBOSE(in, "blocking on demuxer thread\n");
pthread_mutex_lock(&in->lock);
while (in->run_fn)
pthread_cond_wait(&in->wakeup, &in->lock);
- in->run_fn = fn;
- in->run_fn_arg = ctx;
+ in->run_fn = thread_demux_control;
+ in->run_fn_arg = &args;
pthread_cond_signal(&in->wakeup);
while (in->run_fn)
pthread_cond_wait(&in->wakeup, &in->lock);
pthread_mutex_unlock(&in->lock);
} else {
- fn(ctx);
+ thread_demux_control(&args);
}
+
+ return r;
+}
+
+int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg)
+{
+ struct demux_ctrl_stream_ctrl c = {ctrl, arg, STREAM_UNSUPPORTED};
+ demux_control(demuxer, DEMUXER_CTRL_STREAM_CTRL, &c);
+ return c.res;
}
bool demux_cancel_test(struct demuxer *demuxer)
diff --git a/demux/demux.h b/demux/demux.h
index 0e5a5e15c6..8a1da4e400 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -162,9 +162,11 @@ struct demuxer_params {
bool *matroska_was_valid;
struct timeline *timeline;
bool disable_timeline;
+ bool initial_readahead;
+ bstr init_fragment;
+ bool skip_lavf_probing;
// -- demux_open_url() only
int stream_flags;
- bool allow_capture;
bool disable_cache;
// result
bool demuxer_failed;
@@ -287,8 +289,6 @@ double demuxer_get_time_length(struct demuxer *demuxer);
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
-void demux_run_on_thread(struct demuxer *demuxer, void (*fn)(void *), void *ctx);
-
void demux_changed(demuxer_t *demuxer, int events);
void demux_update(demuxer_t *demuxer);
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
index 805ba4ccff..ad54db8c19 100644
--- a/demux/demux_disc.c
+++ b/demux/demux_disc.c
@@ -43,6 +43,8 @@ struct priv {
double base_dts; // packet DTS that maps to base_time
double last_dts; // DTS of previously demuxed packet
bool seek_reinit; // needs reinit after seek
+
+ bool is_dvd, is_cdda;
};
// If the timestamp difference between subsequent packets is this big, assume
@@ -65,7 +67,7 @@ static void reselect_streams(demuxer_t *demuxer)
static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
{
struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
- if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB)
+ if (sh->type == STREAM_SUB)
req.id = req.id & 0x1F; // mpeg ID to index
stream_control(stream, STREAM_CTRL_GET_LANG, &req);
if (req.name[0])
@@ -76,7 +78,7 @@ static void add_dvd_streams(demuxer_t *demuxer)
{
struct priv *p = demuxer->priv;
struct stream *stream = demuxer->stream;
- if (stream->uncached_type != STREAMTYPE_DVD)
+ if (!p->is_dvd)
return;
struct stream_dvd_info_req info;
if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
@@ -162,7 +164,7 @@ static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
{
struct priv *p = demuxer->priv;
- if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
+ if (p->is_cdda) {
demux_seek(p->slave, seek_pts, flags);
return;
}
@@ -222,7 +224,7 @@ static int d_fill_buffer(demuxer_t *demuxer)
return 1;
}
- if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
+ if (p->is_cdda) {
demux_add_packet(sh, pkt);
return 1;
}
@@ -285,7 +287,21 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
struct demuxer_params params = {.force_format = "+lavf"};
- if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
+ struct stream *cur = demuxer->stream;
+ const char *sname = "";
+ while (cur) {
+ if (cur->info)
+ sname = cur->info->name;
+ cur = cur->underlying; // down the caching chain
+ }
+
+ p->is_cdda = strcmp(sname, "cdda") == 0;
+ p->is_dvd = strcmp(sname, "dvd") == 0 ||
+ strcmp(sname, "ifo") == 0 ||
+ strcmp(sname, "dvdnav") == 0 ||
+ strcmp(sname, "ifo_dvdnav") == 0;
+
+ if (p->is_cdda)
params.force_format = "+rawaudio";
char *t = NULL;
@@ -311,7 +327,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
// (actually libavformat/mpegts.c) to seek sometimes when reading a packet.
// It does this to seek back a bit in case the current file position points
// into the middle of a packet.
- if (demuxer->stream->uncached_type != STREAMTYPE_CDDA) {
+ if (!p->is_cdda) {
demuxer->stream->seekable = false;
// Can be seekable even if the stream isn't.
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index 623cae35b3..8b6f402b27 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -44,12 +44,15 @@ struct tl_part {
};
struct tl_parts {
+ bool dash;
+ char *init_fragment_url;
struct tl_part *parts;
int num_parts;
};
struct priv {
bstr data;
+ bool allow_any;
};
// Parse a time (absolute file time or duration). Currently equivalent to a
@@ -64,6 +67,8 @@ static bool parse_time(bstr str, double *out_time)
return true;
}
+#define MAX_PARAMS 10
+
/* Returns a list of parts, or NULL on parse error.
* Syntax (without file header or URI prefix):
* url ::= <entry> ( (';' | '\n') <entry> )*
@@ -78,7 +83,10 @@ static struct tl_parts *parse_edl(bstr str)
bstr_split_tok(str, "\n", &(bstr){0}, &str);
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
continue;
+ bool is_header = bstr_eatstart0(&str, "!");
struct tl_part p = { .length = -1 };
+ bstr param_names[MAX_PARAMS];
+ bstr param_vals[MAX_PARAMS];
int nparam = 0;
while (1) {
bstr name, val;
@@ -116,10 +124,25 @@ static struct tl_parts *parse_edl(bstr str)
if (bstr_equals0(val, "chapters"))
p.chapter_ts = true;
}
+ if (nparam >= MAX_PARAMS)
+ goto error;
+ param_names[nparam] = name;
+ param_vals[nparam] = val;
nparam++;
if (!bstr_eatstart0(&str, ","))
break;
}
+ if (is_header) {
+ if (tl->num_parts)
+ goto error; // can't have header once an entry was defined
+ bstr type = param_vals[0]; // value, because no "="
+ if (bstr_equals0(type, "mp4_dash")) {
+ tl->dash = true;
+ if (bstr_equals0(param_names[1], "init"))
+ tl->init_fragment_url = bstrto0(tl, param_vals[1]);
+ }
+ continue;
+ }
if (!p.filename)
goto error;
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
@@ -139,7 +162,10 @@ static struct demuxer *open_source(struct timeline *tl, char *filename)
if (strcmp(d->stream->url, filename) == 0)
return d;
}
- struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
+ struct demuxer_params params = {
+ .init_fragment = tl->init_fragment,
+ };
+ struct demuxer *d = demux_open_url(filename, &params, tl->cancel, tl->global);
if (d) {
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
} else {
@@ -202,61 +228,110 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
{
+ tl->track_layout = NULL;
+ tl->dash = parts->dash;
+
+ if (parts->init_fragment_url && parts->init_fragment_url[0]) {
+ MP_VERBOSE(tl, "Opening init fragment...\n");
+ stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
+ tl->cancel, tl->global);
+ if (s)
+ tl->init_fragment = stream_read_complete(s, tl, 1000000);
+ free_stream(s);
+ if (!tl->init_fragment.len) {
+ MP_ERR(tl, "Could not read init fragment.\n");
+ goto error;
+ }
+ s = open_memory_stream(tl->init_fragment.start, tl->init_fragment.len);
+ tl->track_layout = demux_open(s, NULL, tl->global);
+ if (!tl->track_layout) {
+ free_stream(s);
+ MP_ERR(tl, "Could not demux init fragment.\n");
+ goto error;
+ }
+ }
+
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1);
double starttime = 0;
for (int n = 0; n < parts->num_parts; n++) {
struct tl_part *part = &parts->parts[n];
- struct demuxer *source = open_source(tl, part->filename);
- if (!source)
- goto error;
-
- resolve_timestamps(part, source);
+ struct demuxer *source = NULL;
- double end_time = source_get_length(source);
- if (end_time >= 0)
- end_time += source->start_time;
+ if (tl->dash) {
+ part->offset = starttime;
+ if (part->length <= 0)
+ MP_WARN(tl, "Segment %d has unknown duration.\n", n);
+ if (part->offset_set)
+ MP_WARN(tl, "Offsets are ignored.\n");
+ tl->demuxer->is_network = true;
- // Unknown length => use rest of the file. If duration is unknown, make
- // something up.
- if (part->length < 0) {
- if (end_time < 0) {
- MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
- part->filename);
- end_time = 1;
+ if (!tl->track_layout) {
+ source = open_source(tl, part->filename);
+ if (!source)
+ goto error;
}
- part->length = end_time - part->offset;
- } else if (end_time >= 0) {
- double end_part = part->offset + part->length;
- if (end_part > end_time) {
- MP_WARN(tl, "EDL: entry %d uses %f "
- "seconds, but file has only %f seconds.\n",
- n, end_part, end_time);
+ } else {
+ MP_VERBOSE(tl, "Opening segment %d...\n", n);
+
+ source = open_source(tl, part->filename);
+ if (!source)
+ goto error;
+
+ resolve_timestamps(part, source);
+
+ double end_time = source_get_length(source);
+ if (end_time >= 0)
+ end_time += source->start_time;
+
+ // Unknown length => use rest of the file. If duration is unknown, make
+ // something up.
+ if (part->length < 0) {
+ if (end_time < 0) {
+ MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
+ part->filename);
+ end_time = 1;
+ }
+ part->length = end_time - part->offset;
+ } else if (end_time >= 0) {
+ double end_part = part->offset + part->length;
+ if (end_part > end_time) {
+ MP_WARN(tl, "EDL: entry %d uses %f "
+ "seconds, but file has only %f seconds.\n",
+ n, end_part, end_time);
+ }
}
- }
- // Add a chapter between each file.
- struct demux_chapter ch = {
- .pts = starttime,
- .metadata = talloc_zero(tl, struct mp_tags),
- };
- mp_tags_set_str(ch.metadata, "title", part->filename);
- MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
+ // Add a chapter between each file.
+ struct demux_chapter ch = {
+ .pts = starttime,
+ .metadata = talloc_zero(tl, struct mp_tags),
+ };
+ mp_tags_set_str(ch.metadata, "title", part->filename);
+ MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
- // Also copy the source file's chapters for the relevant parts
- copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
- part->length, starttime);
+ // Also copy the source file's chapters for the relevant parts
+ copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
+ part->length, starttime);
+ }
tl->parts[n] = (struct timeline_part) {
.start = starttime,
.source_start = part->offset,
.source = source,
+ .url = talloc_strdup(tl, part->filename),
};
starttime += part->length;
+
+ if (source) {
+ tl->demuxer->is_network |= source->is_network;
+
+ if (!tl->track_layout)
+ tl->track_layout = source;
+ }
}
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
tl->num_parts = parts->num_parts;
- tl->track_layout = tl->parts[0].source;
return;
error:
@@ -286,8 +361,7 @@ static void build_mpv_edl_timeline(struct timeline *tl)
return;
}
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
- // Source is .edl and not edl:// => don't allow arbitrary paths
- if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL)
+ if (!p->allow_any)
fix_filenames(parts, tl->demuxer->filename);
build_timeline(tl, parts);
talloc_free(parts);
@@ -303,8 +377,10 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
demuxer->fully_read = true;
struct stream *s = demuxer->stream;
- if (s->uncached_type == STREAMTYPE_EDL) {
+ if (s->info && strcmp(s->info->name, "edl") == 0) {
p->data = bstr0(s->path);
+ // Source is edl:// and not .edl => allow arbitrary paths
+ p->allow_any = true;
return 0;
}
if (check >= DEMUX_CHECK_UNSAFE) {
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index 124956378c..46a2f558af 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -183,6 +183,8 @@ typedef struct lavf_priv {
AVInputFormat *avif;
int avif_flags;
AVFormatContext *avfc;
+ bstr init_fragment;
+ int64_t stream_pos;
AVIOContext *pb;
struct sh_stream **streams; // NULL for unknown streams
int num_streams;
@@ -218,7 +220,14 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
struct stream *stream = priv->stream;
int ret;
- ret = stream_read(stream, buf, size);
+ if (priv->stream_pos < priv->init_fragment.len) {
+ ret = MPMIN(size, priv->init_fragment.len - priv->stream_pos);
+ memcpy(buf, priv->init_fragment.start + priv->stream_pos, ret);
+ priv->stream_pos += ret;
+ } else {
+ ret = stream_read(stream, buf, size);
+ priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
+ }
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
ret, stream, buf, size, stream_tell(stream), stream->eof);
@@ -230,32 +239,44 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
struct demuxer *demuxer = opaque;
lavf_priv_t *priv = demuxer->priv;
struct stream *stream = priv->stream;
- int64_t current_pos;
+
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
whence == SEEK_END ? "end" :
whence == SEEK_CUR ? "cur" :
whence == SEEK_SET ? "set" : "size");
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
- int64_t end = stream_get_size(stream);
+ int64_t end = stream_get_size(stream) + priv->init_fragment.len;
if (end < 0)
return -1;
if (whence == AVSEEK_SIZE)
return end;
pos += end;
} else if (whence == SEEK_CUR) {
- pos += stream_tell(stream);
+ pos += priv->stream_pos;
} else if (whence != SEEK_SET) {
return -1;
}
if (pos < 0)
return -1;
- current_pos = stream_tell(stream);
- if (stream_seek(stream, pos) == 0) {
+
+ int64_t stream_target = pos - priv->init_fragment.len;
+ bool seek_before = stream_target < 0;
+ if (seek_before)
+ stream_target = 0; // within init segment - seek real stream to 0
+
+ int64_t current_pos = stream_tell(stream);
+ if (stream_seek(stream, stream_target) == 0) {
stream_seek(stream, current_pos);
return -1;
}
+ if (seek_before) {
+ priv->stream_pos = pos;
+ } else {
+ priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
+ }
+
return pos;
}
@@ -338,7 +359,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
priv->filename = remove_prefix(s->url, prefixes);
char *avdevice_format = NULL;
- if (s->uncached_type == STREAMTYPE_AVDEVICE) {
+ if (s->info && strcmp(s->info->name, "avdevice") == 0) {
// always require filename in the form "format:filename"
char *sep = strchr(priv->filename, ':');
if (!sep) {
@@ -376,7 +397,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
// Disable file-extension matching with normal checks
.filename = check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
.buf_size = 0,
- .buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
+ .buf = av_mallocz(PROBE_BUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE),
};
if (!avpd.buf)
return -1;
@@ -771,6 +792,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
if (lavf_check_file(demuxer, check) < 0)
return -1;
+ if (demuxer->params)
+ priv->init_fragment = bstrdup(priv, demuxer->params->init_fragment);
+
avfc = avformat_alloc_context();
if (!avfc)
return -1;
@@ -808,10 +832,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
AVDictionary *dopts = NULL;
- if ((priv->avif_flags & AVFMT_NOFILE) ||
- priv->stream->type == STREAMTYPE_AVDEVICE ||
- priv->format_hack.no_stream)
- {
+ if ((priv->avif_flags & AVFMT_NOFILE) || priv->format_hack.no_stream) {
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log);
// This might be incorrect.
demuxer->seekable = true;
@@ -867,9 +888,12 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
av_dict_free(&dopts);
priv->avfc = avfc;
- if (avformat_find_stream_info(avfc, NULL) < 0) {
- MP_ERR(demuxer, "av_find_stream_info() failed\n");
- return -1;
+
+ if (!demuxer->params || !demuxer->params->skip_lavf_probing) {
+ if (avformat_find_stream_info(avfc, NULL) < 0) {
+ MP_ERR(demuxer, "av_find_stream_info() failed\n");
+ return -1;
+ }
}
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
diff --git a/demux/demux_mf.c b/demux/demux_mf.c
index 4abd394317..50afd355b7 100644
--- a/demux/demux_mf.c
+++ b/demux/demux_mf.c
@@ -294,9 +294,10 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
mf_t *mf;
if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
- demuxer->stream->type == STREAMTYPE_MF)
+ demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
+ {
mf = open_mf_pattern(demuxer, demuxer->log, demuxer->stream->url + 5);
- else {
+ } else {
mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
int bog = 0;
MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream);
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index ddda2ecb61..d564999fc6 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -162,6 +162,7 @@ struct block_info {
bstr data;
void *alloc;
int64_t filepos;
+ struct ebml_block_additions *additions;
};
typedef struct mkv_demuxer {
@@ -2392,6 +2393,8 @@ static void free_block(struct block_info *block)
free(block->alloc);
block->alloc = NULL;
block->data = (bstr){0};
+ talloc_free(block->additions);
+ block->additions = NULL;
}
static void index_block(demuxer_t *demuxer, struct block_info *block)
@@ -2552,6 +2555,15 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
block_info->discardpadding / 1e9 * srate);
mkv_d->a_skip_preroll = 0;
}
+ if (block_info->additions) {
+ for (int n = 0; n < block_info->additions->n_block_more; n++) {
+ struct ebml_block_more *add =
+ &block_info->additions->block_more[n];
+ int64_t id = add->n_block_add_id ? add->block_add_id : 1;
+ demux_packet_add_blockadditional(dp, id,
+ add->block_additional.start, add->block_additional.len);
+ }
+ }
mkv_parse_and_add_packet(demuxer, track, dp);
talloc_free_children(track->parser_tmp);
@@ -2604,8 +2616,22 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
int64_t num = ebml_read_int(s);
if (num == EBML_INT_INVALID)
goto error;
- if (num)
- block->keyframe = false;
+ block->keyframe = false;
+ break;
+
+ case MATROSKA_ID_BLOCKADDITIONS:;
+ struct ebml_block_additions additions = {0};
+ struct ebml_parse_ctx parse_ctx = {demuxer->log};
+ if (ebml_read_element(s, &parse_ctx, &additions,
+ &ebml_block_additions_desc) < 0)
+ return -1;
+ if (additions.n_block_more > 0) {
+ block->additions =
+ talloc_memdup(NULL, &additions, sizeof(additions));
+ talloc_steal(block->additions, parse_ctx.talloc_ctx);
+ parse_ctx.talloc_ctx = NULL;
+ }
+ talloc_free(parse_ctx.talloc_ctx);
break;
case MATROSKA_ID_CLUSTER:
diff --git a/demux/demux_mkv_timeline.c b/demux/demux_mkv_timeline.c
index 476551c58b..de7b2e0853 100644
--- a/demux/demux_mkv_timeline.c
+++ b/demux/demux_mkv_timeline.c
@@ -267,7 +267,7 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
talloc_steal(tmp, pl);
for (struct playlist_entry *e = pl ? pl->first : NULL; e; e = e->next)
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
- } else if (ctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
+ } else if (!ctx->demuxer->stream->is_local_file) {
MP_WARN(ctx, "Playback source is not a "
"normal disk file. Will not search for related files.\n");
} else {
diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c
index 0f2ebedd76..d79edfca40 100644
--- a/demux/demux_playlist.c
+++ b/demux/demux_playlist.c
@@ -277,7 +277,7 @@ static int cmp_filename(const void *a, const void *b)
static int parse_dir(struct pl_parser *p)
{
- if (p->real_stream->type != STREAMTYPE_DIR)
+ if (!p->real_stream->is_directory)
return -1;
if (p->probing)
return 0;
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index 8784dcf8f6..3759c9ffc1 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -24,11 +24,14 @@
#include "demux.h"
#include "timeline.h"
#include "stheader.h"
+#include "stream/stream.h"
struct segment {
int index;
double start, end;
double d_start;
+ char *url;
+ bool lazy;
struct demuxer *d;
// stream_map[sh_stream.index] = index into priv.streams, where sh_stream
// is a stream from the source d. It's used to map the streams of the
@@ -51,6 +54,7 @@ struct priv {
struct timeline *tl;
double duration;
+ bool dash;
struct segment **segments;
int num_segments;
@@ -65,6 +69,50 @@ struct priv {
int eos_packets;
};
+static bool target_stream_used(struct segment *seg, int target_index)
+{
+ for (int n = 0; n < seg->num_stream_map; n++) {
+ if (seg->stream_map[n] == target_index)
+ return true;
+ }
+ return false;
+}
+
+// Create mapping from segment streams to virtual timeline streams.
+static void associate_streams(struct demuxer *demuxer, struct segment *seg)
+{
+ struct priv *p = demuxer->priv;
+
+ if (!seg->d || seg->stream_map)
+ return;
+
+ int counts[STREAM_TYPE_COUNT] = {0};
+
+ int num_streams = demux_get_num_stream(seg->d);
+ for (int n = 0; n < num_streams; n++) {
+ struct sh_stream *sh = demux_get_stream(seg->d, n);
+ // Try associating by demuxer ID (supposedly useful for ordered chapters).
+ struct sh_stream *other =
+ demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
+ if (!other || !target_stream_used(seg, other->index)) {
+ // Try to associate the first unused stream with matching media type.
+ for (int i = 0; i < p->num_streams; i++) {
+ struct sh_stream *cur = p->streams[i].sh;
+ if (cur->type == sh->type && !target_stream_used(seg, cur->index))
+ {
+ other = cur;
+ break;
+ }
+ }
+ }
+
+ MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
+ other ? other->index : -1);
+
+ counts[sh->type] += 1;
+ }
+}
+
static void reselect_streams(struct demuxer *demuxer)
{
struct priv *p = demuxer->priv;
@@ -77,6 +125,9 @@ static void reselect_streams(struct demuxer *demuxer)
for (int n = 0; n < p->num_segments; n++) {
struct segment *seg = p->segments[n];
for (int i = 0; i < seg->num_stream_map; i++) {
+ if (!seg->d)
+ continue;
+
struct sh_stream *sh = demux_get_stream(seg->d, i);
bool selected = false;
if (seg->stream_map[i] >= 0)
@@ -89,8 +140,42 @@ static void reselect_streams(struct demuxer *demuxer)
}
}
+static void close_lazy_segments(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ // unload previous segment
+ for (int n = 0; n < p->num_segments; n++) {
+ struct segment *seg = p->segments[n];
+ if (seg != p->current && seg->d && seg->lazy) {
+ free_demuxer_and_stream(seg->d);
+ seg->d = NULL;
+ }
+ }
+}
+
+static void reopen_lazy_segments(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ if (p->current->d)
+ return;
+
+ close_lazy_segments(demuxer);
+
+ struct demuxer_params params = {
+ .init_fragment = p->tl->init_fragment,
+ .skip_lavf_probing = true,
+ };
+ p->current->d = demux_open_url(p->current->url, &params,
+ demuxer->stream->cancel, demuxer->global);
+ if (!p->current->d && !demux_cancel_test(demuxer))
+ MP_ERR(demuxer, "failed to load segment\n");
+ associate_streams(demuxer, p->current);
+}
+
static void switch_segment(struct demuxer *demuxer, struct segment *new,
- double start_pts, int flags)
+ double start_pts, int flags, bool init)
{
struct priv *p = demuxer->priv;
@@ -100,9 +185,14 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
p->current = new;
+ reopen_lazy_segments(demuxer);
+ if (!new->d)
+ return;
reselect_streams(demuxer);
- demux_set_ts_offset(new->d, new->start - new->d_start);
- demux_seek(new->d, start_pts, flags);
+ if (!p->dash)
+ demux_set_ts_offset(new->d, new->start - new->d_start);
+ if (!p->dash || !init)
+ demux_seek(new->d, start_pts, flags);
for (int n = 0; n < p->num_streams; n++) {
struct virtual_stream *vs = &p->streams[n];
@@ -129,7 +219,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
}
}
- switch_segment(demuxer, new, pts, flags);
+ switch_segment(demuxer, new, pts, flags, false);
}
static int d_fill_buffer(struct demuxer *demuxer)
@@ -137,9 +227,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
struct priv *p = demuxer->priv;
if (!p->current)
- switch_segment(demuxer, p->segments[0], 0, 0);
+ switch_segment(demuxer, p->segments[0], 0, 0, true);
struct segment *seg = p->current;
+ if (!seg || !seg->d)
+ return 0;
struct demux_packet *pkt = demux_read_any_packet(seg->d);
if (!pkt || pkt->pts >= seg->end)
@@ -176,20 +268,21 @@ static int d_fill_buffer(struct demuxer *demuxer)
}
if (!next)
return 0;
- switch_segment(demuxer, next, next->start, 0);
+ switch_segment(demuxer, next, next->start, 0, true);
return 1; // reader will retry
}
if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
goto drop;
- if (!pkt->codec)
- pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
-
- if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
- pkt->start = seg->start;
- if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
- pkt->end = seg->end;
+ if (!p->dash) {
+ if (!pkt->codec)
+ pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
+ if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
+ pkt->start = seg->start;
+ if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
+ pkt->end = seg->end;
+ }
pkt->stream = seg->stream_map[pkt->stream];
if (pkt->stream < 0)
@@ -214,7 +307,8 @@ static int d_fill_buffer(struct demuxer *demuxer)
}
}
- pkt->new_segment |= vs->new_segment;
+ if (!p->dash)
+ pkt->new_segment |= vs->new_segment;
vs->new_segment = false;
demux_add_packet(vs->sh, pkt);
@@ -232,9 +326,9 @@ static void print_timeline(struct demuxer *demuxer)
MP_VERBOSE(demuxer, "Timeline segments:\n");
for (int n = 0; n < p->num_segments; n++) {
struct segment *seg = p->segments[n];
- int src_num = -1;
- for (int i = 0; i < p->tl->num_sources; i++) {
- if (p->tl->sources[i] == seg->d) {
+ int src_num = n;
+ for (int i = 0; i < n - 1; i++) {
+ if (seg->d && p->segments[i]->d == seg->d) {
src_num = i;
break;
}
@@ -242,50 +336,12 @@ static void print_timeline(struct demuxer *demuxer)
MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
for (int i = 0; i < seg->num_stream_map; i++)
MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
- MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->d->filename);
+ MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
}
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
-}
-static bool target_stream_used(struct segment *seg, int target_index)
-{
- for (int n = 0; n < seg->num_stream_map; n++) {
- if (seg->stream_map[n] == target_index)
- return true;
- }
- return false;
-}
-
-// Create mapping from segment streams to virtual timeline streams.
-static void associate_streams(struct demuxer *demuxer, struct segment *seg)
-{
- struct priv *p = demuxer->priv;
-
- int counts[STREAM_TYPE_COUNT] = {0};
-
- int num_streams = demux_get_num_stream(seg->d);
- for (int n = 0; n < num_streams; n++) {
- struct sh_stream *sh = demux_get_stream(seg->d, n);
- // Try associating by demuxer ID (supposedly useful for ordered chapters).
- struct sh_stream *other =
- demuxer_stream_by_demuxer_id(demuxer, sh->type, sh->demuxer_id);
- if (!other || !target_stream_used(seg, other->index)) {
- // Try to associate the first unused stream with matching media type.
- for (int i = 0; i < p->num_streams; i++) {
- struct sh_stream *cur = p->streams[i].sh;
- if (cur->type == sh->type && !target_stream_used(seg, cur->index))
- {
- other = cur;
- break;
- }
- }
- }
-
- MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map,
- other ? other->index : -1);
-
- counts[sh->type] += 1;
- }
+ if (p->dash)
+ MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
}
static int d_open(struct demuxer *demuxer, enum demux_check check)
@@ -334,6 +390,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
struct segment *seg = talloc_ptrtype(p, seg);
*seg = (struct segment){
.d = part->source,
+ .url = part->source ? part->source->filename : part->url,
+ .lazy = !part->source,
.d_start = part->source_start,
.start = part->start,
.end = next->start,
@@ -345,6 +403,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
}
+ p->dash = p->tl->dash;
+
print_timeline(demuxer);
demuxer->seekable = true;
@@ -352,6 +412,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
demuxer->filetype = meta->filetype ? meta->filetype : meta->desc->name;
+ demuxer->is_network = p->tl->demuxer->is_network;
+
reselect_streams(demuxer);
return 0;
@@ -361,6 +423,8 @@ static void d_close(struct demuxer *demuxer)
{
struct priv *p = demuxer->priv;
struct demuxer *master = p->tl->demuxer;
+ p->current = NULL;
+ close_lazy_segments(demuxer);
timeline_destroy(p->tl);
free_demuxer(master);
}
diff --git a/demux/demux_tv.c b/demux/demux_tv.c
index d80693e465..4a8e0c70fa 100644
--- a/demux/demux_tv.c
+++ b/demux/demux_tv.c
@@ -22,7 +22,10 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
tvi_handle_t *tvh;
const tvi_functions_t *funcs;
- if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV)
+ if (check > DEMUX_CHECK_REQUEST)
+ return -1;
+
+ if (!demuxer->stream->info || strcmp(demuxer->stream->info->name, "tv") != 0)
return -1;
tv_param_t *params = mp_get_config_group(demuxer, demuxer->global,
@@ -31,7 +34,7 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
bstr channel, input;
bstr_split_tok(urlparams, "/", &channel, &input);
if (channel.len) {
- talloc_free(params->channels);
+ talloc_free(params->channel);
params->channel = bstrto0(NULL, channel);
}
if (input.len) {
diff --git a/demux/ebml.c b/demux/ebml.c
index abbb73cfac..d62dd40593 100644
--- a/demux/ebml.c
+++ b/demux/ebml.c
@@ -120,7 +120,7 @@ int64_t ebml_read_vlen_int(bstr *buffer)
return EBML_INT_INVALID;
l = len - buffer->len;
- return unum - ((1 << ((7 * l) - 1)) - 1);
+ return unum - ((1LL << ((7 * l) - 1)) - 1);
}
/*
diff --git a/demux/packet.c b/demux/packet.c
index 32fabc4f78..47f0cb87dc 100644
--- a/demux/packet.c
+++ b/demux/packet.c
@@ -94,7 +94,7 @@ void demux_packet_shorten(struct demux_packet *dp, size_t len)
{
assert(len <= dp->len);
dp->len = len;
- memset(dp->buffer + dp->len, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+ memset(dp->buffer + dp->len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}
void free_demux_packet(struct demux_packet *dp)
@@ -132,8 +132,8 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp)
int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
{
-#if HAVE_AVFRAME_SKIP_SAMPLES
- if (!start && !end)
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ if (!start && !end)
return 0;
if (!dp->avpacket)
return -1;
@@ -146,3 +146,21 @@ int demux_packet_set_padding(struct demux_packet *dp, int start, int end)
#endif
return 0;
}
+
+int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
+ void *data, size_t size)
+{
+#if LIBAVCODEC_VERSION_MICRO >= 100
+ if (!dp->avpacket)
+ return -1;
+ uint8_t *sd = av_packet_new_side_data(dp->avpacket,
+ AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL,
+ 8 + size);
+ if (!sd)
+ return -1;
+ AV_WB64(sd, id);
+ if (size > 0)
+ memcpy(sd + 8, data, size);
+#endif
+ return 0;
+}
diff --git a/demux/packet.h b/demux/packet.h
index 723dbc7e54..d669d02376 100644
--- a/demux/packet.h
+++ b/demux/packet.h
@@ -55,5 +55,7 @@ struct demux_packet *demux_copy_packet(struct demux_packet *dp);
void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *src);
int demux_packet_set_padding(struct demux_packet *dp, int start, int end);
+int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
+ void *data, size_t size);
#endif /* MPLAYER_DEMUX_PACKET_H */
diff --git a/demux/timeline.c b/demux/timeline.c
index 73f3ab79a2..700a6dfd05 100644
--- a/demux/timeline.c
+++ b/demux/timeline.c
@@ -33,8 +33,10 @@ void timeline_destroy(struct timeline *tl)
return;
for (int n = 0; n < tl->num_sources; n++) {
struct demuxer *d = tl->sources[n];
- if (d != tl->demuxer)
+ if (d != tl->demuxer && d != tl->track_layout)
free_demuxer_and_stream(d);
}
+ if (tl->track_layout && tl->track_layout != tl->demuxer)
+ free_demuxer_and_stream(tl->track_layout);
talloc_free(tl);
}
diff --git a/demux/timeline.h b/demux/timeline.h
index edc6a2f7ae..21a6602ba8 100644
--- a/demux/timeline.h
+++ b/demux/timeline.h
@@ -4,6 +4,7 @@
struct timeline_part {
double start;
double source_start;
+ char *url;
struct demuxer *source;
};
@@ -15,6 +16,9 @@ struct timeline {
// main source
struct demuxer *demuxer;
+ bstr init_fragment;
+ bool dash;
+
// All referenced files. The source file must be at sources[0].
struct demuxer **sources;
int num_sources;
diff --git a/etc/input.conf b/etc/input.conf
index c51d5c7995..fe39b6c980 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -143,7 +143,7 @@
#CLOSE_WIN {encode} quit 4
#E cycle edition # next edition
#l ab-loop # Set/clear A-B loop points
-#L cycle-values loop "inf" "no" # toggle infinite looping
+#L cycle-values loop-file "inf" "no" # toggle infinite looping
#ctrl+c quit 4
# Apple Remote section
diff --git a/etc/restore-old-bindings.conf b/etc/restore-old-bindings.conf
index aab0c9f4bc..af8933896f 100644
--- a/etc/restore-old-bindings.conf
+++ b/etc/restore-old-bindings.conf
@@ -9,6 +9,10 @@
#
# Older installations use ~/.mpv/input.conf instead.
+# changed in mpv 0.24.0
+
+L cycle-values loop "inf" "no"
+
# changed in mpv 0.10.0
O osd
diff --git a/input/input.c b/input/input.c
index 9525dbcbb1..f0f9f64e9b 100644
--- a/input/input.c
+++ b/input/input.c
@@ -144,7 +144,8 @@ struct input_ctx {
struct cmd_queue cmd_queue;
- struct mp_cancel *cancel;
+ void (*cancel)(void *cancel_ctx);
+ void *cancel_ctx;
void (*wakeup_cb)(void *ctx);
void *wakeup_ctx;
@@ -809,7 +810,7 @@ int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
input_lock(ictx);
if (cmd) {
if (ictx->cancel && test_abort_cmd(ictx, cmd))
- mp_cancel_trigger(ictx->cancel);
+ ictx->cancel(ictx->cancel_ctx);
queue_add_tail(&ictx->cmd_queue, cmd);
mp_input_wakeup(ictx);
}
@@ -1335,10 +1336,11 @@ void mp_input_uninit(struct input_ctx *ictx)
talloc_free(ictx);
}
-void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel)
+void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c)
{
input_lock(ictx);
- ictx->cancel = cancel;
+ ictx->cancel = cb;
+ ictx->cancel_ctx = c;
input_unlock(ictx);
}
diff --git a/input/input.h b/input/input.h
index 5b5edd580d..fb928e0808 100644
--- a/input/input.h
+++ b/input/input.h
@@ -242,8 +242,7 @@ void mp_input_wakeup(struct input_ctx *ictx);
// Used to asynchronously abort playback. Needed because the core still can
// block on network in some situations.
-struct mp_cancel;
-void mp_input_set_cancel(struct input_ctx *ictx, struct mp_cancel *cancel);
+void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c);
// If this returns true, use Right Alt key as Alt Gr to produce special
// characters. If false, count Right Alt as the modifier Alt key.
diff --git a/libmpv/client.h b/libmpv/client.h
index 51f5c24d59..14777b429a 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -864,8 +864,7 @@ int mpv_command(mpv_handle *ctx, const char **args);
* function succeeds, this is set to command-specific return
* data. You must call mpv_free_node_contents() to free it
* (again, only if the command actually succeeds).
- * Currently, no command uses this, but that can change in
- * the future.
+ * Not many commands actually use this at all.
* @return error code (the result parameter is not set on error)
*/
int mpv_command_node(mpv_handle *ctx, mpv_node *args, mpv_node *result);
diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h
index 6500b4ef65..0c14f0f2de 100644
--- a/libmpv/opengl_cb.h
+++ b/libmpv/opengl_cb.h
@@ -117,14 +117,17 @@ extern "C" {
*
* While "normal" mpv loads the OpenGL hardware decoding interop on demand,
* this can't be done with opengl_cb for internal technical reasons. Instead,
- * make it load the interop at load time by setting the "hwdec-preload"="auto"
- * option before calling mpv_opengl_cb_init_gl().
+ * make it load the interop at load time by setting the
+ * "opengl-hwdec-interop"="auto" option before calling mpv_opengl_cb_init_gl()
+ * ("hwdec-preload" in older mpv releases).
*
* There may be certain requirements on the OpenGL implementation:
* - Windows: ANGLE is required (although in theory GL/DX interop could be used)
* - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
* must be provided (see sections below)
- * - nVidia/Linux: GLX is required
+ * - nVidia/Linux: GLX is required (if you force "cuda", it should work on EGL
+ * as well, if you have recent enough drivers and the
+ * "hwaccel" option is set to "cuda" as well)
* - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
*
* Once these things are setup, hardware decoding can be enabled/disabled at
diff --git a/misc/charset_conv.c b/misc/charset_conv.c
index 1758223f1a..51e55c6338 100644
--- a/misc/charset_conv.c
+++ b/misc/charset_conv.c
@@ -51,28 +51,6 @@ bool mp_charset_is_utf16(const char *user_cp)
bstr_case_startswith(s, bstr0("utf-16"));
}
-// Split the string on ':' into components.
-// out_arr is at least max entries long.
-// Return number of out_arr entries filled.
-static int split_colon(const char *user_cp, int max, bstr *out_arr)
-{
- if (!user_cp || max < 1)
- return 0;
-
- int count = 0;
- while (1) {
- const char *next = strchr(user_cp, ':');
- if (next && max - count > 1) {
- out_arr[count++] = (bstr){(char *)user_cp, next - user_cp};
- user_cp = next + 1;
- } else {
- out_arr[count++] = (bstr){(char *)user_cp, strlen(user_cp)};
- break;
- }
- }
- return count;
-}
-
static const char *const utf_bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"};
static const char *const utf_enc[3] = {"utf-8", "utf-16le", "utf-16be"};
@@ -120,59 +98,20 @@ static const char *mp_uchardet(void *talloc_ctx, struct mp_log *log, bstr buf)
// it's a real iconv codepage), user_cp is returned without even looking at
// the buf data.
// The return value may (but doesn't have to) be allocated under talloc_ctx.
-static const char *mp_charset_guess_compat(void *talloc_ctx, struct mp_log *log,
- bstr buf, const char *user_cp,
- int flags)
-{
- mp_warn(log, "This syntax for the --sub-codepage option is deprecated.\n");
-
- bstr params[3] = {{0}};
- split_colon(user_cp, 3, params);
-
- bstr type = params[0];
- char lang[100];
- snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1]));
- const char *fallback = params[2].start; // last item, already 0-terminated
-
- const char *res = NULL;
-
-#if HAVE_UCHARDET
- if (bstrcasecmp0(type, "uchardet") == 0) {
- res = mp_uchardet(talloc_ctx, log, buf);
- if (!res && bstr_validate_utf8(buf) >= 0)
- res = "utf-8";
- }
-#endif
-
- if (bstrcasecmp0(type, "utf8") == 0 || bstrcasecmp0(type, "utf-8") == 0) {
- if (!fallback)
- fallback = params[1].start; // must be already 0-terminated
- int r = bstr_validate_utf8(buf);
- if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF)))
- res = "utf-8";
- }
-
- if (res) {
- mp_dbg(log, "%.*s detected charset: '%s'\n", BSTR_P(type), res);
- } else {
- res = fallback;
- mp_dbg(log, "Detection with %.*s failed: fallback to %s\n",
- BSTR_P(type), res && res[0] ? res : "broken UTF-8/Latin1");
- }
-
- if (!res && !(flags & MP_STRICT_UTF8))
- res = "UTF-8-BROKEN";
-
- mp_verbose(log, "Using charset '%s'.\n", res);
- return res;
-}
-
const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
const char *user_cp, int flags)
{
if (strcasecmp(user_cp, "enca") == 0 || strcasecmp(user_cp, "guess") == 0 ||
strcasecmp(user_cp, "uchardet") == 0 || strchr(user_cp, ':'))
- return mp_charset_guess_compat(talloc_ctx, log, buf, user_cp, flags);
+ {
+ mp_err(log, "This syntax for the --sub-codepage option was deprecated "
+ "and has been removed.\n");
+ if (strncasecmp(user_cp, "utf8:", 5) == 0) {
+ user_cp = user_cp + 5;
+ } else {
+ user_cp = "";
+ }
+ }
if (user_cp[0] == '+') {
mp_verbose(log, "Forcing charset '%s'.\n", user_cp + 1);
@@ -191,7 +130,7 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
return "utf-8";
}
- const char *res = user_cp;
+ const char *res = NULL;
if (strcasecmp(user_cp, "auto") == 0) {
#if HAVE_UCHARDET
res = mp_uchardet(talloc_ctx, log, buf);
@@ -200,6 +139,8 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
mp_verbose(log, "Charset auto-detection failed.\n");
res = "UTF-8-BROKEN";
}
+ } else {
+ res = user_cp;
}
mp_verbose(log, "Using charset '%s'.\n", res);
@@ -293,5 +234,9 @@ bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags)
#endif
failure:
- return (bstr){0};
+ if (flags & MP_NO_LATIN1_FALLBACK) {
+ return buf;
+ } else {
+ return bstr_sanitize_utf8_latin1(NULL, buf);
+ }
}
diff --git a/misc/charset_conv.h b/misc/charset_conv.h
index 9be7a50961..ccaa17e3c9 100644
--- a/misc/charset_conv.h
+++ b/misc/charset_conv.h
@@ -10,6 +10,7 @@ enum {
MP_ICONV_VERBOSE = 1, // print errors instead of failing silently
MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data
MP_STRICT_UTF8 = 4, // don't fall back to UTF-8-BROKEN when guessing
+ MP_NO_LATIN1_FALLBACK = 8, // fall back to input buffer instead of latin1
};
bool mp_charset_is_utf8(const char *user_cp);
diff --git a/options/options.c b/options/options.c
index f79df7d82f..b711156c18 100644
--- a/options/options.c
+++ b/options/options.c
@@ -40,6 +40,7 @@
#include "stream/stream.h"
#include "video/csputils.h"
#include "video/hwdec.h"
+#include "video/out/opengl/hwdec.h"
#include "video/image_writer.h"
#include "sub/osd.h"
#include "audio/filter/af.h"
@@ -87,6 +88,8 @@ extern const struct m_obj_list af_obj_list;
extern const struct m_obj_list vo_obj_list;
extern const struct m_obj_list ao_obj_list;
+extern const struct m_sub_options angle_conf;
+
const struct m_opt_choice_alternatives mp_hwdec_names[] = {
{"no", HWDEC_NONE},
{"auto", HWDEC_AUTO},
@@ -150,9 +153,9 @@ const struct m_sub_options stream_cache_conf = {
static const m_option_t mp_vo_opt_list[] = {
OPT_SETTINGSLIST("vo", video_driver_list, 0, &vo_obj_list, ),
- OPT_CHOICE_C("hwdec-preload", hwdec_preload_api, 0, mp_hwdec_names),
OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0),
OPT_FLAG("taskbar-progress", taskbar_progress, 0),
+ OPT_FLAG("snap-window", snap_window, 0),
OPT_FLAG("ontop", ontop, 0),
OPT_FLAG("border", border, 0),
OPT_FLAG("fit-border", fit_border, 0),
@@ -199,7 +202,11 @@ static const m_option_t mp_vo_opt_list[] = {
0, drm_validate_connector_opt),
OPT_INT("drm-mode", drm_mode_id, 0),
#endif
-
+#if HAVE_GL
+ OPT_STRING_VALIDATE("opengl-hwdec-interop", gl_hwdec_interop, 0,
+ gl_hwdec_validate_opt),
+ OPT_REPLACED("hwdec-preload", "opengl-hwdec-interop"),
+#endif
{0}
};
@@ -216,6 +223,7 @@ const struct m_sub_options vo_sub_opts = {
.keepaspect_window = 1,
.hidpi_window_scale = 1,
.taskbar_progress = 1,
+ .snap_window = 0,
.border = 1,
.fit_border = 1,
.WinID = -1,
@@ -379,6 +387,7 @@ const m_option_t mp_opts[] = {
OPT_STRING("audio-demuxer", audio_demuxer_name, 0),
OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
OPT_FLAG("demuxer-thread", demuxer_thread, 0),
+ OPT_FLAG("prefetch-playlist", prefetch_open, 0),
OPT_FLAG("cache-pause", cache_pausing, 0),
OPT_DOUBLE("mf-fps", mf_fps, 0),
@@ -466,6 +475,7 @@ const m_option_t mp_opts[] = {
OPT_FLAG("sub-forced-only", forced_subs_only, UPDATE_OSD),
OPT_FLAG("stretch-dvd-subs", stretch_dvd_subs, UPDATE_OSD),
OPT_FLAG("stretch-image-subs-to-screen", stretch_image_subs, UPDATE_OSD),
+ OPT_FLAG("image-subs-video-resolution", image_subs_video_res, UPDATE_OSD),
OPT_FLAG("sub-fix-timing", sub_fix_timing, 0),
OPT_CHOICE("sub-auto", sub_auto, 0,
({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})),
@@ -490,6 +500,7 @@ const m_option_t mp_opts[] = {
({"none", 0}, {"light", 1}, {"normal", 2}, {"native", 3})),
OPT_CHOICE("sub-ass-shaper", ass_shaper, UPDATE_OSD,
({"simple", 0}, {"complex", 1})),
+ OPT_FLAG("sub-ass-justify", ass_justify, 0),
OPT_CHOICE("sub-ass-style-override", ass_style_override, UPDATE_OSD,
({"no", 0}, {"yes", 1}, {"force", 3}, {"signfs", 4}, {"strip", 5})),
OPT_FLAG("sub-scale-by-window", sub_scale_by_window, UPDATE_OSD),
@@ -583,7 +594,6 @@ const m_option_t mp_opts[] = {
OPT_FLAG("untimed", untimed, 0),
- OPT_STRING("stream-capture", stream_capture, M_OPT_FILE),
OPT_STRING("stream-dump", stream_dump, M_OPT_FILE),
OPT_FLAG("stop-playback-on-init-failure", stop_playback_on_init_failure, 0),
@@ -671,6 +681,8 @@ const m_option_t mp_opts[] = {
OPT_STRING("screenshot-template", screenshot_template, 0),
OPT_STRING("screenshot-directory", screenshot_directory, 0),
+ OPT_STRING("record-file", record_file, M_OPT_FILE),
+
OPT_SUBSTRUCT("", input_opts, input_config, 0),
OPT_PRINT("list-protocols", stream_print_proto_list),
@@ -686,6 +698,15 @@ const m_option_t mp_opts[] = {
OPT_SUBSTRUCT("", gl_video_opts, gl_video_conf, 0),
#endif
+#if HAVE_EGL_ANGLE
+ OPT_SUBSTRUCT("", angle_opts, angle_conf, 0),
+#endif
+
+#if HAVE_GL_WIN32
+ OPT_CHOICE("opengl-dwmflush", wingl_dwm_flush, 0,
+ ({"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})),
+#endif
+
#if HAVE_ENCODING
OPT_SUBSTRUCT("", encode_opts, encode_config, 0),
#endif
@@ -697,7 +718,8 @@ const m_option_t mp_opts[] = {
OPT_REPLACED("ass", "sub-ass"),
OPT_REPLACED("audiofile", "audio-file"),
OPT_REMOVED("benchmark", "use --untimed (no stats)"),
- OPT_REMOVED("capture", "use --stream-capture=<filename>"),
+ OPT_REMOVED("capture", NULL),
+ OPT_REMOVED("stream-capture", NULL),
OPT_REMOVED("channels", "use --audio-channels (changed semantics)"),
OPT_REPLACED("cursor-autohide-delay", "cursor-autohide"),
OPT_REPLACED("delay", "audio-delay"),
diff --git a/options/options.h b/options/options.h
index fc37e98f8a..f465c0f862 100644
--- a/options/options.h
+++ b/options/options.h
@@ -10,6 +10,7 @@ typedef struct mp_vo_opts {
struct m_obj_settings *video_driver_list;
int taskbar_progress;
+ int snap_window;
int ontop;
int fullscreen;
int border;
@@ -50,7 +51,7 @@ typedef struct mp_vo_opts {
// vo_wayland, vo_drm
struct sws_opts *sws_opts;
// vo_opengl, vo_opengl_cb
- int hwdec_preload_api;
+ char *gl_hwdec_interop;
// vo_drm
char *drm_connector_spec;
int drm_mode_id;
@@ -136,8 +137,8 @@ typedef struct MPOpts {
int video_osd;
int untimed;
- char *stream_capture;
char *stream_dump;
+ char *record_file;
int stop_playback_on_init_failure;
int loop_times;
int loop_file;
@@ -213,12 +214,14 @@ typedef struct MPOpts {
int forced_subs_only;
int stretch_dvd_subs;
int stretch_image_subs;
+ int image_subs_video_res;
int sub_fix_timing;
char **audio_files;
char *demuxer_name;
int demuxer_thread;
+ int prefetch_open;
char *audio_demuxer_name;
char *sub_demuxer_name;
@@ -279,6 +282,7 @@ typedef struct MPOpts {
int ass_style_override;
int ass_hinting;
int ass_shaper;
+ int ass_justify;
int sub_clear_on_seek;
int teletext_page;
@@ -318,7 +322,10 @@ typedef struct MPOpts {
char *ipc_path;
char *input_file;
+ int wingl_dwm_flush;
+
struct gl_video_opts *gl_video_opts;
+ struct angle_opts *angle_opts;
struct dvd_opts *dvd_opts;
} MPOpts;
diff --git a/options/parse_configfile.c b/options/parse_configfile.c
index e7c877349d..0ef80082d4 100644
--- a/options/parse_configfile.c
+++ b/options/parse_configfile.c
@@ -150,7 +150,8 @@ int m_config_parse(m_config_t *config, const char *location, bstr data,
}
}
- m_config_finish_default_profile(config, flags);
+ if (config->recursion_depth == 0)
+ m_config_finish_default_profile(config, flags);
talloc_free(tmp);
return 1;
diff --git a/osdep/atomic.h b/osdep/atomic.h
index 9028a504eb..50f4f403da 100644
--- a/osdep/atomic.h
+++ b/osdep/atomic.h
@@ -61,25 +61,6 @@ typedef struct { volatile unsigned long long v; } atomic_ullong;
__atomic_compare_exchange_n(&(a)->v, b, c, 0, __ATOMIC_SEQ_CST, \
__ATOMIC_SEQ_CST)
-#elif HAVE_SYNC_BUILTINS
-
-#define atomic_load(p) \
- __sync_fetch_and_add(&(p)->v, 0)
-#define atomic_store(p, val) \
- (__sync_synchronize(), (p)->v = (val), __sync_synchronize())
-#define atomic_fetch_add(a, b) \
- __sync_fetch_and_add(&(a)->v, b)
-#define atomic_fetch_and(a, b) \
- __sync_fetch_and_and(&(a)->v, b)
-#define atomic_fetch_or(a, b) \
- __sync_fetch_and_or(&(a)->v, b)
-// Assumes __sync_val_compare_and_swap is "strong" (using the C11 meaning).
-#define atomic_compare_exchange_strong(p, old, new) \
- ({ __typeof__((p)->v) val_ = __sync_val_compare_and_swap(&(p)->v, *(old), new); \
- bool ok_ = val_ == *(old); \
- if (!ok_) *(old) = val_; \
- ok_; })
-
#elif defined(__GNUC__)
#include <pthread.h>
diff --git a/osdep/mpv.rc b/osdep/mpv.rc
index 67c09f4f2c..8212437574 100644
--- a/osdep/mpv.rc
+++ b/osdep/mpv.rc
@@ -34,7 +34,7 @@ VS_VERSION_INFO VERSIONINFO
VALUE "CompanyName", "mpv"
VALUE "FileDescription", "mpv"
VALUE "FileVersion", "2.0.0.0"
- VALUE "LegalCopyright", "(C) 2000-2016 mpv/mplayer2/MPlayer"
+ VALUE "LegalCopyright", "(C) 2000-2017 mpv/mplayer2/MPlayer"
VALUE "OriginalFilename", "mpv.exe"
VALUE "ProductName", "mpv"
VALUE "ProductVersion", "2.0.0.0"
diff --git a/osdep/windows_utils.c b/osdep/windows_utils.c
index a1ea32191a..a60eba3d26 100644
--- a/osdep/windows_utils.c
+++ b/osdep/windows_utils.c
@@ -22,6 +22,7 @@
#include <errors.h>
#include <audioclient.h>
#include <d3d9.h>
+#include <dxgi1_2.h>
#include "windows_utils.h"
@@ -118,6 +119,13 @@ static char *hresult_to_str(const HRESULT hr)
E(D3DERR_CANNOTPROTECTCONTENT)
E(D3DERR_UNSUPPORTEDCRYPTO)
E(D3DERR_PRESENT_STATISTICS_DISJOINT)
+ E(DXGI_ERROR_DEVICE_HUNG)
+ E(DXGI_ERROR_DEVICE_REMOVED)
+ E(DXGI_ERROR_DEVICE_RESET)
+ E(DXGI_ERROR_DRIVER_INTERNAL_ERROR)
+ E(DXGI_ERROR_INVALID_CALL)
+ E(DXGI_ERROR_WAS_STILL_DRAWING)
+ E(DXGI_STATUS_OCCLUDED)
default:
return "<Unknown>";
}
diff --git a/osdep/windows_utils.h b/osdep/windows_utils.h
index 6c750ded2a..a20a558709 100644
--- a/osdep/windows_utils.h
+++ b/osdep/windows_utils.h
@@ -20,6 +20,10 @@
#include <windows.h>
+// Conditionally release a COM interface and set the pointer to NULL
+#define SAFE_RELEASE(u) \
+ do { if ((u) != NULL) (u)->lpVtbl->Release(u); (u) = NULL; } while(0)
+
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid);
#define mp_GUID_to_str(guid) mp_GUID_to_str_buf((char[40]){0}, 40, (guid))
char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr);
diff --git a/player/audio.c b/player/audio.c
index 5c393f3d3a..e1766143b9 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -817,7 +817,7 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
break;
res = decode_new_frame(ao_c);
- if (res == AD_NO_PROGRESS)
+ if (res == AD_NO_PROGRESS || res == AD_WAIT)
break;
if (res < 0) {
// drain filters first (especially for true EOF case)
diff --git a/player/client.c b/player/client.c
index 85e3c4021c..31a55332d2 100644
--- a/player/client.c
+++ b/player/client.c
@@ -359,6 +359,8 @@ void mpv_detach_destroy(mpv_handle *ctx)
if (!ctx)
return;
+ MP_VERBOSE(ctx, "Exiting...\n");
+
// reserved_events equals the number of asynchronous requests that weren't
// yet replied. In order to avoid that trying to reply to a removed client
// causes a crash, block until all asynchronous requests were served.
@@ -407,7 +409,13 @@ void mpv_terminate_destroy(mpv_handle *ctx)
if (!ctx)
return;
- mpv_command(ctx, (const char*[]){"quit", NULL});
+ if (ctx->mpctx->initialized) {
+ mpv_command(ctx, (const char*[]){"quit", NULL});
+ } else {
+ mp_dispatch_lock(ctx->mpctx->dispatch);
+ ctx->mpctx->stop_play = PT_QUIT;
+ mp_dispatch_unlock(ctx->mpctx->dispatch);
+ }
if (!ctx->owner) {
mpv_detach_destroy(ctx);
@@ -851,6 +859,12 @@ static bool compare_value(void *a, void *b, mpv_format format)
return false;
return compare_value(&a_n->u, &b_n->u, a_n->format);
}
+ case MPV_FORMAT_BYTE_ARRAY: {
+ struct mpv_byte_array *a_r = a, *b_r = b;
+ if (a_r->size != b_r->size)
+ return false;
+ return memcmp(a_r->data, b_r->data, a_r->size) == 0;
+ }
case MPV_FORMAT_NODE_ARRAY:
case MPV_FORMAT_NODE_MAP:
{
@@ -968,7 +982,7 @@ static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd, mpv_node *res
return MPV_ERROR_INVALID_PARAMETER;
if (mp_input_is_abort_cmd(cmd))
- mp_cancel_trigger(ctx->mpctx->playback_abort);
+ mp_abort_playback_async(ctx->mpctx);
cmd->sender = ctx->name;
@@ -1088,6 +1102,7 @@ int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format,
mp_get_property_id(ctx->mpctx, name) >= 0)
return MPV_ERROR_PROPERTY_UNAVAILABLE;
switch (r) {
+ case MPV_ERROR_SUCCESS: return MPV_ERROR_SUCCESS;
case MPV_ERROR_OPTION_FORMAT: return MPV_ERROR_PROPERTY_FORMAT;
case MPV_ERROR_OPTION_NOT_FOUND: return MPV_ERROR_PROPERTY_NOT_FOUND;
default: return MPV_ERROR_PROPERTY_ERROR;
diff --git a/player/command.c b/player/command.c
index 74c7e26966..cde67ebe51 100644
--- a/player/command.c
+++ b/player/command.c
@@ -243,6 +243,78 @@ void mark_seek(struct MPContext *mpctx)
cmd->last_seek_time = now;
}
+static char *skip_n_lines(char *text, int lines)
+{
+ while (text && lines > 0) {
+ char *next = strchr(text, '\n');
+ text = next ? next + 1 : NULL;
+ lines--;
+ }
+ return text;
+}
+
+static int count_lines(char *text)
+{
+ int count = 0;
+ while (text) {
+ char *next = strchr(text, '\n');
+ if (!next || (next[0] == '\n' && !next[1]))
+ break;
+ text = next + 1;
+ count++;
+ }
+ return count;
+}
+
+// Given a huge string separated by new lines, attempts to cut off text above
+// the current line to keep the line visible, and below to keep rendering
+// performance up. pos gives the current line (0 for the first line).
+// "text" might be returned as is, or it can be freed and a new allocation is
+// returned.
+// This is only a heuristic - we can't deal with line breaking.
+static char *cut_osd_list(struct MPContext *mpctx, char *text, int pos)
+{
+ int screen_h, font_h;
+ osd_get_text_size(mpctx->osd, &screen_h, &font_h);
+ int max_lines = screen_h / MPMAX(font_h, 1) - 1;
+
+ if (!text || max_lines < 5)
+ return text;
+
+ int count = count_lines(text);
+ if (count <= max_lines)
+ return text;
+
+ char *new = talloc_strdup(NULL, "");
+
+ int start = pos - max_lines / 2;
+ if (start == 1)
+ start = 0; // avoid weird transition when pad_h becomes visible
+ int pad_h = start > 0;
+ if (pad_h)
+ new = talloc_strdup_append_buffer(new, "\342\206\221 (hidden items)\n");
+
+ int space = max_lines - pad_h - 1;
+ int pad_t = count - start > space;
+ if (!pad_t)
+ start = count - space;
+
+ char *head = skip_n_lines(text, start);
+ if (!head) {
+ talloc_free(new);
+ return text;
+ }
+
+ char *tail = skip_n_lines(head, max_lines - pad_h - pad_t);
+ new = talloc_asprintf_append_buffer(new, "%.*s",
+ (int)(tail ? tail - head : strlen(head)), head);
+ if (pad_t)
+ new = talloc_strdup_append_buffer(new, "\342\206\223 (hidden items)\n");
+
+ talloc_free(text);
+ return new;
+}
+
static char *format_file_size(int64_t size)
{
double s = size;
@@ -515,29 +587,6 @@ static int mp_property_stream_path(void *ctx, struct m_property *prop,
return m_property_strdup_ro(action, arg, mpctx->demuxer->filename);
}
-struct change_stream_capture_args {
- char *filename;
- struct demuxer *demux;
-};
-
-static void do_change_stream_capture(void *p)
-{
- struct change_stream_capture_args *args = p;
- stream_set_capture_file(args->demux->stream, args->filename);
-}
-
-static int mp_property_stream_capture(void *ctx, struct m_property *prop,
- int action, void *arg)
-{
- MPContext *mpctx = ctx;
- if (mpctx->demuxer && action == M_PROPERTY_SET) {
- struct change_stream_capture_args args = {*(char **)arg, mpctx->demuxer};
- demux_run_on_thread(mpctx->demuxer, do_change_stream_capture, &args);
- // fall through to mp_property_generic_option
- }
- return mp_property_generic_option(mpctx, prop, action, arg);
-}
-
/// Demuxer name (RO)
static int mp_property_demuxer(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -650,7 +699,7 @@ static int mp_property_total_avsync_change(void *ctx, struct m_property *prop,
return m_property_double_ro(action, arg, mpctx->total_avsync_change);
}
-static int mp_property_drop_frame_cnt(void *ctx, struct m_property *prop,
+static int mp_property_frame_drop_dec(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
@@ -692,8 +741,8 @@ static int mp_property_vsync_ratio(void *ctx, struct m_property *prop,
return m_property_double_ro(action, arg, vsyncs / (double)frames);
}
-static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int mp_property_frame_drop_vo(void *ctx, struct m_property *prop,
+ int action, void *arg)
{
MPContext *mpctx = ctx;
if (!mpctx->vo_chain)
@@ -3344,9 +3393,10 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
if (action == M_PROPERTY_PRINT) {
+ struct playlist *pl = mpctx->playlist;
char *res = talloc_strdup(NULL, "");
- for (struct playlist_entry *e = mpctx->playlist->first; e; e = e->next)
+ for (struct playlist_entry *e = pl->first; e; e = e->next)
{
char *p = e->filename;
if (!mp_is_url(bstr0(p))) {
@@ -3354,12 +3404,12 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
if (s[0])
p = s;
}
- const char *m = mpctx->playlist->current == e ?
- list_current : list_normal;
+ const char *m = pl->current == e ? list_current : list_normal;
res = talloc_asprintf_append(res, "%s%s\n", m, p);
}
- *(char **)arg = res;
+ *(char **)arg =
+ cut_osd_list(mpctx, res, playlist_entry_to_index(pl, pl->current));
return M_PROPERTY_OK;
}
@@ -3499,6 +3549,26 @@ static int mp_property_cwd(void *ctx, struct m_property *prop,
return M_PROPERTY_NOT_IMPLEMENTED;
}
+static int mp_property_record_file(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ struct MPContext *mpctx = ctx;
+ struct MPOpts *opts = mpctx->opts;
+ if (action == M_PROPERTY_SET) {
+ char *new = *(char **)arg;
+ if (!bstr_equals(bstr0(new), bstr0(opts->record_file))) {
+ talloc_free(opts->record_file);
+ opts->record_file = talloc_strdup(NULL, new);
+ open_recorder(mpctx, false);
+ // open_recorder() unsets it on failure.
+ if (new && !opts->record_file)
+ return M_PROPERTY_ERROR;
+ }
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(mpctx, prop, action, arg);
+}
+
static int mp_property_protocols(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -3789,7 +3859,6 @@ static const struct m_property mp_properties_base[] = {
{"path", mp_property_path},
{"media-title", mp_property_media_title},
{"stream-path", mp_property_stream_path},
- {"stream-capture", mp_property_stream_capture},
{"current-demuxer", mp_property_demuxer},
{"file-format", mp_property_file_format},
{"stream-pos", mp_property_stream_pos},
@@ -3797,10 +3866,10 @@ static const struct m_property mp_properties_base[] = {
{"duration", mp_property_duration},
{"avsync", mp_property_avsync},
{"total-avsync-change", mp_property_total_avsync_change},
- {"drop-frame-count", mp_property_drop_frame_cnt},
{"mistimed-frame-count", mp_property_mistimed_frame_count},
{"vsync-ratio", mp_property_vsync_ratio},
- {"vo-drop-frame-count", mp_property_vo_drop_frame_count},
+ {"decoder-frame-drop-count", mp_property_frame_drop_dec},
+ {"frame-drop-count", mp_property_frame_drop_vo},
{"vo-delayed-frame-count", mp_property_vo_delayed_frame_count},
{"percent-pos", mp_property_percent_pos},
{"time-start", mp_property_time_start},
@@ -3971,6 +4040,8 @@ static const struct m_property mp_properties_base[] = {
{"working-directory", mp_property_cwd},
+ {"record-file", mp_property_record_file},
+
{"protocol-list", mp_property_protocols},
{"decoder-list", mp_property_decoders},
{"encoder-list", mp_property_encoders},
@@ -3994,6 +4065,9 @@ static const struct m_property mp_properties_base[] = {
M_PROPERTY_ALIAS("colormatrix-input-range", "video-params/colorlevels"),
M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"),
M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"),
+
+ M_PROPERTY_DEPRECATED_ALIAS("drop-frame-count", "decoder-frame-drop-count"),
+ M_PROPERTY_DEPRECATED_ALIAS("vo-drop-frame-count", "frame-drop-count"),
};
// Each entry describes which properties an event (possibly) changes.
@@ -4015,7 +4089,8 @@ static const char *const *const mp_event_property_change[] = {
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
"vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio",
"estimated-display-fps", "vsync-jitter", "sub-text", "audio-bitrate",
- "video-bitrate", "sub-bitrate"),
+ "video-bitrate", "sub-bitrate", "decoder-frame-drop-count",
+ "frame-drop-count"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
"width", "height", "fps", "aspect", "vo-configured", "current-vo",
@@ -4197,9 +4272,9 @@ static const struct property_osd_display {
} property_osd_display[] = {
// general
{ "loop", "Loop" },
+ { "loop-file", "Loop current file" },
{ "chapter", .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT,
.seek_bar = OSD_SEEK_INFO_BAR },
- { "edition", .seek_msg = OSD_SEEK_INFO_EDITION },
{ "hr-seek", "hr-seek" },
{ "speed", "Speed" },
{ "clock", "Clock" },
@@ -4218,6 +4293,7 @@ static const struct property_osd_display {
// video
{ "panscan", "Panscan", .osd_progbar = OSD_PANSCAN },
{ "taskbar-progress", "Progress in taskbar" },
+ { "snap-window", "Snap to screen edges" },
{ "ontop", "Stay on top" },
{ "border", "Border" },
{ "framedrop", "Framedrop" },
@@ -4960,7 +5036,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
int dir = cmd->id == MP_CMD_PLAYLIST_PREV ? -1 : +1;
int force = cmd->args[0].v.i;
- struct playlist_entry *e = mp_next_file(mpctx, dir, force);
+ struct playlist_entry *e = mp_next_file(mpctx, dir, force, true);
if (!e && !force)
return -1;
mp_set_playlist_entry(mpctx, e);
@@ -5309,20 +5385,19 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
case MP_CMD_AB_LOOP: {
double now = get_current_time(mpctx);
- int r = 0;
if (opts->ab_loop[0] == MP_NOPTS_VALUE) {
- r = mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
+ mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
show_property_osd(mpctx, "ab-loop-a", on_osd);
} else if (opts->ab_loop[1] == MP_NOPTS_VALUE) {
- r = mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
+ mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
show_property_osd(mpctx, "ab-loop-b", on_osd);
} else {
now = MP_NOPTS_VALUE;
- r = mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
- r = mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
+ mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx);
+ mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx);
set_osd_msg(mpctx, osdl, osd_duration, "Clear A-B loop");
}
- return r > 0;
+ break;
}
case MP_CMD_DROP_BUFFERS: {
@@ -5376,7 +5451,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
if (cmd->is_up_down)
state[0] = cmd->repeated ? 'r' : (cmd->is_up ? 'u' : 'd');
event.num_args = 4;
- event.args = (const char*[4]){"key-binding", name, state, cmd->key_name};
+ event.args = (const char*[4]){"key-binding", name, state,
+ cmd->key_name ? cmd->key_name : ""};
if (mp_client_send_event_dup(mpctx, target,
MPV_EVENT_CLIENT_MESSAGE, &event) < 0)
{
diff --git a/player/core.h b/player/core.h
index 5d055482a5..79120decd3 100644
--- a/player/core.h
+++ b/player/core.h
@@ -52,8 +52,7 @@ enum mp_osd_seek_info {
OSD_SEEK_INFO_BAR = 1,
OSD_SEEK_INFO_TEXT = 2,
OSD_SEEK_INFO_CHAPTER_TEXT = 4,
- OSD_SEEK_INFO_EDITION = 8,
- OSD_SEEK_INFO_CURRENT_FILE = 16,
+ OSD_SEEK_INFO_CURRENT_FILE = 8,
};
@@ -154,6 +153,9 @@ struct track {
struct vo_chain *vo_c;
struct ao_chain *ao_c;
struct lavfi_pad *sink;
+
+ // For stream recording (remuxing mode).
+ struct mp_recorder_sink *remux_sink;
};
// Summarizes video filtering and output.
@@ -179,6 +181,8 @@ struct vo_chain {
// - video consists of a single picture, which should be shown only once
// - do not sync audio to video in any way
bool is_coverart;
+ // Just to avoid decoding the coverart picture again after a seek.
+ struct mp_image *cached_coverart;
};
// Like vo_chain, for audio.
@@ -420,6 +424,8 @@ typedef struct MPContext {
// playback rate. Used to avoid showing it multiple times.
bool drop_message_shown;
+ struct mp_recorder *recorder;
+
char *cached_watch_later_configdir;
struct screenshot_ctx *screenshot_ctx;
@@ -429,6 +435,26 @@ typedef struct MPContext {
struct mp_ipc_ctx *ipc_ctx;
struct mpv_opengl_cb_context *gl_cb_ctx;
+
+ pthread_mutex_t lock;
+
+ // --- The following fields are protected by lock
+ struct mp_cancel *demuxer_cancel; // cancel handle for MPContext.demuxer
+
+ // --- Owned by MPContext
+ pthread_t open_thread;
+ bool open_active; // open_thread is a valid thread handle, all setup
+ atomic_bool open_done;
+ // --- All fields below are immutable while open_active is true.
+ // Otherwise, they're owned by MPContext.
+ struct mp_cancel *open_cancel;
+ char *open_url;
+ char *open_format;
+ int open_url_flags;
+ // --- All fields below are owned by open_thread, unless open_done was set
+ // to true.
+ struct demuxer *open_res_demuxer;
+ int open_res_error;
} MPContext;
// audio.c
@@ -459,6 +485,7 @@ struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
struct playlist *playlist);
// loadfile.c
+void mp_abort_playback_async(struct MPContext *mpctx);
void uninit_player(struct MPContext *mpctx, unsigned int mask);
struct track *mp_add_external_file(struct MPContext *mpctx, char *filename,
enum stream_type filter);
@@ -473,7 +500,7 @@ struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer);
bool mp_remove_track(struct MPContext *mpctx, struct track *track);
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
- bool force);
+ bool force, bool mutate);
void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
void mp_play_files(struct MPContext *mpctx);
void update_demuxer_properties(struct MPContext *mpctx);
@@ -483,6 +510,10 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl);
void autoload_external_files(struct MPContext *mpctx);
struct track *select_default_track(struct MPContext *mpctx, int order,
enum stream_type type);
+void prefetch_next(struct MPContext *mpctx);
+void close_recorder(struct MPContext *mpctx);
+void close_recorder_and_error(struct MPContext *mpctx);
+void open_recorder(struct MPContext *mpctx, bool on_init);
// main.c
int mp_initialize(struct MPContext *mpctx, char **argv);
@@ -501,8 +532,6 @@ void update_vo_playback_state(struct MPContext *mpctx);
void update_window_title(struct MPContext *mpctx, bool force);
void error_on_track(struct MPContext *mpctx, struct track *track);
int stream_dump(struct MPContext *mpctx, const char *source_filename);
-int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
- void *thread_arg);
double get_track_seek_offset(struct MPContext *mpctx, struct track *track);
// osd.c
@@ -549,6 +578,7 @@ void update_screensaver_state(struct MPContext *mpctx);
// scripting.c
struct mp_scripting {
+ const char *name; // e.g. "lua script"
const char *file_ext; // e.g. "lua"
int (*load)(struct mpv_handle *client, const char *filename);
};
diff --git a/player/external_files.c b/player/external_files.c
index eb7345ac84..5eb3a1d730 100644
--- a/player/external_files.c
+++ b/player/external_files.c
@@ -10,13 +10,15 @@
#include "common/global.h"
#include "common/msg.h"
#include "misc/ctype.h"
+#include "misc/charset_conv.h"
#include "options/options.h"
#include "options/path.h"
#include "external_files.h"
static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "srt",
- "smi", "rt", "txt", "ssa", "aqt", "jss",
- "js", "ass", "mks", "vtt", "sup", NULL};
+ "smi", "rt", "ssa", "aqt", "jss",
+ "js", "ass", "mks", "vtt", "sup", "scc",
+ NULL};
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
"ogg", "m4a", "ac3", "opus", "wav",
@@ -97,11 +99,16 @@ static void append_dir_subtitles(struct mpv_global *global,
if (mp_is_url(bstr0(fname)))
goto out;
- struct bstr f_fname = bstr0(mp_basename(fname));
+ struct bstr f_fbname = bstr0(mp_basename(fname));
+ struct bstr f_fname = mp_iconv_to_utf8(log, f_fbname,
+ "UTF-8-MAC", MP_NO_LATIN1_FALLBACK);
struct bstr f_fname_noext = bstrdup(tmpmem, bstr_strip_ext(f_fname));
bstr_lower(f_fname_noext);
struct bstr f_fname_trim = bstr_strip(f_fname_noext);
+ if (f_fbname.start != f_fname.start)
+ talloc_steal(tmpmem, f_fname.start);
+
// 0 = nothing
// 1 = any subtitle file
// 2 = any sub file containing movie name
@@ -113,15 +120,19 @@ static void append_dir_subtitles(struct mpv_global *global,
mp_verbose(log, "Loading external files in %.*s\n", BSTR_P(path));
struct dirent *de;
while ((de = readdir(d))) {
- struct bstr dename = bstr0(de->d_name);
void *tmpmem2 = talloc_new(tmpmem);
-
+ struct bstr den = bstr0(de->d_name);
+ struct bstr dename = mp_iconv_to_utf8(log, den,
+ "UTF-8-MAC", MP_NO_LATIN1_FALLBACK);
// retrieve various parts of the filename
struct bstr tmp_fname_noext = bstrdup(tmpmem2, bstr_strip_ext(dename));
bstr_lower(tmp_fname_noext);
struct bstr tmp_fname_ext = bstr_get_ext(dename);
struct bstr tmp_fname_trim = bstr_strip(tmp_fname_noext);
+ if (den.start != dename.start)
+ talloc_steal(tmpmem2, dename.start);
+
// check what it is (most likely)
int type = test_ext(tmp_fname_ext);
char **langs = NULL;
diff --git a/player/lavfi.c b/player/lavfi.c
index 50c2fd8fc2..2a41aa8a96 100644
--- a/player/lavfi.c
+++ b/player/lavfi.c
@@ -43,6 +43,11 @@
#include "lavfi.h"
+#if LIBAVFILTER_VERSION_MICRO < 100
+#define av_buffersink_get_frame_flags(a, b, c) av_buffersink_get_frame(a, b)
+#define AV_BUFFERSINK_FLAG_NO_REQUEST 0
+#endif
+
struct lavfi {
struct mp_log *log;
char *graph_string;
@@ -266,6 +271,10 @@ enum stream_type lavfi_pad_type(struct lavfi_pad *pad)
void lavfi_set_connected(struct lavfi_pad *pad, bool connected)
{
pad->connected = connected;
+ if (!pad->connected) {
+ pad->output_needed = false;
+ drop_pad_data(pad);
+ }
}
bool lavfi_get_connected(struct lavfi_pad *pad)
@@ -545,19 +554,17 @@ static void read_output_pads(struct lavfi *c)
if (pad->dir != LAVFI_OUT)
continue;
- // If disconnected, read and discard everything.
- if (!pad->pending_v && !pad->pending_a && !pad->connected)
- pad->output_needed = true;
-
- if (!pad->output_needed)
+ // If disconnected, read and discard everything (passively).
+ if (pad->connected && !pad->output_needed)
continue;
assert(pad->buffer);
assert(!pad->pending_v && !pad->pending_a);
+ int flags = pad->output_needed ? 0 : AV_BUFFERSINK_FLAG_NO_REQUEST;
int r = AVERROR_EOF;
if (!pad->buffer_is_eof)
- r = av_buffersink_get_frame(pad->buffer, pad->tmp_frame);
+ r = av_buffersink_get_frame_flags(pad->buffer, pad->tmp_frame, flags);
if (r >= 0) {
pad->output_needed = false;
double pts = mp_pts_from_av(pad->tmp_frame->pts, &pad->timebase);
@@ -583,6 +590,7 @@ static void read_output_pads(struct lavfi *c)
// input pads (via av_buffersrc_get_nb_failed_requests()).
pad->output_eof = false;
} else if (r == AVERROR_EOF) {
+ pad->output_needed = false;
pad->buffer_is_eof = true;
if (!c->draining_recover_eof && !c->draining_new_format)
pad->output_eof = true;
@@ -611,7 +619,7 @@ bool lavfi_process(struct lavfi *c)
bool all_waiting = true;
bool any_needs_input = false;
bool any_needs_output = false;
- bool all_lavfi_eof = true;
+ bool all_output_eof = true;
bool all_input_eof = true;
// Determine the graph state
@@ -623,12 +631,12 @@ bool lavfi_process(struct lavfi *c)
any_needs_input |= pad->input_needed;
all_input_eof &= pad->input_eof;
} else if (pad->dir == LAVFI_OUT) {
- all_lavfi_eof &= pad->buffer_is_eof;
+ all_output_eof &= pad->buffer_is_eof;
any_needs_output |= pad->output_needed;
}
}
- if (all_lavfi_eof && !all_input_eof) {
+ if (all_output_eof && !all_input_eof) {
free_graph(c);
precreate_graph(c);
all_waiting = false;
diff --git a/player/loadfile.c b/player/loadfile.c
index b94ce8af43..dba02ee828 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -40,6 +40,7 @@
#include "options/m_property.h"
#include "common/common.h"
#include "common/encode.h"
+#include "common/recorder.h"
#include "input/input.h"
#include "audio/audio.h"
@@ -57,6 +58,17 @@
#include "command.h"
#include "libmpv/client.h"
+// Called by foreign threads when playback should be stopped and such.
+void mp_abort_playback_async(struct MPContext *mpctx)
+{
+ mp_cancel_trigger(mpctx->playback_abort);
+
+ pthread_mutex_lock(&mpctx->lock);
+ if (mpctx->demuxer_cancel)
+ mp_cancel_trigger(mpctx->demuxer_cancel);
+ pthread_mutex_unlock(&mpctx->lock);
+}
+
static void uninit_demuxer(struct MPContext *mpctx)
{
for (int r = 0; r < NUM_PTRACKS; r++) {
@@ -80,6 +92,11 @@ static void uninit_demuxer(struct MPContext *mpctx)
free_demuxer_and_stream(mpctx->demuxer);
mpctx->demuxer = NULL;
+
+ pthread_mutex_lock(&mpctx->lock);
+ talloc_free(mpctx->demuxer_cancel);
+ mpctx->demuxer_cancel = NULL;
+ pthread_mutex_unlock(&mpctx->lock);
}
#define APPEND(s, ...) mp_snprintf_cat(s, sizeof(s), __VA_ARGS__)
@@ -453,6 +470,8 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
uninit_sub(mpctx, current);
if (current) {
+ if (current->remux_sink)
+ close_recorder_and_error(mpctx);
current->selected = false;
reselect_demux_stream(mpctx, current);
}
@@ -774,57 +793,144 @@ static void load_per_file_options(m_config_t *conf,
}
}
-struct demux_open_args {
- int stream_flags;
- char *url;
- struct mpv_global *global;
- struct mp_cancel *cancel;
- struct mp_log *log;
- // results
- struct demuxer *demux;
- int err;
-};
-
-static void open_demux_thread(void *pctx)
+static void *open_demux_thread(void *ctx)
{
- struct demux_open_args *args = pctx;
- struct mpv_global *global = args->global;
+ struct MPContext *mpctx = ctx;
+
struct demuxer_params p = {
- .force_format = global->opts->demuxer_name,
- .allow_capture = true,
- .stream_flags = args->stream_flags,
+ .force_format = mpctx->open_format,
+ .stream_flags = mpctx->open_url_flags,
+ .initial_readahead = true,
};
- args->demux = demux_open_url(args->url, &p, args->cancel, global);
- if (!args->demux) {
+ mpctx->open_res_demuxer =
+ demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global);
+
+ if (mpctx->open_res_demuxer) {
+ MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url);
+ } else {
+ MP_VERBOSE(mpctx, "Opening failed or was aborted: %s\n", mpctx->open_url);
+
if (p.demuxer_failed) {
- args->err = MPV_ERROR_UNKNOWN_FORMAT;
+ mpctx->open_res_error = MPV_ERROR_UNKNOWN_FORMAT;
} else {
- args->err = MPV_ERROR_LOADING_FAILED;
+ mpctx->open_res_error = MPV_ERROR_LOADING_FAILED;
}
}
- if (args->demux && global->opts->rebase_start_time)
- demux_set_ts_offset(args->demux, -args->demux->start_time);
+
+ atomic_store(&mpctx->open_done, true);
+ mp_wakeup_core(mpctx);
+ return NULL;
}
-static void open_demux_reentrant(struct MPContext *mpctx)
+static void cancel_open(struct MPContext *mpctx)
{
- struct demux_open_args args = {
- .global = mpctx->global,
- .cancel = mpctx->playback_abort,
- .log = mpctx->log,
- .stream_flags = mpctx->playing->stream_flags,
- .url = talloc_strdup(NULL, mpctx->stream_open_filename),
- };
+ if (mpctx->open_cancel)
+ mp_cancel_trigger(mpctx->open_cancel);
+
+ if (mpctx->open_active)
+ pthread_join(mpctx->open_thread, NULL);
+ mpctx->open_active = false;
+
+ TA_FREEP(&mpctx->open_cancel);
+ TA_FREEP(&mpctx->open_url);
+ TA_FREEP(&mpctx->open_format);
+
+ if (mpctx->open_res_demuxer)
+ free_demuxer_and_stream(mpctx->open_res_demuxer);
+ mpctx->open_res_demuxer = NULL;
+
+ atomic_store(&mpctx->open_done, false);
+}
+
+// Setup all the field to open this url, and make sure a thread is running.
+static void start_open(struct MPContext *mpctx, char *url, int url_flags)
+{
+ cancel_open(mpctx);
+
+ assert(!mpctx->open_active);
+ assert(!mpctx->open_cancel);
+ assert(!mpctx->open_res_demuxer);
+ assert(!atomic_load(&mpctx->open_done));
+
+ mpctx->open_cancel = mp_cancel_new(NULL);
+ mpctx->open_url = talloc_strdup(NULL, url);
+ mpctx->open_format = talloc_strdup(NULL, mpctx->opts->demuxer_name);
+ mpctx->open_url_flags = url_flags;
if (mpctx->opts->load_unsafe_playlists)
- args.stream_flags = 0;
- mpctx_run_reentrant(mpctx, open_demux_thread, &args);
- if (args.demux) {
- mpctx->demuxer = args.demux;
- enable_demux_thread(mpctx, mpctx->demuxer);
+ mpctx->open_url_flags = 0;
+
+ if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) {
+ cancel_open(mpctx);
+ return;
+ }
+
+ mpctx->open_active = true;
+}
+
+static void open_demux_reentrant(struct MPContext *mpctx)
+{
+ char *url = mpctx->stream_open_filename;
+
+ if (mpctx->open_active) {
+ bool done = atomic_load(&mpctx->open_done);
+ bool failed = done && !mpctx->open_res_demuxer;
+ bool correct_url = strcmp(mpctx->open_url, url) == 0;
+
+ if (correct_url && !failed) {
+ MP_VERBOSE(mpctx, "Using prefetched/prefetching URL.\n");
+ } else if (correct_url && failed) {
+ MP_VERBOSE(mpctx, "Prefetched URL failed, retrying.\n");
+ cancel_open(mpctx);
+ } else {
+ if (done) {
+ MP_VERBOSE(mpctx, "Dropping finished prefetch of wrong URL.\n");
+ } else {
+ MP_VERBOSE(mpctx, "Aborting onging prefetch of wrong URL.\n");
+ }
+ cancel_open(mpctx);
+ }
+ }
+
+ if (!mpctx->open_active)
+ start_open(mpctx, url, mpctx->playing->stream_flags);
+
+ // User abort should cancel the opener now.
+ pthread_mutex_lock(&mpctx->lock);
+ mpctx->demuxer_cancel = mpctx->open_cancel;
+ pthread_mutex_unlock(&mpctx->lock);
+
+ while (!atomic_load(&mpctx->open_done)) {
+ mp_idle(mpctx);
+
+ if (mpctx->stop_play)
+ mp_abort_playback_async(mpctx);
+ }
+
+ if (mpctx->open_res_demuxer) {
+ assert(mpctx->demuxer_cancel == mpctx->open_cancel);
+ mpctx->demuxer = mpctx->open_res_demuxer;
+ mpctx->open_res_demuxer = NULL;
+ mpctx->open_cancel = NULL;
} else {
- mpctx->error_playing = args.err;
+ mpctx->error_playing = mpctx->open_res_error;
+ pthread_mutex_lock(&mpctx->lock);
+ mpctx->demuxer_cancel = NULL;
+ pthread_mutex_unlock(&mpctx->lock);
+ }
+
+ cancel_open(mpctx); // cleanup
+}
+
+void prefetch_next(struct MPContext *mpctx)
+{
+ if (!mpctx->opts->prefetch_open)
+ return;
+
+ struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, false);
+ if (new_entry && !mpctx->open_active && new_entry->filename) {
+ MP_VERBOSE(mpctx, "Prefetching: %s\n", new_entry->filename);
+ start_open(mpctx, new_entry->filename, new_entry->stream_flags);
}
- talloc_free(args.url);
}
static bool init_complex_filters(struct MPContext *mpctx)
@@ -977,7 +1083,7 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->filename = talloc_strdup(NULL, mpctx->playing->filename);
mpctx->stream_open_filename = mpctx->filename;
- mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION | OSD_SEEK_INFO_CURRENT_FILE;
+ mpctx->add_osd_seek_info &= OSD_SEEK_INFO_CURRENT_FILE;
if (opts->reset_options) {
for (int n = 0; opts->reset_options[n]; n++) {
@@ -1038,6 +1144,10 @@ reopen_file:
goto terminate_playback;
}
+ if (mpctx->opts->rebase_start_time)
+ demux_set_ts_offset(mpctx->demuxer, -mpctx->demuxer->start_time);
+ enable_demux_thread(mpctx, mpctx->demuxer);
+
load_chapters(mpctx);
add_demuxer_tracks(mpctx, mpctx->demuxer);
@@ -1149,6 +1259,8 @@ reopen_file:
if (mpctx->opts->pause)
pause_player(mpctx);
+ open_recorder(mpctx, true);
+
playback_start = mp_time_sec();
mpctx->error_playing = 0;
while (!mpctx->stop_play)
@@ -1169,7 +1281,9 @@ terminate_playback:
if (mpctx->step_frames)
opts->pause = 1;
- mp_cancel_trigger(mpctx->playback_abort);
+ mp_abort_playback_async(mpctx);
+
+ close_recorder(mpctx);
// time to uninit all, except global stuff:
uninit_complex_filters(mpctx);
@@ -1255,8 +1369,9 @@ terminate_playback:
// it can have side-effects and mutate mpctx.
// direction: -1 (previous) or +1 (next)
// force: if true, don't skip playlist entries marked as failed
+// mutate: if true, change loop counters
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
- bool force)
+ bool force, bool mutate)
{
struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction);
if (next && direction < 0 && !force) {
@@ -1316,7 +1431,7 @@ void mp_play_files(struct MPContext *mpctx)
if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR ||
mpctx->stop_play == AT_END_OF_FILE || !mpctx->stop_play)
{
- new_entry = mp_next_file(mpctx, +1, false);
+ new_entry = mp_next_file(mpctx, +1, false, true);
}
mpctx->playlist->current = new_entry;
@@ -1326,6 +1441,8 @@ void mp_play_files(struct MPContext *mpctx)
if (!mpctx->playlist->current && mpctx->opts->player_idle_mode < 2)
break;
}
+
+ cancel_open(mpctx);
}
// Abort current playback and set the given entry to play next.
@@ -1339,3 +1456,89 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
mpctx->stop_play = PT_CURRENT_ENTRY;
mp_wakeup_core(mpctx);
}
+
+static void set_track_recorder_sink(struct track *track,
+ struct mp_recorder_sink *sink)
+{
+ if (track->d_sub)
+ sub_set_recorder_sink(track->d_sub, sink);
+ if (track->d_video)
+ track->d_video->recorder_sink = sink;
+ if (track->d_audio)
+ track->d_audio->recorder_sink = sink;
+ track->remux_sink = sink;
+}
+
+void close_recorder(struct MPContext *mpctx)
+{
+ if (!mpctx->recorder)
+ return;
+
+ for (int n = 0; n < mpctx->num_tracks; n++)
+ set_track_recorder_sink(mpctx->tracks[n], NULL);
+
+ mp_recorder_destroy(mpctx->recorder);
+ mpctx->recorder = NULL;
+}
+
+// Like close_recorder(), but also unset the option. Intended for use on errors.
+void close_recorder_and_error(struct MPContext *mpctx)
+{
+ close_recorder(mpctx);
+ talloc_free(mpctx->opts->record_file);
+ mpctx->opts->record_file = NULL;
+ mp_notify_property(mpctx, "record-file");
+ MP_ERR(mpctx, "Disabling stream recording.\n");
+}
+
+void open_recorder(struct MPContext *mpctx, bool on_init)
+{
+ if (!mpctx->playback_initialized)
+ return;
+
+ close_recorder(mpctx);
+
+ char *target = mpctx->opts->record_file;
+ if (!target || !target[0])
+ return;
+
+ struct sh_stream **streams = NULL;
+ int num_streams = 0;
+
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->stream && track->selected &&
+ (track->d_sub || track->d_video || track->d_audio))
+ {
+ MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream);
+ }
+ }
+
+ mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
+ streams, num_streams);
+
+ if (!mpctx->recorder) {
+ talloc_free(streams);
+ close_recorder_and_error(mpctx);
+ return;
+ }
+
+ if (!on_init)
+ mp_recorder_mark_discontinuity(mpctx->recorder);
+
+ int n_stream = 0;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (n_stream >= num_streams)
+ break;
+ // (We expect track->stream not to be reused on other tracks.)
+ if (track->stream == streams[n_stream]) {
+ set_track_recorder_sink(track,
+ mp_recorder_get_sink(mpctx->recorder, n_stream));
+ n_stream++;
+ }
+ }
+
+ talloc_free(streams);
+}
+
diff --git a/player/lua.c b/player/lua.c
index e76dc46fca..62f42baea1 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -1079,6 +1079,7 @@ static int script_readdir(lua_State *L)
lua_pushstring(L, name); // list index name
lua_settable(L, -3); // list
}
+ closedir(dir);
talloc_free(fullpath);
return 1;
}
@@ -1331,6 +1332,7 @@ static void add_functions(struct script_ctx *ctx)
}
const struct mp_scripting mp_scripting_lua = {
+ .name = "lua script",
.file_ext = "lua",
.load = load_lua,
};
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index 08616c5638..6669cf38a4 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -419,6 +419,16 @@ function mp.register_idle(cb)
idle_handlers[#idle_handlers + 1] = cb
end
+function mp.unregister_idle(cb)
+ local new = {}
+ for _, handler in ipairs(idle_handlers) do
+ if handler ~= cb then
+ new[#new + 1] = handler
+ end
+ end
+ idle_handlers = new
+end
+
-- sent by "script-binding"
mp.register_script_message("key-binding", dispatch_key_binding)
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index 4ad6929800..160457458c 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -459,20 +459,19 @@ function prepare_elements()
elem_geo.h - slider_lo.border)
end
- else -- draw 1px nibbles
+ else -- draw 2x1px nibbles
--top
if (slider_lo.nibbles_top) then
- static_ass:rect_cw(s - 0.5, slider_lo.gap,
- s + 0.5, slider_lo.gap*2);
+ static_ass:rect_cw(s - 1, slider_lo.border,
+ s + 1, slider_lo.border + slider_lo.gap);
end
--bottom
if (slider_lo.nibbles_bottom) then
- static_ass:rect_cw(s - 0.5,
- elem_geo.h - slider_lo.gap*2,
- s + 0.5,
- elem_geo.h - slider_lo.gap);
+ static_ass:rect_cw(s - 1,
+ elem_geo.h -slider_lo.border -slider_lo.gap,
+ s + 1, elem_geo.h - slider_lo.border);
end
end
end
@@ -548,14 +547,12 @@ function render_elements(master_ass)
elem_ass:merge(element.static_ass)
end
-
-
if (element.type == "slider") then
local slider_lo = element.layout.slider
local elem_geo = element.layout.geometry
- local s_min, s_max = element.slider.min.value, element.slider.max.value
-
+ local s_min = element.slider.min.value
+ local s_max = element.slider.max.value
-- draw pos marker
local pos = element.slider.posF()
@@ -568,7 +565,6 @@ function render_elements(master_ass)
(slider_lo.stype == "knob") then
foH = elem_geo.h / 2
elseif (slider_lo.stype == "bar") then
- foV = foV + 1
foH = slider_lo.border + slider_lo.gap
end
@@ -585,10 +581,12 @@ function render_elements(master_ass)
elem_ass:line_to(xp, (innerH)+foV)
elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV)
elseif (slider_lo.stype == "knob") then
- elem_ass:rect_cw(xp, (9*innerH/20)+foV, elem_geo.w - foH, (11*innerH/20)+foV)
- elem_ass:rect_cw(foH, (3*innerH/8)+foV, xp, (5*innerH/8)+foV)
- elem_ass:round_rect_cw(xp - innerH/2, foV, xp + innerH/2,
- foV + innerH, innerH/2.0)
+ elem_ass:rect_cw(xp, (9*innerH/20) + foV,
+ elem_geo.w - foH, (11*innerH/20) + foV)
+ elem_ass:rect_cw(foH, (3*innerH/8) + foV,
+ xp, (5*innerH/8) + foV)
+ elem_ass:round_rect_cw(xp - innerH/2, foV,
+ xp + innerH/2, foV + innerH, innerH/2.0)
end
end
@@ -660,8 +658,15 @@ function render_elements(master_ass)
end
local maxchars = element.layout.button.maxchars
-
if not (maxchars == nil) and (#buttontext > maxchars) then
+ if (#buttontext > maxchars+20) then
+ while (#buttontext > maxchars+20) do
+ buttontext = buttontext:gsub(".[\128-\191]*$", "")
+ end
+ buttontext = buttontext .. "..."
+ end
+ local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
+ local stretch = (maxchars/#buttontext)*100
buttontext = string.format("{\\fscx%f}",
(maxchars/#buttontext)*100) .. buttontext
end
@@ -946,7 +951,7 @@ layouts["box"] = function ()
lo = add_layout("title")
lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
lo.style = osc_styles.vidtitle
- lo.button.maxchars = 90
+ lo.button.maxchars = 80
lo = add_layout("pl_prev")
lo.geometry =
@@ -1226,8 +1231,9 @@ layouts["bottombar"] = function()
w = t_r - t_l, h = geo.h }
lo = add_layout("title")
lo.geometry = geo
- lo.style = osc_styles.vidtitleBar
- lo.button.maxchars = math.floor(geo.w/7)
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.vidtitleBar,
+ geo.x, geo.y-geo.h/2, geo.w, geo.y+geo.h/2)
-- Playback control buttons
@@ -1296,6 +1302,7 @@ layouts["bottombar"] = function()
lo.geometry = geo
lo.style = osc_styles.timecodes
lo.slider.border = 0
+ lo.slider.gap = 2
lo.slider.tooltip_style = osc_styles.timePosBar
lo.slider.tooltip_an = 5
lo.slider.stype = user_opts["seekbarstyle"]
@@ -1403,7 +1410,8 @@ layouts["topbar"] = function()
-- Seekbar
- geo = { x = sb_l, y = user_opts.barmargin, an = 7, w = math.max(0, sb_r - sb_l), h = geo.h }
+ geo = { x = sb_l, y = user_opts.barmargin, an = 7,
+ w = math.max(0, sb_r - sb_l), h = geo.h }
new_element("bgbar1", "box")
lo = add_layout("bgbar1")
@@ -1417,6 +1425,7 @@ layouts["topbar"] = function()
lo.geometry = geo
lo.style = osc_styles.timecodesBar
lo.slider.border = 0
+ lo.slider.gap = 2
lo.slider.tooltip_style = osc_styles.timePosBar
lo.slider.stype = user_opts["seekbarstyle"]
lo.slider.tooltip_an = 5
@@ -1449,8 +1458,9 @@ layouts["topbar"] = function()
w = t_r - t_l, h = geo.h }
lo = add_layout("title")
lo.geometry = geo
- lo.style = osc_styles.vidtitleBar
- lo.button.maxchars = math.floor(geo.w/7)
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.vidtitleBar,
+ geo.x, geo.y-geo.h/2, geo.w, geo.y+geo.h/2)
end
-- Validate string type user options
@@ -1506,6 +1516,7 @@ function osc_init()
local have_pl = (pl_count > 1)
local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
local have_ch = (mp.get_property_number("chapters", 0) > 0)
+ local loop = mp.get_property("loop", "no")
local ne
@@ -1539,7 +1550,7 @@ function osc_init()
ne = new_element("pl_prev", "button")
ne.content = "\238\132\144"
- ne.enabled = (pl_pos > 1)
+ ne.enabled = (pl_pos > 1) or (loop ~= "no")
ne.eventresponder["mouse_btn0_up"] =
function ()
mp.commandv("playlist-prev", "weak")
@@ -1554,7 +1565,7 @@ function osc_init()
ne = new_element("pl_next", "button")
ne.content = "\238\132\129"
- ne.enabled = (have_pl) and (pl_pos < pl_count)
+ ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
ne.eventresponder["mouse_btn0_up"] =
function ()
mp.commandv("playlist-next", "weak")
@@ -2071,6 +2082,7 @@ function process_event(source, what)
if n == 0 then
--click on background (does not work)
elseif n > 0 and not (n > #elements) and
+ not (elements[n].eventresponder == nil) and
not (elements[n].eventresponder[source .. "_" .. what] == nil) then
if mouse_hit(elements[n]) then
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 1bae4f398c..d53a2ae4c1 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -88,6 +88,42 @@ local function extract_chapters(data, video_length)
return ret
end
+local function edl_track_joined(fragments, protocol, is_live)
+ if not (type(fragments) == "table") or not fragments[1] then
+ msg.debug("No fragments to join into EDL")
+ return nil
+ end
+
+ local edl = "edl://"
+ local offset = 1
+
+ if (protocol == "http_dash_segments") and
+ not fragments[1].duration and not is_live then
+ -- assume MP4 DASH initialization segment
+ edl = edl .. "!mp4_dash,init=" .. edl_escape(fragments[1].url) .. ";"
+ offset = 2
+
+ -- Check remaining fragments for duration;
+ -- if not available in all, give up.
+ for i = offset, #fragments do
+ if not fragments[i].duration then
+ msg.error("EDL doesn't support fragments" ..
+ "without duration with MP4 DASH")
+ return nil
+ end
+ end
+ end
+
+ for i = offset, #fragments do
+ local fragment = fragments[i]
+ edl = edl .. edl_escape(fragment.url)
+ if fragment.duration then
+ edl = edl..",length="..fragment.duration
+ end
+ edl = edl .. ";"
+ end
+ return edl
+end
mp.add_hook("on_load", 10, function ()
local url = mp.get_property("stream-open-filename")
@@ -124,15 +160,15 @@ mp.add_hook("on_load", 10, function ()
if (mp.get_property("options/vid") == "no")
and not option_was_set("ytdl-format") then
- format = "bestaudio"
+ format = "bestaudio/best"
msg.verbose("Video disabled. Only using audio")
end
if (format == "") then
- format = "bestvideo+bestaudio"
+ format = "bestvideo+bestaudio/best"
end
table.insert(command, "--format")
- table.insert(command, string.format('(%s)[protocol!=http_dash_segments]/best', format))
+ table.insert(command, format)
for param, arg in pairs(raw_options) do
table.insert(command, "--" .. param)
@@ -190,14 +226,7 @@ mp.add_hook("on_load", 10, function ()
and (json.entries[1]["webpage_url"] == json["webpage_url"])) then
msg.verbose("multi-arc video detected, building EDL")
- local playlist = "edl://"
- for i, entry in pairs(json.entries) do
- playlist = playlist .. edl_escape(entry.url)
- if not (entry.duration == nil) then
- playlist = playlist..",start=0,length="..entry.duration
- end
- playlist = playlist .. ";"
- end
+ local playlist = edl_track_joined(json.entries)
msg.debug("EDL: " .. playlist)
@@ -232,7 +261,7 @@ mp.add_hook("on_load", 10, function ()
else
subfile = subfile..edl_escape("memory://WEBVTT")
end
- subfile = subfile..",start=0,length="..entry.duration..";"
+ subfile = subfile..",length="..entry.duration..";"
end
msg.debug(j.." sub EDL: "..subfile)
mp.commandv("sub-add", subfile, "auto", req.ext, j)
@@ -270,23 +299,30 @@ mp.add_hook("on_load", 10, function ()
else -- probably a video
local streamurl = ""
- -- DASH?
+ -- DASH/split tracks
if not (json["requested_formats"] == nil) then
- if (json["requested_formats"][1].protocol == "http_dash_segments") then
- msg.error("MPEG-Dash Segments unsupported, add [protocol!=http_dash_segments] to your ytdl-format.")
- return
+ for _, track in pairs(json.requested_formats) do
+ local edl_track = nil
+ edl_track = edl_track_joined(track.fragments,
+ track.protocol, json.is_live)
+ if track.acodec and track.acodec ~= "none" then
+ -- audio track
+ mp.commandv("audio-add",
+ edl_track or track.url, "auto",
+ track.format_note or "")
+ elseif track.vcodec and track.vcodec ~= "none" then
+ -- video track
+ streamurl = edl_track or track.url
+ end
end
- -- video url
- streamurl = json["requested_formats"][1].url
-
- -- audio url
- mp.commandv("audio-add", json["requested_formats"][2].url,
- "select", json["requested_formats"][2]["format_note"] or "")
-
elseif not (json.url == nil) then
- -- normal video
- streamurl = json.url
+ local edl_track = nil
+ edl_track = edl_track_joined(json.fragments, json.protocol,
+ json.is_live)
+
+ -- normal video or single track
+ streamurl = edl_track or json.url
set_http_headers(json.http_headers)
else
msg.error("No URL found in JSON data.")
@@ -295,7 +331,7 @@ mp.add_hook("on_load", 10, function ()
msg.debug("streamurl: " .. streamurl)
- mp.set_property("stream-open-filename", streamurl)
+ mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))
mp.set_property("file-local-options/force-media-title", json.title)
@@ -349,6 +385,8 @@ mp.add_hook("on_load", 10, function ()
rtmp_prop = append_rtmp_prop(rtmp_prop,
"rtmp_swfverify", json.player_url)
rtmp_prop = append_rtmp_prop(rtmp_prop,
+ "rtmp_swfurl", json.player_url)
+ rtmp_prop = append_rtmp_prop(rtmp_prop,
"rtmp_app", json.app)
mp.set_property("file-local-options/stream-lavf-o", rtmp_prop)
diff --git a/player/main.c b/player/main.c
index 8ebfc357a2..87ec1ab70b 100644
--- a/player/main.c
+++ b/player/main.c
@@ -136,7 +136,7 @@ void mp_print_version(struct mp_log *log, int always)
{
int v = always ? MSGL_INFO : MSGL_V;
mp_msg(log, v,
- "%s (C) 2000-2016 mpv/MPlayer/mplayer2 projects\n built on %s\n",
+ "%s (C) 2000-2017 mpv/MPlayer/mplayer2 projects\n built on %s\n",
mpv_version, mpv_builddate);
print_libav_versions(log, v);
mp_msg(log, v, "\n");
@@ -199,6 +199,7 @@ void mp_destroy(struct MPContext *mpctx)
pthread_detach(pthread_self());
mp_msg_uninit(mpctx->global);
+ pthread_mutex_destroy(&mpctx->lock);
talloc_free(mpctx);
}
@@ -311,6 +312,12 @@ static int cfg_include(void *ctx, char *filename, int flags)
return r;
}
+static void abort_playback_cb(void *ctx)
+{
+ struct MPContext *mpctx = ctx;
+ mp_abort_playback_async(mpctx);
+}
+
struct MPContext *mp_create(void)
{
char *enable_talloc = getenv("MPV_LEAK_REPORT");
@@ -329,6 +336,8 @@ struct MPContext *mp_create(void)
.playback_abort = mp_cancel_new(mpctx),
};
+ pthread_mutex_init(&mpctx->lock, NULL);
+
mpctx->global = talloc_zero(mpctx, struct mpv_global);
// Nothing must call mp_msg*() and related before this
@@ -361,7 +370,7 @@ struct MPContext *mp_create(void)
cocoa_set_input_context(mpctx->input);
#endif
- mp_input_set_cancel(mpctx->input, mpctx->playback_abort);
+ mp_input_set_cancel(mpctx->input, abort_playback_cb, mpctx);
char *verbose_env = getenv("MPV_VERBOSE");
if (verbose_env)
diff --git a/player/misc.c b/player/misc.c
index eb4c1c031e..6762f8a518 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -17,7 +17,7 @@
#include <stddef.h>
#include <stdbool.h>
-#include <pthread.h>
+#include <errno.h>
#include <assert.h>
#include "config.h"
@@ -213,21 +213,34 @@ int stream_dump(struct MPContext *mpctx, const char *source_filename)
int64_t size = stream_get_size(stream);
- stream_set_capture_file(stream, opts->stream_dump);
+ FILE *dest = fopen(opts->stream_dump, "wb");
+ if (!dest) {
+ MP_ERR(mpctx, "Error opening dump file: %s\n", mp_strerror(errno));
+ return -1;
+ }
- while (mpctx->stop_play == KEEP_PLAYING && !stream->eof) {
+ bool ok = true;
+
+ while (mpctx->stop_play == KEEP_PLAYING && ok) {
if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
uint64_t pos = stream->pos;
MP_MSG(mpctx, MSGL_STATUS, "Dumping %lld/%lld...",
(long long int)pos, (long long int)size);
}
- stream_fill_buffer(stream);
+ bstr data = stream_peek(stream, STREAM_MAX_BUFFER_SIZE);
+ if (data.len == 0) {
+ ok &= stream->eof;
+ break;
+ }
+ ok &= fwrite(data.start, data.len, 1, dest) == 1;
+ stream_skip(stream, data.len);
mp_wakeup_core(mpctx); // don't actually sleep
mp_idle(mpctx); // but process input
}
+ ok &= fclose(dest) == 0;
free_stream(stream);
- return 0;
+ return ok ? 0 : -1;
}
void merge_playlist_files(struct playlist *pl)
@@ -251,51 +264,3 @@ void merge_playlist_files(struct playlist *pl)
playlist_add_file(pl, edl);
talloc_free(edl);
}
-
-struct wrapper_args {
- struct MPContext *mpctx;
- void (*thread_fn)(void *);
- void *thread_arg;
- pthread_mutex_t mutex;
- bool done;
-};
-
-static void *thread_wrapper(void *pctx)
-{
- struct wrapper_args *args = pctx;
- mpthread_set_name("opener");
- args->thread_fn(args->thread_arg);
- pthread_mutex_lock(&args->mutex);
- args->done = true;
- pthread_mutex_unlock(&args->mutex);
- mp_wakeup_core(args->mpctx); // this interrupts mp_idle()
- return NULL;
-}
-
-// Run the thread_fn in a new thread. Wait until the thread returns, but while
-// waiting, process input and input commands.
-int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
- void *thread_arg)
-{
- struct wrapper_args args = {mpctx, thread_fn, thread_arg};
- pthread_mutex_init(&args.mutex, NULL);
- bool success = false;
- pthread_t thread;
- if (pthread_create(&thread, NULL, thread_wrapper, &args))
- goto done;
- while (!success) {
- mp_idle(mpctx);
-
- if (mpctx->stop_play)
- mp_cancel_trigger(mpctx->playback_abort);
-
- pthread_mutex_lock(&args.mutex);
- success |= args.done;
- pthread_mutex_unlock(&args.mutex);
- }
- pthread_join(thread, NULL);
-done:
- pthread_mutex_destroy(&args.mutex);
- mp_wakeup_core(mpctx); // avoid lost wakeups during waiting
- return success ? 0 : -1;
-}
diff --git a/player/osd.c b/player/osd.c
index 4dbdfe4024..75f4cdcf7b 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -262,7 +262,7 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
if (mpctx->demuxer) {
struct stream_cache_info info = {0};
demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info);
- if (info.size > 0) {
+ if (info.size > 0 || mpctx->demuxer->is_network) {
saddf(&line, " Cache: ");
struct demux_ctrl_reader_state s = {.ts_duration = -1};
@@ -273,10 +273,12 @@ static void term_osd_print_status_lazy(struct MPContext *mpctx)
} else {
saddf(&line, "%2ds", (int)s.ts_duration);
}
- if (info.fill >= 1024 * 1024) {
- saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
- } else {
- saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
+ if (info.size > 0) {
+ if (info.fill >= 1024 * 1024) {
+ saddf(&line, "+%lldMB", (long long)(info.fill / 1024 / 1024));
+ } else {
+ saddf(&line, "+%lldKB", (long long)(info.fill / 1024));
+ }
}
}
}
@@ -474,12 +476,6 @@ static void add_seek_osd_messages(struct MPContext *mpctx)
"Chapter: %s", chapter);
talloc_free(chapter);
}
- if ((mpctx->add_osd_seek_info & OSD_SEEK_INFO_EDITION) && mpctx->demuxer) {
- set_osd_msg(mpctx, 1, mpctx->opts->osd_duration,
- "Playing edition %d of %d.",
- mpctx->demuxer->edition + 1,
- mpctx->demuxer->num_editions);
- }
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_CURRENT_FILE) {
if (mpctx->filename) {
set_osd_msg(mpctx, 1, mpctx->opts->osd_duration, "%s",
diff --git a/player/playloop.c b/player/playloop.c
index 9db9396f95..e73ad61788 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -28,6 +28,7 @@
#include "options/options.h"
#include "common/common.h"
#include "common/encode.h"
+#include "common/recorder.h"
#include "options/m_config.h"
#include "options/m_property.h"
#include "common/playlist.h"
@@ -330,6 +331,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
clear_audio_output_buffers(mpctx);
reset_playback_state(mpctx);
+ if (mpctx->recorder)
+ mp_recorder_mark_discontinuity(mpctx->recorder);
/* Use the target time as "current position" for further relative
* seeks etc until a new video frame has been decoded */
@@ -663,6 +666,9 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
force_update = true;
}
+ if (s.eof && !busy)
+ prefetch_next(mpctx);
+
if (force_update)
mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL);
}
diff --git a/player/screenshot.c b/player/screenshot.c
index 13532ec1a3..4c043ab986 100644
--- a/player/screenshot.c
+++ b/player/screenshot.c
@@ -31,6 +31,7 @@
#include "common/msg.h"
#include "options/path.h"
#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
#include "video/decode/dec_video.h"
#include "video/out/vo.h"
#include "video/image_writer.h"
@@ -346,12 +347,15 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
}
}
- if (image && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
- struct mp_hwdec_ctx *ctx =
- hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
- struct mp_image *nimage = NULL;
- if (ctx && ctx->download_image && (image->fmt.flags & MP_IMGFLAG_HWACCEL))
- nimage = ctx->download_image(ctx, image, NULL);
+ bool hwimage = image && (image->fmt.flags & MP_IMGFLAG_HWACCEL);
+ if (hwimage) {
+ struct mp_image *nimage = mp_image_hw_download(image, NULL);
+ if (!nimage && mpctx->vo_chain && mpctx->vo_chain->hwdec_devs) {
+ struct mp_hwdec_ctx *ctx =
+ hwdec_devices_get_first(mpctx->vo_chain->hwdec_devs);
+ if (ctx && ctx->download_image && hwimage)
+ nimage = ctx->download_image(ctx, image, NULL);
+ }
if (nimage) {
talloc_free(image);
image = nimage;
diff --git a/player/scripting.c b/player/scripting.c
index 4b92f7bf1b..092404231d 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -37,11 +37,15 @@
#include "libmpv/client.h"
extern const struct mp_scripting mp_scripting_lua;
+extern const struct mp_scripting mp_scripting_cplugin;
static const struct mp_scripting *const scripting_backends[] = {
#if HAVE_LUA
&mp_scripting_lua,
#endif
+#if HAVE_CPLUGINS
+ &mp_scripting_cplugin,
+#endif
NULL
};
@@ -80,13 +84,12 @@ static void *script_thread(void *p)
struct thread_arg *arg = p;
char name[90];
- snprintf(name, sizeof(name), "lua (%s)", mpv_client_name(arg->client));
+ snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
+ mpv_client_name(arg->client));
mpthread_set_name(name);
if (arg->backend->load(arg->client, arg->fname) < 0)
- MP_ERR(arg, "Could not load script %s\n", arg->fname);
-
- MP_VERBOSE(arg, "Exiting...\n");
+ MP_ERR(arg, "Could not load %s %s\n", arg->backend->name, arg->fname);
mpv_detach_destroy(arg->client);
talloc_free(arg);
@@ -133,7 +136,7 @@ int mp_load_script(struct MPContext *mpctx, const char *fname)
}
arg->log = mp_client_get_log(arg->client);
- MP_VERBOSE(arg, "Loading script %s...\n", fname);
+ MP_VERBOSE(arg, "Loading %s %s...\n", backend->name, fname);
pthread_t thread;
if (pthread_create(&thread, NULL, script_thread, arg)) {
@@ -228,3 +231,34 @@ void mp_load_scripts(struct MPContext *mpctx)
}
talloc_free(tmp);
}
+
+#if HAVE_CPLUGINS
+
+#include <dlfcn.h>
+
+#define MPV_DLOPEN_FN "mpv_open_cplugin"
+typedef int (*mpv_open_cplugin)(mpv_handle *handle);
+
+static int load_cplugin(struct mpv_handle *client, const char *fname)
+{
+ int r = -1;
+ void *lib = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
+ if (!lib)
+ goto error;
+ // Note: once loaded, we never unload, as unloading the libraries linked to
+ // the plugin can cause random serious problems.
+ mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);
+ if (!sym)
+ goto error;
+ r = sym(client) ? -1 : 0;
+error:
+ return r;
+}
+
+const struct mp_scripting mp_scripting_cplugin = {
+ .name = "SO plugin",
+ .file_ext = "so",
+ .load = load_cplugin,
+};
+
+#endif
diff --git a/player/video.c b/player/video.c
index 4621d19cb4..6ea3e4c6e8 100644
--- a/player/video.c
+++ b/player/video.c
@@ -328,6 +328,10 @@ static void vo_chain_reset_state(struct vo_chain *vo_c)
if (vo_c->video_src)
video_reset(vo_c->video_src);
+
+ // Prepare for continued playback after a seek.
+ if (!vo_c->input_mpi && vo_c->cached_coverart)
+ vo_c->input_mpi = mp_image_new_ref(vo_c->cached_coverart);
}
void reset_video_state(struct MPContext *mpctx)
@@ -381,6 +385,7 @@ static void vo_chain_uninit(struct vo_chain *vo_c)
lavfi_set_connected(vo_c->filter_src, false);
mp_image_unrefp(&vo_c->input_mpi);
+ mp_image_unrefp(&vo_c->cached_coverart);
vf_destroy(vo_c->vf);
talloc_free(vo_c);
// this does not free the VO
@@ -682,13 +687,25 @@ static int video_decode_and_filter(struct MPContext *mpctx)
return r;
if (!vo_c->input_mpi) {
- // Decode a new image, or at least feed the decoder a packet.
- r = decode_image(mpctx);
- if (r == VD_WAIT)
- return r;
+ if (vo_c->cached_coverart) {
+ // Don't ever decode it twice, not even after seek resets.
+ // (On seek resets, input_mpi is set to the cached image.)
+ r = VD_EOF;
+ } else {
+ // Decode a new image, or at least feed the decoder a packet.
+ r = decode_image(mpctx);
+ if (r == VD_WAIT)
+ return r;
+ }
}
- if (vo_c->input_mpi)
+
+ if (vo_c->input_mpi) {
vo_c->input_format = vo_c->input_mpi->params;
+ vf_set_proto_frame(vo_c->vf, vo_c->input_mpi);
+
+ if (vo_c->is_coverart && !vo_c->cached_coverart)
+ vo_c->cached_coverart = mp_image_new_ref(vo_c->input_mpi);
+ }
bool eof = !vo_c->input_mpi && (r == VD_EOF || r < 0);
r = video_filter(mpctx, eof);
@@ -1413,8 +1430,11 @@ void write_video(struct MPContext *mpctx)
mp_image_params_get_dsize(&p, &d_w, &d_h);
snprintf(extra, sizeof(extra), " => %dx%d", d_w, d_h);
}
- MP_INFO(mpctx, "VO: [%s] %dx%d%s %s\n",
- info->name, p.w, p.h, extra, vo_format_name(p.imgfmt));
+ char sfmt[20] = {0};
+ if (p.hw_subfmt)
+ snprintf(sfmt, sizeof(sfmt), "[%s]", mp_imgfmt_to_name(p.hw_subfmt));
+ MP_INFO(mpctx, "VO: [%s] %dx%d%s %s%s\n",
+ info->name, p.w, p.h, extra, mp_imgfmt_to_name(p.imgfmt), sfmt);
MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description);
int vo_r = vo_reconfig(vo, &p);
diff --git a/stream/cache.c b/stream/cache.c
index 85b78f9038..b2ff1a91c8 100644
--- a/stream/cache.c
+++ b/stream/cache.c
@@ -338,10 +338,8 @@ static int resize_cache(struct priv *s, int64_t size)
buffer_size += s->back_size;
unsigned char *buffer = malloc(buffer_size);
- if (!buffer) {
- free(buffer);
+ if (!buffer)
return STREAM_ERROR;
- }
if (s->buffer) {
// Copy & free the old ringbuffer data.
diff --git a/stream/stream.c b/stream/stream.c
index 4c7aa04844..94b9c44bc4 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -258,8 +258,6 @@ static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
assert(s->seekable == !!s->seek);
- s->uncached_type = s->type;
-
if (s->mime_type)
MP_VERBOSE(s, "Mime-type: '%s'\n", s->mime_type);
@@ -328,7 +326,7 @@ stream_t *open_output_stream(const char *filename, struct mpv_global *global)
static bool stream_reconnect(stream_t *s)
{
- if (!s->streaming || s->uncached_stream || !s->seekable || !s->cancel)
+ if (!s->streaming || s->caching || !s->seekable || !s->cancel)
return false;
int64_t pos = s->pos;
@@ -353,57 +351,25 @@ static bool stream_reconnect(stream_t *s)
return false;
}
-static void stream_capture_write(stream_t *s, void *buf, size_t len)
-{
- if (s->capture_file && len > 0) {
- if (fwrite(buf, len, 1, s->capture_file) < 1) {
- MP_ERR(s, "Error writing capture file: %s\n", mp_strerror(errno));
- stream_set_capture_file(s, NULL);
- }
- }
-}
-
-void stream_set_capture_file(stream_t *s, const char *filename)
-{
- if (!bstr_equals(bstr0(s->capture_filename), bstr0(filename))) {
- if (s->capture_file)
- fclose(s->capture_file);
- talloc_free(s->capture_filename);
- s->capture_file = NULL;
- s->capture_filename = NULL;
- if (filename) {
- s->capture_file = fopen(filename, "ab");
- if (s->capture_file) {
- s->capture_filename = talloc_strdup(NULL, filename);
- if (s->buf_pos < s->buf_len)
- stream_capture_write(s, s->buffer, s->buf_len);
- } else {
- MP_ERR(s, "Error opening capture file: %s\n", mp_strerror(errno));
- }
- }
- }
-}
-
// Read function bypassing the local stream buffer. This will not write into
// s->buffer, but into buf[0..len] instead.
// Returns 0 on error or EOF, and length of bytes read on success.
// Partial reads are possible, even if EOF is not reached.
static int stream_read_unbuffered(stream_t *s, void *buf, int len)
{
- int orig_len = len;
+ int res = 0;
s->buf_pos = s->buf_len = 0;
// we will retry even if we already reached EOF previously.
- len = s->fill_buffer ? s->fill_buffer(s, buf, len) : -1;
- if (len < 0)
- len = 0;
- if (len == 0) {
+ if (s->fill_buffer && !mp_cancel_test(s->cancel))
+ res = s->fill_buffer(s, buf, len);
+ if (res <= 0) {
// just in case this is an error e.g. due to network
// timeout reset and retry
// do not retry if this looks like proper eof
int64_t size = stream_get_size(s);
if (!s->eof && s->pos != size && stream_reconnect(s)) {
s->eof = 1; // make sure EOF is set to ensure no endless recursion
- return stream_read_unbuffered(s, buf, orig_len);
+ return stream_read_unbuffered(s, buf, len);
}
s->eof = 1;
@@ -411,9 +377,8 @@ static int stream_read_unbuffered(stream_t *s, void *buf, int len)
}
// When reading succeeded we are obviously not at eof.
s->eof = 0;
- s->pos += len;
- stream_capture_write(s, buf, len);
- return len;
+ s->pos += res;
+ return res;
}
static int stream_fill_buffer_by(stream_t *s, int64_t len)
@@ -647,11 +612,9 @@ void free_stream(stream_t *s)
if (!s)
return;
- stream_set_capture_file(s, NULL);
-
if (s->close)
s->close(s);
- free_stream(s->uncached_stream);
+ free_stream(s->underlying);
talloc_free(s);
}
@@ -670,8 +633,8 @@ stream_t *open_memory_stream(void *data, int len)
static stream_t *open_cache(stream_t *orig, const char *name)
{
stream_t *cache = new_stream();
- cache->uncached_type = orig->uncached_type;
- cache->uncached_stream = orig;
+ cache->underlying = orig;
+ cache->caching = true;
cache->seekable = true;
cache->mode = STREAM_READ;
cache->read_chunk = 4 * STREAM_BUFFER_SIZE;
@@ -682,6 +645,8 @@ static stream_t *open_cache(stream_t *orig, const char *name)
cache->lavf_type = talloc_strdup(cache, orig->lavf_type);
cache->streaming = orig->streaming,
cache->is_network = orig->is_network;
+ cache->is_local_file = orig->is_local_file;
+ cache->is_directory = orig->is_directory;
cache->cancel = orig->cancel;
cache->global = orig->global;
@@ -722,7 +687,7 @@ static int stream_enable_cache(stream_t **stream, struct mp_cache_opts *opts)
stream_t *fcache = open_cache(orig, "file-cache");
if (stream_file_cache_init(fcache, orig, &use_opts) <= 0) {
- fcache->uncached_stream = NULL; // don't free original stream
+ fcache->underlying = NULL; // don't free original stream
free_stream(fcache);
fcache = orig;
}
@@ -731,10 +696,10 @@ static int stream_enable_cache(stream_t **stream, struct mp_cache_opts *opts)
int res = stream_cache_init(cache, fcache, &use_opts);
if (res <= 0) {
- cache->uncached_stream = NULL; // don't free original stream
+ cache->underlying = NULL; // don't free original stream
free_stream(cache);
if (fcache != orig) {
- fcache->uncached_stream = NULL;
+ fcache->underlying = NULL;
free_stream(fcache);
}
} else {
diff --git a/stream/stream.h b/stream/stream.h
index 7d44e30eae..cc47184ea6 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -28,20 +28,6 @@
#include "misc/bstr.h"
-enum streamtype {
- STREAMTYPE_GENERIC = 0,
- STREAMTYPE_FILE,
- STREAMTYPE_DIR,
- STREAMTYPE_DVB,
- STREAMTYPE_DVD,
- STREAMTYPE_BLURAY,
- STREAMTYPE_TV,
- STREAMTYPE_MF,
- STREAMTYPE_EDL,
- STREAMTYPE_AVDEVICE,
- STREAMTYPE_CDDA,
-};
-
#define STREAM_BUFFER_SIZE 2048
#define STREAM_MAX_SECTOR_SIZE (8 * 1024)
@@ -178,8 +164,6 @@ typedef struct stream {
// Close
void (*close)(struct stream *s);
- enum streamtype type; // see STREAMTYPE_*
- enum streamtype uncached_type; // if stream is cache, type of wrapped str.
int sector_size; // sector size (seek will be aligned on this size if non 0)
int read_chunk; // maximum amount of data to read at once to limit latency
unsigned int buf_pos, buf_len;
@@ -197,16 +181,16 @@ typedef struct stream {
bool fast_skip : 1; // consider stream fast enough to fw-seek by skipping
bool is_network : 1; // original stream_info_t.is_network flag
bool allow_caching : 1; // stream cache makes sense
+ bool caching : 1; // is a cache, or accesses a cache
+ bool is_local_file : 1; // from the filesystem
+ bool is_directory : 1; // directory on the filesystem
bool access_references : 1; // open other streams
struct mp_log *log;
struct mpv_global *global;
struct mp_cancel *cancel; // cancellation notification
- FILE *capture_file;
- char *capture_filename;
-
- struct stream *uncached_stream; // underlying stream for cache wrapper
+ struct stream *underlying; // e.g. cache wrapper
// Includes additional padding in case sizes get rounded up by sector size.
unsigned char buffer[];
@@ -214,8 +198,6 @@ typedef struct stream {
int stream_fill_buffer(stream_t *s);
-void stream_set_capture_file(stream_t *s, const char *filename);
-
struct mp_cache_opts;
bool stream_wants_cache(stream_t *stream, struct mp_cache_opts *opts);
int stream_enable_cache_defaults(stream_t **stream);
diff --git a/stream/stream_avdevice.c b/stream/stream_avdevice.c
index 9734b7b6f5..2b132cd1a9 100644
--- a/stream/stream_avdevice.c
+++ b/stream/stream_avdevice.c
@@ -21,8 +21,8 @@
static int open_f(stream_t *stream)
{
- stream->type = STREAMTYPE_AVDEVICE;
stream->demuxer = "lavf";
+ stream->allow_caching = false;
return STREAM_OK;
}
diff --git a/stream/stream_bluray.c b/stream/stream_bluray.c
index 5f083954c2..07dcc7f69e 100644
--- a/stream/stream_bluray.c
+++ b/stream/stream_bluray.c
@@ -425,7 +425,7 @@ static int bluray_stream_open_internal(stream_t *s)
char *time = mp_format_time(ti->duration / 90000, false);
MP_VERBOSE(s, "idx: %3d duration: %s (playlist: %05d.mpls)\n",
- i + 1, time, ti->playlist);
+ i, time, ti->playlist);
talloc_free(time);
/* try to guess which title may contain the main movie */
@@ -450,7 +450,6 @@ static int bluray_stream_open_internal(stream_t *s)
s->fill_buffer = bluray_stream_fill_buffer;
s->close = bluray_stream_close;
s->control = bluray_stream_control;
- s->type = STREAMTYPE_BLURAY;
s->sector_size = BLURAY_SECTOR_SIZE;
s->priv = b;
s->demuxer = "+disc";
diff --git a/stream/stream_cdda.c b/stream/stream_cdda.c
index df7862cd8f..2a8eb7553f 100644
--- a/stream/stream_cdda.c
+++ b/stream/stream_cdda.c
@@ -383,7 +383,6 @@ static int open_cdda(stream_t *st)
st->streaming = true;
- st->type = STREAMTYPE_CDDA;
st->demuxer = "+disc";
print_cdtext(st, 0);
diff --git a/stream/stream_dvb.c b/stream/stream_dvb.c
index 6e55d8c156..01ec6c2e77 100644
--- a/stream/stream_dvb.c
+++ b/stream/stream_dvb.c
@@ -931,6 +931,7 @@ static int dvb_open(stream_t *stream)
return STREAM_ERROR;
}
+ stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf);
dvb_state_t* state = dvb_get_state(stream);
dvb_priv_t *p = stream->priv;
@@ -990,7 +991,6 @@ static int dvb_open(stream_t *stream)
return STREAM_ERROR;
}
- stream->type = STREAMTYPE_DVB;
stream->fill_buffer = dvb_streaming_read;
stream->close = dvbin_close;
stream->control = dvbin_stream_control;
@@ -1010,7 +1010,6 @@ dvb_state_t *dvb_get_state(stream_t *stream)
}
struct mp_log *log = stream->log;
struct mpv_global *global = stream->global;
- stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf);
dvb_priv_t *priv = stream->priv;
int type, size;
char filename[30], *name;
diff --git a/stream/stream_dvd.c b/stream/stream_dvd.c
index 338c23633c..fe5796d30d 100644
--- a/stream/stream_dvd.c
+++ b/stream/stream_dvd.c
@@ -904,7 +904,6 @@ static int open_s_internal(stream_t *stream)
// ... (unimplemented)
// return NULL;
- stream->type = STREAMTYPE_DVD;
stream->demuxer = "+disc";
stream->lavf_type = "mpeg";
stream->sector_size = 2048;
diff --git a/stream/stream_dvdnav.c b/stream/stream_dvdnav.c
index 1178f50857..21827b6898 100644
--- a/stream/stream_dvdnav.c
+++ b/stream/stream_dvdnav.c
@@ -507,7 +507,6 @@ static int open_s_internal(stream_t *stream)
stream->fill_buffer = fill_buffer;
stream->control = control;
stream->close = stream_dvdnav_close;
- stream->type = STREAMTYPE_DVD;
stream->demuxer = "+disc";
stream->lavf_type = "mpeg";
stream->allow_caching = false;
@@ -596,7 +595,7 @@ unsupported:
}
const stream_info_t stream_info_ifo_dvdnav = {
- .name = "ifo/dvdnav",
+ .name = "ifo_dvdnav",
.open = ifo_dvdnav_stream_open,
.protocols = (const char*const[]){ "file", "", NULL },
};
diff --git a/stream/stream_edl.c b/stream/stream_edl.c
index 4873047cc2..11c149b3ab 100644
--- a/stream/stream_edl.c
+++ b/stream/stream_edl.c
@@ -5,8 +5,8 @@
static int s_open (struct stream *stream)
{
- stream->type = STREAMTYPE_EDL;
stream->demuxer = "edl";
+ stream->allow_caching = false;
return STREAM_OK;
}
diff --git a/stream/stream_file.c b/stream/stream_file.c
index 5d5925ac7c..bfe40429cb 100644
--- a/stream/stream_file.c
+++ b/stream/stream_file.c
@@ -234,7 +234,7 @@ static int open_f(stream_t *stream)
.fd = -1
};
stream->priv = p;
- stream->type = STREAMTYPE_FILE;
+ stream->is_local_file = true;
bool write = stream->mode == STREAM_WRITE;
int m = O_CLOEXEC | (write ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY);
@@ -281,7 +281,7 @@ static int open_f(stream_t *stream)
if (fstat(p->fd, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
p->use_poll = false;
- stream->type = STREAMTYPE_DIR;
+ stream->is_directory = true;
stream->allow_caching = false;
MP_INFO(stream, "This is a directory - adding to playlist.\n");
}
diff --git a/stream/stream_lavf.c b/stream/stream_lavf.c
index 7594175bd3..873043b38c 100644
--- a/stream/stream_lavf.c
+++ b/stream/stream_lavf.c
@@ -416,6 +416,7 @@ const stream_info_t stream_info_ffmpeg = {
.protocols = (const char *const[]){
"rtmp", "rtsp", "http", "https", "mms", "mmst", "mmsh", "mmshttp", "rtp",
"httpproxy", "hls", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srtp",
+ "data",
NULL },
.can_write = true,
.is_safe = true,
diff --git a/stream/stream_mf.c b/stream/stream_mf.c
index a027e8b79c..ca6ab6ae88 100644
--- a/stream/stream_mf.c
+++ b/stream/stream_mf.c
@@ -30,7 +30,6 @@
static int
mf_stream_open (stream_t *stream)
{
- stream->type = STREAMTYPE_MF;
stream->demuxer = "mf";
stream->allow_caching = false;
diff --git a/stream/stream_tv.c b/stream/stream_tv.c
index 435e3e7b54..d9acbe4cf4 100644
--- a/stream/stream_tv.c
+++ b/stream/stream_tv.c
@@ -39,7 +39,6 @@ static int
tv_stream_open (stream_t *stream)
{
- stream->type = STREAMTYPE_TV;
stream->close=tv_stream_close;
stream->demuxer = "tv";
stream->allow_caching = false;
diff --git a/stream/tv.c b/stream/tv.c
index 0b34b566d8..89783374f9 100644
--- a/stream/tv.c
+++ b/stream/tv.c
@@ -145,7 +145,7 @@ const struct m_sub_options tv_params_conf = {
tvi_handle_t *tv_new_handle(int size, struct mp_log *log, const tvi_functions_t *functions)
{
- tvi_handle_t *h = malloc(sizeof(*h));
+ tvi_handle_t *h = calloc(1, sizeof(*h));
if (!h)
return NULL;
@@ -159,12 +159,9 @@ tvi_handle_t *tv_new_handle(int size, struct mp_log *log, const tvi_functions_t
h->log = log;
h->functions = functions;
- h->seq = 0;
h->chanlist = -1;
- h->chanlist_s = NULL;
h->norm = -1;
h->channel = -1;
- h->scan = NULL;
return h;
}
diff --git a/stream/tvi_dummy.c b/stream/tvi_dummy.c
index 571dace9ab..9eee9437f0 100644
--- a/stream/tvi_dummy.c
+++ b/stream/tvi_dummy.c
@@ -20,6 +20,7 @@
#include "config.h"
#include <stdio.h>
+#include "common/common.h"
#include "video/img_fourcc.h"
#include "tv.h"
@@ -105,7 +106,7 @@ static int do_control(priv_t *priv, int cmd, void *arg)
static double grab_video_frame(priv_t *priv, char *buffer, int len)
{
memset(buffer, 0x42, len);
- return 1;
+ return MP_NOPTS_VALUE;
}
static int get_video_framesize(priv_t *priv)
@@ -117,7 +118,7 @@ static int get_video_framesize(priv_t *priv)
static double grab_audio_frame(priv_t *priv, char *buffer, int len)
{
memset(buffer, 0x42, len);
- return 1;
+ return MP_NOPTS_VALUE;
}
static int get_audio_framesize(priv_t *priv)
diff --git a/sub/ass_mp.c b/sub/ass_mp.c
index 84a706b681..6d85ac1f28 100644
--- a/sub/ass_mp.c
+++ b/sub/ass_mp.c
@@ -78,6 +78,9 @@ void mp_ass_set_style(ASS_Style *style, double res_y,
style->ScaleX = 1.;
style->ScaleY = 1.;
style->Alignment = 1 + (opts->align_x + 1) + (opts->align_y + 2) % 3 * 4;
+#ifdef ASS_JUSTIFY_LEFT
+ style->Justify = opts->justify;
+#endif
style->Blur = opts->blur;
style->Bold = opts->bold;
style->Italic = opts->italic;
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index b9f04b3123..743a06ed14 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -29,6 +29,7 @@
#include "options/options.h"
#include "common/global.h"
#include "common/msg.h"
+#include "common/recorder.h"
#include "osdep/threads.h"
extern const struct sd_functions sd_ass;
@@ -49,6 +50,8 @@ struct dec_sub {
struct mpv_global *global;
struct MPOpts *opts;
+ struct mp_recorder_sink *recorder_sink;
+
struct attachment_list *attachments;
struct sh_stream *sh;
@@ -240,6 +243,9 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
break;
}
+ if (sub->recorder_sink)
+ mp_recorder_feed_packet(sub->recorder_sink, pkt);
+
sub->last_pkt_pts = pkt->pts;
if (is_new_segment(sub, pkt)) {
@@ -323,3 +329,11 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg)
pthread_mutex_unlock(&sub->lock);
return r;
}
+
+void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink)
+{
+ pthread_mutex_lock(&sub->lock);
+ sub->recorder_sink = sink;
+ pthread_mutex_unlock(&sub->lock);
+}
+
diff --git a/sub/dec_sub.h b/sub/dec_sub.h
index 341966a430..26781fd99f 100644
--- a/sub/dec_sub.h
+++ b/sub/dec_sub.h
@@ -9,6 +9,7 @@
struct sh_stream;
struct mpv_global;
struct demux_packet;
+struct mp_recorder_sink;
struct dec_sub;
struct sd;
@@ -16,7 +17,6 @@ struct sd;
enum sd_ctrl {
SD_CTRL_SUB_STEP,
SD_CTRL_SET_VIDEO_PARAMS,
- SD_CTRL_GET_RESOLUTION,
SD_CTRL_SET_TOP,
SD_CTRL_SET_VIDEO_DEF_FPS,
SD_CTRL_UPDATE_SPEED,
@@ -41,6 +41,7 @@ void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, int format,
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);
+void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink);
int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg);
diff --git a/sub/osd.c b/sub/osd.c
index bf6233a0b1..a35380d99f 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -62,6 +62,8 @@ static const m_option_t style_opts[] = {
OPT_FLOATRANGE("blur", blur, 0, 0, 20),
OPT_FLAG("bold", bold, 0),
OPT_FLAG("italic", italic, 0),
+ OPT_CHOICE("justify", justify, 0,
+ ({"auto", 0}, {"left", 1}, {"center", 2}, {"right", 3})),
{0}
};
diff --git a/sub/osd.h b/sub/osd.h
index 755aca9969..7572ec0360 100644
--- a/sub/osd.h
+++ b/sub/osd.h
@@ -134,6 +134,7 @@ struct osd_style_opts {
float blur;
int bold;
int italic;
+ int justify;
};
extern const struct m_sub_options osd_style_conf;
@@ -199,17 +200,9 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h,
struct mp_osd_res res, double compensate_par);
// defined in osd_libass.c and osd_dummy.c
-
-// internal use only
-void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
- int format, struct sub_bitmaps *out_imgs);
-void osd_init_backend(struct osd_state *osd);
-void osd_destroy_backend(struct osd_state *osd);
-
void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
char *text);
-
-// doesn't need locking
+void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h);
void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function);
#endif /* MPLAYER_SUB_H */
diff --git a/sub/osd_dummy.c b/sub/osd_dummy.c
index 796d954c08..0e6b802cef 100644
--- a/sub/osd_dummy.c
+++ b/sub/osd_dummy.c
@@ -4,7 +4,7 @@
#include "config.h"
#include "mpv_talloc.h"
-#include "osd.h"
+#include "osd_state.h"
void osd_init_backend(struct osd_state *osd)
{
@@ -28,3 +28,9 @@ void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y,
char *text)
{
}
+
+void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h)
+{
+ *out_screen_h = 0;
+ *out_font_h = 0;
+}
diff --git a/sub/osd_libass.c b/sub/osd_libass.c
index 454d0387a4..44fcf6d269 100644
--- a/sub/osd_libass.c
+++ b/sub/osd_libass.c
@@ -219,13 +219,10 @@ static ASS_Event *add_osd_ass_event_escaped(ASS_Track *track, const char *style,
return e;
}
-static void update_osd_text(struct osd_state *osd, struct osd_object *obj)
+static ASS_Style *prepare_osd_ass(struct osd_state *osd, struct osd_object *obj)
{
struct MPOpts *opts = osd->opts;
- if (!obj->text[0])
- return;
-
create_ass_track(osd, obj, &obj->ass, 0, 0);
struct osd_style_opts font = *opts->osd_style;
@@ -236,10 +233,31 @@ static void update_osd_text(struct osd_state *osd, struct osd_object *obj)
if (!opts->osd_scale_by_window)
playresy *= 720.0 / obj->vo_res.h;
- mp_ass_set_style(get_style(&obj->ass, "OSD"), playresy, &font);
+ ASS_Style *style = get_style(&obj->ass, "OSD");
+ mp_ass_set_style(style, playresy, &font);
+ return style;
+}
+
+static void update_osd_text(struct osd_state *osd, struct osd_object *obj)
+{
+
+ if (!obj->text[0])
+ return;
+
+ prepare_osd_ass(osd, obj);
add_osd_ass_event_escaped(obj->ass.track, "OSD", obj->text);
}
+void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h)
+{
+ pthread_mutex_lock(&osd->lock);
+ struct osd_object *obj = osd->objs[OSDTYPE_OSD];
+ ASS_Style *style = prepare_osd_ass(osd, obj);
+ *out_screen_h = obj->ass.track->PlayResY - style->MarginV;
+ *out_font_h = style->FontSize;
+ pthread_mutex_unlock(&osd->lock);
+}
+
// align: -1 .. +1
// frame: size of the containing area
// obj: size of the object that should be positioned inside the area
diff --git a/sub/osd_state.h b/sub/osd_state.h
index cce415a1b9..dc2d6d5b6c 100644
--- a/sub/osd_state.h
+++ b/sub/osd_state.h
@@ -79,4 +79,10 @@ struct osd_state {
struct mp_draw_sub_cache *draw_cache;
};
+// defined in osd_libass.c and osd_dummy.c
+void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
+ int format, struct sub_bitmaps *out_imgs);
+void osd_init_backend(struct osd_state *osd);
+void osd_destroy_backend(struct osd_state *osd);
+
#endif
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index 31c66a6087..9b4d3763c5 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -333,6 +333,10 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
if (converted)
set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT;
#endif
+#ifdef ASS_JUSTIFY_AUTO
+ if ((converted || opts->ass_style_override) && opts->ass_justify)
+ set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY;
+#endif
ass_set_selective_style_override_enabled(priv, set_force_flags);
ASS_Style style = {0};
mp_ass_set_style(&style, 288, opts->sub_style);
diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c
index 4ce8c5588c..b660912bf5 100644
--- a/sub/sd_lavc.c
+++ b/sub/sd_lavc.c
@@ -71,35 +71,6 @@ struct sd_lavc_priv {
struct bitmap_packer *packer;
};
-static void get_resolution(struct sd *sd, int wh[2])
-{
- struct sd_lavc_priv *priv = sd->priv;
- enum AVCodecID codec = priv->avctx->codec_id;
- int *w = &wh[0], *h = &wh[1];
- *w = priv->avctx->width;
- *h = priv->avctx->height;
- if (codec == AV_CODEC_ID_DVD_SUBTITLE) {
- if (*w <= 0 || *h <= 0) {
- *w = priv->video_params.w;
- *h = priv->video_params.h;
- }
- /* XXX Although the video frame is some size, the SPU frame is
- always maximum size i.e. 720 wide and 576 or 480 high */
- // For HD files in MKV the VobSub resolution can be higher though,
- // see largeres_vobsub.mkv
- if (*w <= 720 && *h <= 576) {
- *w = 720;
- *h = (*h == 480 || *h == 240) ? 480 : 576;
- }
- } else {
- // Hope that PGS subs set these and 720/576 works for dvb subs
- if (!*w)
- *w = 720;
- if (!*h)
- *h = 576;
- }
-}
-
static int init(struct sd *sd)
{
enum AVCodecID cid = mp_codec_to_av_codec_id(sd->codec->codec);
@@ -466,13 +437,17 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format,
video_par = -1;
if (opts->stretch_image_subs)
d.ml = d.mr = d.mt = d.mb = 0;
- int insize[2];
- get_resolution(sd, insize);
- if (current->src_w > insize[0] || current->src_h > insize[1]) {
- insize[0] = priv->video_params.w;
- insize[1] = priv->video_params.h;
+ int w = priv->avctx->width;
+ int h = priv->avctx->height;
+ if (w <= 0 || h <= 0 || opts->image_subs_video_res) {
+ w = priv->video_params.w;
+ h = priv->video_params.h;
}
- osd_rescale_bitmaps(res, insize[0], insize[1], d, video_par);
+ if (current->src_w > w || current->src_h > h) {
+ w = priv->video_params.w;
+ h = priv->video_params.h;
+ }
+ osd_rescale_bitmaps(res, w, h, d, video_par);
}
static bool accepts_packet(struct sd *sd, double min_pts)
@@ -606,9 +581,6 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
case SD_CTRL_SET_VIDEO_PARAMS:
priv->video_params = *(struct mp_image_params *)arg;
return CONTROL_OK;
- case SD_CTRL_GET_RESOLUTION:
- get_resolution(sd, arg);
- return CONTROL_OK;
default:
return CONTROL_UNKNOWN;
}
diff --git a/ta/ta_talloc.h b/ta/ta_talloc.h
index ce0ddaa4c9..f5eb35e17f 100644
--- a/ta/ta_talloc.h
+++ b/ta/ta_talloc.h
@@ -73,7 +73,7 @@ char *ta_talloc_asprintf_append_buffer(char *s, const char *fmt, ...) TA_PRF(2,
// mpv specific stuff - should be made part of proper TA API
-#define TA_FREEP(pctx) do {if (pctx) {talloc_free(*pctx); (*pctx) = NULL;}} while(0)
+#define TA_FREEP(pctx) do {talloc_free(*(pctx)); *(pctx) = NULL;} while(0)
#define TA_EXPAND_ARGS(...) __VA_ARGS__
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index d9dbf0f326..23aba81709 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -33,6 +33,7 @@
#include "demux/packet.h"
#include "common/codecs.h"
+#include "common/recorder.h"
#include "video/out/vo.h"
#include "video/csputils.h"
@@ -89,7 +90,6 @@ void video_uninit(struct dec_video *d_video)
if (!d_video)
return;
mp_image_unrefp(&d_video->current_mpi);
- mp_image_unrefp(&d_video->cover_art_mpi);
if (d_video->vd_driver) {
MP_VERBOSE(d_video, "Uninit video.\n");
d_video->vd_driver->uninit(d_video);
@@ -252,36 +252,53 @@ static void fix_image_params(struct dec_video *d_video,
d_video->fixed_format = p;
}
-static struct mp_image *decode_packet(struct dec_video *d_video,
- struct demux_packet *packet,
- int drop_frame)
+static bool send_packet(struct dec_video *d_video, struct demux_packet *packet)
{
- struct MPOpts *opts = d_video->opts;
-
- if (!d_video->vd_driver)
- return NULL;
-
double pkt_pts = packet ? packet->pts : MP_NOPTS_VALUE;
double pkt_dts = packet ? packet->dts : MP_NOPTS_VALUE;
if (pkt_pts == MP_NOPTS_VALUE)
d_video->has_broken_packet_pts = 1;
+ bool dts_replaced = false;
+ if (packet && packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts) {
+ packet->dts = packet->pts;
+ dts_replaced = true;
+ }
+
double pkt_pdts = pkt_pts == MP_NOPTS_VALUE ? pkt_dts : pkt_pts;
if (pkt_pdts != MP_NOPTS_VALUE && d_video->first_packet_pdts == MP_NOPTS_VALUE)
d_video->first_packet_pdts = pkt_pdts;
MP_STATS(d_video, "start decode video");
- struct mp_image *mpi = d_video->vd_driver->decode(d_video, packet, drop_frame);
+ bool res = d_video->vd_driver->send_packet(d_video, packet);
MP_STATS(d_video, "end decode video");
- // Error, discarded frame, dropped frame, or initial codec delay.
- if (!mpi || drop_frame) {
- talloc_free(mpi);
- return NULL;
- }
+ // Stream recording can't deal with almost surely wrong fake DTS.
+ if (dts_replaced)
+ packet->dts = MP_NOPTS_VALUE;
+
+ return res;
+}
+
+static bool receive_frame(struct dec_video *d_video, struct mp_image **out_image)
+{
+ struct MPOpts *opts = d_video->opts;
+ struct mp_image *mpi = NULL;
+
+ assert(!*out_image);
+
+ MP_STATS(d_video, "start decode video");
+
+ bool progress = d_video->vd_driver->receive_frame(d_video, &mpi);
+
+ MP_STATS(d_video, "end decode video");
+
+ // Error, EOF, discarded frame, dropped frame, or initial codec delay.
+ if (!mpi)
+ return progress;
if (opts->field_dominance == 0) {
mpi->fields |= MP_IMGFIELD_TOP_FIRST | MP_IMGFIELD_INTERLACED;
@@ -353,7 +370,8 @@ static struct mp_image *decode_packet(struct dec_video *d_video,
mpi->pts -= MPMAX(delay, 0) / d_video->fps;
}
- return mpi;
+ *out_image = mpi;
+ return true;
}
void video_reset_params(struct dec_video *d_video)
@@ -379,24 +397,8 @@ void video_set_start(struct dec_video *d_video, double start_pts)
void video_work(struct dec_video *d_video)
{
- if (d_video->current_mpi)
- return;
-
- if (d_video->header->attached_picture) {
- if (d_video->current_state == DATA_AGAIN && !d_video->cover_art_mpi) {
- struct demux_packet *packet =
- demux_copy_packet(d_video->header->attached_picture);
- d_video->cover_art_mpi = decode_packet(d_video, packet, 0);
- // Might need flush.
- if (!d_video->cover_art_mpi)
- d_video->cover_art_mpi = decode_packet(d_video, NULL, 0);
- talloc_free(packet);
- }
- if (d_video->current_state != DATA_EOF)
- d_video->current_mpi = mp_image_new_ref(d_video->cover_art_mpi);
- d_video->current_state = DATA_EOF;
+ if (d_video->current_mpi || !d_video->vd_driver)
return;
- }
if (!d_video->packet && !d_video->new_segment &&
demux_read_packet_async(d_video->header, &d_video->packet) == 0)
@@ -405,20 +407,12 @@ void video_work(struct dec_video *d_video)
return;
}
- if (d_video->packet) {
- if (d_video->packet->dts == MP_NOPTS_VALUE && !d_video->codec->avi_dts)
- d_video->packet->dts = d_video->packet->pts;
- }
-
if (d_video->packet && d_video->packet->new_segment) {
assert(!d_video->new_segment);
d_video->new_segment = d_video->packet;
d_video->packet = NULL;
}
- bool had_input_packet = !!d_video->packet;
- bool had_packet = had_input_packet || d_video->new_segment;
-
double start_pts = d_video->start_pts;
if (d_video->start != MP_NOPTS_VALUE && (start_pts == MP_NOPTS_VALUE ||
d_video->start > start_pts))
@@ -431,23 +425,29 @@ void video_work(struct dec_video *d_video)
{
framedrop_type = 2;
}
- d_video->current_mpi = decode_packet(d_video, d_video->packet, framedrop_type);
- if (d_video->packet && d_video->packet->len == 0) {
+
+ d_video->vd_driver->control(d_video, VDCTRL_SET_FRAMEDROP, &framedrop_type);
+
+ if (send_packet(d_video, d_video->packet)) {
+ if (d_video->recorder_sink)
+ mp_recorder_feed_packet(d_video->recorder_sink, d_video->packet);
+
talloc_free(d_video->packet);
d_video->packet = NULL;
}
+ bool progress = receive_frame(d_video, &d_video->current_mpi);
+
d_video->current_state = DATA_OK;
- if (!d_video->current_mpi) {
+ if (!progress) {
d_video->current_state = DATA_EOF;
- if (had_packet) {
- if (framedrop_type == 1)
- d_video->dropped_frames += 1;
- d_video->current_state = DATA_AGAIN;
- }
+ } else if (!d_video->current_mpi) {
+ if (framedrop_type == 1)
+ d_video->dropped_frames += 1;
+ d_video->current_state = DATA_AGAIN;
}
- bool segment_ended = !d_video->current_mpi && !had_input_packet;
+ bool segment_ended = d_video->current_state == DATA_EOF;
if (d_video->current_mpi && d_video->current_mpi->pts != MP_NOPTS_VALUE) {
double vpts = d_video->current_mpi->pts;
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 1d2b3f087e..5ef1f9252a 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -42,6 +42,8 @@ struct dec_video {
int dropped_frames;
+ struct mp_recorder_sink *recorder_sink;
+
// Internal (shared with vd_lavc.c).
void *priv; // for free use by vd_driver
@@ -75,7 +77,6 @@ struct dec_video {
struct demux_packet *new_segment;
struct demux_packet *packet;
bool framedrop_enabled;
- struct mp_image *cover_art_mpi;
struct mp_image *current_mpi;
int current_state;
};
diff --git a/video/decode/cuda.c b/video/decode/hw_cuda.c
index cad02b2353..92ba0772c4 100644
--- a/video/decode/cuda.c
+++ b/video/decode/hw_cuda.c
@@ -38,54 +38,45 @@ static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
static int init(struct lavc_ctx *ctx)
{
- ctx->hwdec_priv = hwdec_devices_get(ctx->hwdec_devs, HWDEC_CUDA)->ctx;
+ ctx->hwdec_priv = hwdec_devices_get(ctx->hwdec_devs, HWDEC_CUDA);
return 0;
}
static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
AVCodecContext *avctx = ctx->avctx;
- AVCUDADeviceContext *device_hwctx;
- AVHWDeviceContext *device_ctx;
- AVHWFramesContext *hwframe_ctx;
- int ret = 0;
+ struct mp_hwdec_ctx *hwctx = ctx->hwdec_priv;
if (avctx->hw_frames_ctx) {
MP_ERR(ctx, "hw_frames_ctx already initialised!\n");
return -1;
}
- AVBufferRef *hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
- if (!hw_device_ctx) {
- MP_WARN(ctx, "av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA) failed\n");
- goto error;
- }
-
- device_ctx = (AVHWDeviceContext*)hw_device_ctx->data;
-
- device_hwctx = device_ctx->hwctx;
- device_hwctx->cuda_ctx = ctx->hwdec_priv;
-
- ret = av_hwdevice_ctx_init(hw_device_ctx);
- if (ret < 0) {
- MP_ERR(ctx, "av_hwdevice_ctx_init failed\n");
- goto error;
- }
-
- avctx->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
+ avctx->hw_frames_ctx = av_hwframe_ctx_alloc(hwctx->av_device_ref);
if (!avctx->hw_frames_ctx) {
MP_ERR(ctx, "av_hwframe_ctx_alloc failed\n");
goto error;
}
- hwframe_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data;
+ AVHWFramesContext *hwframe_ctx = (void* )avctx->hw_frames_ctx->data;
hwframe_ctx->format = AV_PIX_FMT_CUDA;
+ // This is proper use of the hw_frames_ctx API, but it does not work
+ // (appaears to work but fails e.g. with 10 bit). The cuvid wrapper
+ // does non-standard things, and it's a meesy situation.
+ /*
+ hwframe_ctx->width = w;
+ hwframe_ctx->height = h;
+ hwframe_ctx->sw_format = avctx->sw_pix_fmt;
+
+ if (av_hwframe_ctx_init(avctx->hw_frames_ctx) < 0)
+ goto error;
+ */
+
return 0;
error:
av_buffer_unref(&avctx->hw_frames_ctx);
- av_buffer_unref(&hw_device_ctx);
return -1;
}
diff --git a/video/decode/d3d11va.c b/video/decode/hw_d3d11va.c
index e31582d37c..a69a3890bd 100644
--- a/video/decode/d3d11va.c
+++ b/video/decode/hw_d3d11va.c
@@ -27,7 +27,7 @@
#include "d3d.h"
-#define ADDITIONAL_SURFACES (4 + HWDEC_DELAY_QUEUE_COUNT)
+#define ADDITIONAL_SURFACES (HWDEC_EXTRA_SURFACES + HWDEC_DELAY_QUEUE_COUNT)
struct d3d11va_decoder {
ID3D11VideoDecoder *decoder;
diff --git a/video/decode/dxva2.c b/video/decode/hw_dxva2.c
index ab91c186df..7b1a6b4bc7 100644
--- a/video/decode/dxva2.c
+++ b/video/decode/hw_dxva2.c
@@ -32,7 +32,7 @@
#include "d3d.h"
-#define ADDITIONAL_SURFACES (4 + HWDEC_DELAY_QUEUE_COUNT)
+#define ADDITIONAL_SURFACES (HWDEC_EXTRA_SURFACES + HWDEC_DELAY_QUEUE_COUNT)
struct priv {
struct mp_log *log;
@@ -357,44 +357,53 @@ static bool create_device(struct lavc_ctx *s)
return false;
}
- IDirect3D9* (WINAPI *Direct3DCreate9)(UINT) =
- (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9");
- if (!Direct3DCreate9) {
- MP_ERR(p, "Failed to locate Direct3DCreate9\n");
+ HRESULT (WINAPI *Direct3DCreate9Ex)(UINT, IDirect3D9Ex **) =
+ (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
+ if (!Direct3DCreate9Ex) {
+ MP_ERR(p, "Failed to locate Direct3DCreate9Ex\n");
return false;
}
- p->d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
- if (!p->d3d9) {
- MP_ERR(p, "Failed to create IDirect3D object\n");
+ IDirect3D9Ex *d3d9ex = NULL;
+ HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
+ if (FAILED(hr)) {
+ MP_ERR(p, "Failed to create IDirect3D9Ex object\n");
return false;
}
UINT adapter = D3DADAPTER_DEFAULT;
- D3DDISPLAYMODE display_mode;
- IDirect3D9_GetAdapterDisplayMode(p->d3d9, adapter, &display_mode);
+ D3DDISPLAYMODEEX modeex = {0};
+ IDirect3D9Ex_GetAdapterDisplayModeEx(d3d9ex, adapter, &modeex, NULL);
+
D3DPRESENT_PARAMETERS present_params = {
.Windowed = TRUE,
.BackBufferWidth = 640,
.BackBufferHeight = 480,
.BackBufferCount = 0,
- .BackBufferFormat = display_mode.Format,
+ .BackBufferFormat = modeex.Format,
.SwapEffect = D3DSWAPEFFECT_DISCARD,
.Flags = D3DPRESENTFLAG_VIDEO,
};
- HRESULT hr = IDirect3D9_CreateDevice(p->d3d9, adapter,
- D3DDEVTYPE_HAL,
- GetShellWindow(),
- D3DCREATE_SOFTWARE_VERTEXPROCESSING |
- D3DCREATE_MULTITHREADED |
- D3DCREATE_FPU_PRESERVE,
- &present_params,
- &p->device);
+
+ IDirect3DDevice9Ex *exdev = NULL;
+ hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, adapter,
+ D3DDEVTYPE_HAL,
+ GetShellWindow(),
+ D3DCREATE_SOFTWARE_VERTEXPROCESSING |
+ D3DCREATE_MULTITHREADED |
+ D3DCREATE_FPU_PRESERVE,
+ &present_params,
+ NULL,
+ &exdev);
if (FAILED(hr)) {
MP_ERR(p, "Failed to create Direct3D device: %s\n",
mp_HRESULT_to_str(hr));
+ IDirect3D9_Release(d3d9ex);
return false;
}
+
+ p->d3d9 = (IDirect3D9 *)d3d9ex;
+ p->device = (IDirect3DDevice9 *)exdev;
return true;
}
diff --git a/video/decode/hw_vaapi.c b/video/decode/hw_vaapi.c
new file mode 100644
index 0000000000..99c23f48c4
--- /dev/null
+++ b/video/decode/hw_vaapi.c
@@ -0,0 +1,171 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <assert.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/common.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+
+#include "config.h"
+
+#include "lavc.h"
+#include "common/common.h"
+#include "common/av_common.h"
+#include "video/fmt-conversion.h"
+#include "video/vaapi.h"
+#include "video/mp_image_pool.h"
+#include "video/hwdec.h"
+#include "video/filter/vf.h"
+
+#define ADDITIONAL_SURFACES (HWDEC_EXTRA_SURFACES + HWDEC_DELAY_QUEUE_COUNT)
+
+struct priv {
+ struct mp_log *log;
+ struct mp_vaapi_ctx *ctx;
+ struct mp_hwdec_ctx *hwdev;
+};
+
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
+{
+ struct priv *p = ctx->hwdec_priv;
+ // libavcodec has no way yet to communicate the exact surface format needed
+ // for the frame pool, or the required minimum size of the frame pool.
+ // Hopefully, this weakness in the libavcodec API will be fixed in the
+ // future.
+ // For the pixel format, we try to second-guess from what the libavcodec
+ // software decoder would require (sw_pix_fmt). It could break and require
+ // adjustment if new VAAPI surface formats are added.
+ int sw_format = ctx->avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P10 ?
+ AV_PIX_FMT_P010 : AV_PIX_FMT_NV12;
+
+ // The video output might not support all formats.
+ // Note that supported_formats==NULL means any are accepted.
+ if (p->hwdev && p->hwdev->supported_formats) {
+ int mp_format = pixfmt2imgfmt(sw_format);
+ bool found = false;
+ for (int n = 0; p->hwdev->supported_formats[n]; n++) {
+ if (p->hwdev->supported_formats[n] == mp_format) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ MP_WARN(ctx, "Surface format %s not supported for direct rendering.\n",
+ mp_imgfmt_to_name(mp_format));
+ return -1;
+ }
+ }
+
+ return hwdec_setup_hw_frames_ctx(ctx, p->ctx->av_device_ref, sw_format,
+ hwdec_get_max_refs(ctx) + ADDITIONAL_SURFACES);
+}
+
+static void uninit(struct lavc_ctx *ctx)
+{
+ struct priv *p = ctx->hwdec_priv;
+
+ if (!p)
+ return;
+
+ if (!p->hwdev)
+ va_destroy(p->ctx);
+
+ talloc_free(p);
+ ctx->hwdec_priv = NULL;
+}
+
+static int init(struct lavc_ctx *ctx, bool direct)
+{
+ struct priv *p = talloc_ptrtype(NULL, p);
+ *p = (struct priv) {
+ .log = mp_log_new(p, ctx->log, "vaapi"),
+ };
+
+ if (direct) {
+ p->hwdev = hwdec_devices_get(ctx->hwdec_devs, HWDEC_VAAPI);
+ p->ctx = p->hwdev->ctx;
+ } else {
+ p->ctx = va_create_standalone(ctx->log, false);
+ if (!p->ctx) {
+ talloc_free(p);
+ return -1;
+ }
+ }
+
+ ctx->hwdec_priv = p;
+
+ if (!p->ctx->av_device_ref)
+ return -1;
+
+ return 0;
+}
+
+static int init_direct(struct lavc_ctx *ctx)
+{
+ return init(ctx, true);
+}
+
+static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
+ const char *codec)
+{
+ if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_VAAPI))
+ return HWDEC_ERR_NO_CTX;
+ return 0;
+}
+
+static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
+ const char *codec)
+{
+ struct mp_vaapi_ctx *dummy = va_create_standalone(ctx->log, true);
+ if (!dummy)
+ return HWDEC_ERR_NO_CTX;
+ bool emulated = va_guess_if_emulated(dummy);
+ va_destroy(dummy);
+ if (emulated)
+ return HWDEC_ERR_EMULATED;
+ return 0;
+}
+
+static int init_copy(struct lavc_ctx *ctx)
+{
+ return init(ctx, false);
+}
+
+const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
+ .type = HWDEC_VAAPI,
+ .image_format = IMGFMT_VAAPI,
+ .volatile_context = true,
+ .probe = probe,
+ .init = init_direct,
+ .uninit = uninit,
+ .init_decoder = init_decoder,
+};
+
+const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy = {
+ .type = HWDEC_VAAPI_COPY,
+ .copying = true,
+ .image_format = IMGFMT_VAAPI,
+ .volatile_context = true,
+ .probe = probe_copy,
+ .init = init_copy,
+ .uninit = uninit,
+ .init_decoder = init_decoder,
+ .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
+};
diff --git a/video/decode/vaapi.c b/video/decode/hw_vaapi_old.c
index c095868816..88379dfed8 100644
--- a/video/decode/vaapi.c
+++ b/video/decode/hw_vaapi_old.c
@@ -52,11 +52,9 @@
struct priv {
struct mp_log *log;
struct mp_vaapi_ctx *ctx;
+ bool own_ctx;
VADisplay display;
- const struct va_native_display *native_display_fns;
- void *native_display;
-
// libavcodec shared struct
struct vaapi_context *va_context;
struct vaapi_context va_context_storage;
@@ -67,98 +65,6 @@ struct priv {
struct mp_image_pool *sw_pool;
};
-struct va_native_display {
- void (*create)(struct priv *p);
- void (*destroy)(struct priv *p);
-};
-
-#if HAVE_VAAPI_X11
-#include <X11/Xlib.h>
-#include <va/va_x11.h>
-
-static void x11_destroy(struct priv *p)
-{
- if (p->native_display)
- XCloseDisplay(p->native_display);
- p->native_display = NULL;
-}
-
-static void x11_create(struct priv *p)
-{
- p->native_display = XOpenDisplay(NULL);
- if (!p->native_display)
- return;
- p->display = vaGetDisplay(p->native_display);
- if (!p->display)
- x11_destroy(p);
-}
-
-static const struct va_native_display disp_x11 = {
- .create = x11_create,
- .destroy = x11_destroy,
-};
-#endif
-
-#if HAVE_VAAPI_DRM
-#include <unistd.h>
-#include <fcntl.h>
-#include <va/va_drm.h>
-
-struct va_native_display_drm {
- int drm_fd;
-};
-
-static void drm_destroy(struct priv *p)
-{
- struct va_native_display_drm *native_display = p->native_display;
- if (native_display) {
- if (native_display->drm_fd >= 0)
- close(native_display->drm_fd);
- talloc_free(native_display);
- p->native_display = NULL;
- }
-}
-
-static void drm_create(struct priv *p)
-{
- static const char *drm_device_paths[] = {
- "/dev/dri/renderD128",
- "/dev/dri/card0",
- NULL
- };
-
- for (int i = 0; drm_device_paths[i]; i++) {
- int drm_fd = open(drm_device_paths[i], O_RDWR);
- if (drm_fd < 0)
- continue;
-
- struct va_native_display_drm *native_display = talloc_ptrtype(NULL, native_display);
- native_display->drm_fd = drm_fd;
- p->native_display = native_display;
- p->display = vaGetDisplayDRM(drm_fd);
- if (p->display)
- return;
-
- drm_destroy(p);
- }
-}
-
-static const struct va_native_display disp_drm = {
- .create = drm_create,
- .destroy = drm_destroy,
-};
-#endif
-
-static const struct va_native_display *const native_displays[] = {
-#if HAVE_VAAPI_DRM
- &disp_drm,
-#endif
-#if HAVE_VAAPI_X11
- &disp_x11,
-#endif
- NULL
-};
-
#define HAS_HEVC VA_CHECK_VERSION(0, 38, 0)
#define HAS_VP9 (VA_CHECK_VERSION(0, 38, 1) && defined(FF_PROFILE_VP9_0))
@@ -264,8 +170,6 @@ static void destroy_decoder(struct lavc_ctx *ctx)
{
struct priv *p = ctx->hwdec_priv;
- va_lock(p->ctx);
-
if (p->va_context->context_id != VA_INVALID_ID) {
vaDestroyContext(p->display, p->va_context->context_id);
p->va_context->context_id = VA_INVALID_ID;
@@ -276,8 +180,6 @@ static void destroy_decoder(struct lavc_ctx *ctx)
p->va_context->config_id = VA_INVALID_ID;
}
- va_unlock(p->ctx);
-
mp_image_pool_clear(p->pool);
}
@@ -300,8 +202,6 @@ static int init_decoder(struct lavc_ctx *ctx, int w, int h)
destroy_decoder(ctx);
- va_lock(p->ctx);
-
const struct hwdec_profile_entry *pe = hwdec_find_profile(ctx, profiles);
if (!pe) {
MP_ERR(p, "Unsupported codec or profile.\n");
@@ -376,7 +276,6 @@ static int init_decoder(struct lavc_ctx *ctx, int w, int h)
res = 0;
error:
- va_unlock(p->ctx);
talloc_free(tmp);
return res;
}
@@ -397,40 +296,6 @@ static struct mp_image *update_format(struct lavc_ctx *ctx, struct mp_image *img
return img;
}
-static void destroy_va_dummy_ctx(struct priv *p)
-{
- va_destroy(p->ctx);
- p->ctx = NULL;
- p->display = NULL;
- if (p->native_display_fns)
- p->native_display_fns->destroy(p);
-}
-
-// Creates a "private" VADisplay, disconnected from the VO. We just create a
-// new X connection, because that's simpler. (We could also pass the X
-// connection along with struct mp_hwdec_devices, if we wanted.)
-static bool create_va_dummy_ctx(struct priv *p)
-{
- for (int n = 0; native_displays[n]; n++) {
- native_displays[n]->create(p);
- if (p->display) {
- p->native_display_fns = native_displays[n];
- break;
- }
- }
- if (!p->display)
- goto destroy_ctx;
- p->ctx = va_initialize(p->display, p->log, true);
- if (!p->ctx) {
- vaTerminate(p->display);
- goto destroy_ctx;
- }
- return true;
-destroy_ctx:
- destroy_va_dummy_ctx(p);
- return false;
-}
-
static void uninit(struct lavc_ctx *ctx)
{
struct priv *p = ctx->hwdec_priv;
@@ -443,8 +308,8 @@ static void uninit(struct lavc_ctx *ctx)
talloc_free(p->pool);
p->pool = NULL;
- if (p->native_display_fns)
- destroy_va_dummy_ctx(p);
+ if (p->own_ctx)
+ va_destroy(p->ctx);
talloc_free(p);
ctx->hwdec_priv = NULL;
@@ -462,11 +327,12 @@ static int init(struct lavc_ctx *ctx, bool direct)
if (direct) {
p->ctx = hwdec_devices_get(ctx->hwdec_devs, HWDEC_VAAPI)->ctx;
} else {
- create_va_dummy_ctx(p);
+ p->ctx = va_create_standalone(ctx->log, false);
if (!p->ctx) {
talloc_free(p);
return -1;
}
+ p->own_ctx = true;
}
p->display = p->ctx->display;
@@ -502,11 +368,11 @@ static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
const char *codec)
{
- struct priv dummy = {mp_null_log};
- if (!create_va_dummy_ctx(&dummy))
+ struct mp_vaapi_ctx *dummy = va_create_standalone(ctx->log, true);
+ if (!dummy)
return HWDEC_ERR_NO_CTX;
- bool emulated = va_guess_if_emulated(dummy.ctx);
- destroy_va_dummy_ctx(&dummy);
+ bool emulated = va_guess_if_emulated(dummy);
+ va_destroy(dummy);
if (!hwdec_check_codec_support(codec, profiles))
return HWDEC_ERR_NO_CODEC;
if (emulated)
@@ -531,18 +397,6 @@ static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *img)
return img;
}
-static void intel_shit_lock(struct lavc_ctx *ctx)
-{
- struct priv *p = ctx->hwdec_priv;
- va_lock(p->ctx);
-}
-
-static void intel_crap_unlock(struct lavc_ctx *ctx)
-{
- struct priv *p = ctx->hwdec_priv;
- va_unlock(p->ctx);
-}
-
const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
.type = HWDEC_VAAPI,
.image_format = IMGFMT_VAAPI,
@@ -551,8 +405,6 @@ const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
.uninit = uninit,
.init_decoder = init_decoder,
.allocate_image = allocate_image,
- .lock = intel_shit_lock,
- .unlock = intel_crap_unlock,
.process_image = update_format,
};
diff --git a/video/decode/vdpau.c b/video/decode/hw_vdpau.c
index a6b6210804..6047ef1360 100644
--- a/video/decode/vdpau.c
+++ b/video/decode/hw_vdpau.c
@@ -18,6 +18,7 @@
#include <libavcodec/avcodec.h>
#include <libavcodec/vdpau.h>
#include <libavutil/common.h>
+#include <libavutil/hwcontext.h>
#include "lavc.h"
#include "common/common.h"
@@ -31,12 +32,20 @@ struct priv {
uint64_t preemption_counter;
// vdpau-copy
Display *display;
- struct mp_image_pool *sw_pool;
};
static int init_decoder(struct lavc_ctx *ctx, int w, int h)
{
struct priv *p = ctx->hwdec_priv;
+ int sw_format = ctx->avctx->sw_pix_fmt;
+
+ if (sw_format != AV_PIX_FMT_YUV420P && sw_format != AV_PIX_FMT_NV12) {
+ MP_VERBOSE(ctx, "Rejecting non 4:2:0 8 bit decoding.\n");
+ return -1;
+ }
+
+ if (hwdec_setup_hw_frames_ctx(ctx, p->mpvdp->av_device_ref, sw_format, 0) < 0)
+ return -1;
// During preemption, pretend everything is ok.
if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) < 0)
@@ -48,34 +57,6 @@ static int init_decoder(struct lavc_ctx *ctx, int w, int h)
AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH);
}
-static struct mp_image *allocate_image(struct lavc_ctx *ctx, int w, int h)
-{
- struct priv *p = ctx->hwdec_priv;
-
- // In case of preemption, reinit the decoder. Setting hwdec_request_reinit
- // will cause init_decoder() to be called again.
- if (mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter) == 0)
- ctx->hwdec_request_reinit = true;
-
- VdpChromaType chroma = 0;
- uint32_t s_w = w, s_h = h;
- if (av_vdpau_get_surface_parameters(ctx->avctx, &chroma, &s_w, &s_h) < 0)
- return NULL;
-
- return mp_vdpau_get_video_surface(p->mpvdp, chroma, s_w, s_h);
-}
-
-static struct mp_image *update_format(struct lavc_ctx *ctx, struct mp_image *img)
-{
- VdpChromaType chroma = 0;
- uint32_t s_w, s_h;
- if (av_vdpau_get_surface_parameters(ctx->avctx, &chroma, &s_w, &s_h) >= 0) {
- if (chroma == VDP_CHROMA_TYPE_420)
- img->params.hw_subfmt = IMGFMT_NV12;
- }
- return img;
-}
-
static void uninit(struct lavc_ctx *ctx)
{
struct priv *p = ctx->hwdec_priv;
@@ -128,8 +109,6 @@ static int init_copy(struct lavc_ctx *ctx)
if (!p->mpvdp)
goto error;
- p->sw_pool = talloc_steal(p, mp_image_pool_new(17));
-
ctx->hwdec_priv = p;
mp_vdpau_handle_preemption(p->mpvdp, &p->preemption_counter);
@@ -156,15 +135,6 @@ static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
return r;
}
-static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *img)
-{
- struct priv *p = ctx->hwdec_priv;
- struct mp_hwdec_ctx *hwctx = &p->mpvdp->hwctx;
- struct mp_image *out = hwctx->download_image(hwctx, img, p->sw_pool);
- talloc_free(img);
- return out;
-}
-
const struct vd_lavc_hwdec mp_vd_lavc_vdpau = {
.type = HWDEC_VDPAU,
.image_format = IMGFMT_VDPAU,
@@ -172,8 +142,7 @@ const struct vd_lavc_hwdec mp_vd_lavc_vdpau = {
.init = init,
.uninit = uninit,
.init_decoder = init_decoder,
- .allocate_image = allocate_image,
- .process_image = update_format,
+ .volatile_context = true,
};
const struct vd_lavc_hwdec mp_vd_lavc_vdpau_copy = {
@@ -184,6 +153,5 @@ const struct vd_lavc_hwdec mp_vd_lavc_vdpau_copy = {
.init = init_copy,
.uninit = uninit,
.init_decoder = init_decoder,
- .allocate_image = allocate_image,
- .process_image = copy_image,
+ .volatile_context = true,
};
diff --git a/video/decode/videotoolbox.c b/video/decode/hw_videotoolbox.c
index c6f1a472bf..c6f1a472bf 100644
--- a/video/decode/videotoolbox.c
+++ b/video/decode/hw_videotoolbox.c
diff --git a/video/decode/lavc.h b/video/decode/lavc.h
index 993c3ec437..fd1c7feb42 100644
--- a/video/decode/lavc.h
+++ b/video/decode/lavc.h
@@ -7,10 +7,18 @@
#include "demux/stheader.h"
#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
#include "video/hwdec.h"
#define HWDEC_DELAY_QUEUE_COUNT 2
+// Maximum number of surfaces the player wants to buffer.
+// This number might require adjustment depending on whatever the player does;
+// for example, if vo_opengl increases the number of reference surfaces for
+// interpolation, this value has to be increased too.
+// This value does not yet include HWDEC_DELAY_QUEUE_COUNT.
+#define HWDEC_EXTRA_SURFACES 4
+
typedef struct lavc_ctx {
struct mp_log *log;
struct MPOpts *opts;
@@ -25,9 +33,18 @@ typedef struct lavc_ctx {
bool hwdec_failed;
bool hwdec_notified;
+ int framedrop_flags;
+
// For HDR side-data caching
double cached_hdr_peak;
+ bool hw_probing;
+ struct demux_packet **sent_packets;
+ int num_sent_packets;
+
+ struct demux_packet **requeue_packets;
+ int num_requeue_packets;
+
struct mp_image **delay_queue;
int num_delay_queue;
int max_delay_queue;
@@ -45,6 +62,10 @@ typedef struct lavc_ctx {
bool hwdec_request_reinit;
int hwdec_fail_count;
+
+ struct mp_image_pool *hwdec_swpool;
+
+ AVBufferRef *cached_hw_frames_ctx;
} vd_ffmpeg_ctx;
struct vd_lavc_hwdec {
@@ -59,6 +80,8 @@ struct vd_lavc_hwdec {
// efficiency by not blocking on the hardware pipeline by reading back
// immediately after decoding.
int delay_queue;
+ // If true, AVCodecContext will destroy the underlying decoder.
+ bool volatile_context;
int (*probe)(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
const char *codec);
int (*init)(struct lavc_ctx *ctx);
@@ -97,6 +120,8 @@ const struct hwdec_profile_entry *hwdec_find_profile(
bool hwdec_check_codec_support(const char *codec,
const struct hwdec_profile_entry *table);
int hwdec_get_max_refs(struct lavc_ctx *ctx);
+int hwdec_setup_hw_frames_ctx(struct lavc_ctx *ctx, AVBufferRef *device_ctx,
+ int av_sw_format, int initial_pool_size);
const char *hwdec_find_decoder(const char *codec, const char *suffix);
diff --git a/video/decode/vd.h b/video/decode/vd.h
index c4b77f31f1..3897eedc31 100644
--- a/video/decode/vd.h
+++ b/video/decode/vd.h
@@ -33,8 +33,11 @@ typedef struct vd_functions
int (*init)(struct dec_video *vd, const char *decoder);
void (*uninit)(struct dec_video *vd);
int (*control)(struct dec_video *vd, int cmd, void *arg);
- struct mp_image *(*decode)(struct dec_video *vd, struct demux_packet *pkt,
- int flags);
+ // Return whether or not the packet has been consumed.
+ bool (*send_packet)(struct dec_video *vd, struct demux_packet *pkt);
+ // Return whether decoding is still going on (false if EOF was reached).
+ // Never returns false & *out_image set, but can return true with no image.
+ bool (*receive_frame)(struct dec_video *vd, struct mp_image **out_image);
} vd_functions_t;
// NULL terminated array of all drivers
@@ -46,6 +49,8 @@ enum vd_ctrl {
VDCTRL_GET_HWDEC,
VDCTRL_REINIT,
VDCTRL_GET_BFRAMES,
+ // framedrop mode: 0=none, 1=standard, 2=hrseek
+ VDCTRL_SET_FRAMEDROP,
};
#endif /* MPLAYER_VD_H */
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index cc3bbc86c7..54d827896f 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -24,6 +24,7 @@
#include <libavutil/common.h>
#include <libavutil/opt.h>
+#include <libavutil/hwcontext.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/pixdesc.h>
@@ -47,7 +48,7 @@
#include "video/csputils.h"
#include "video/sws_utils.h"
-#if HAVE_AVUTIL_MASTERING_METADATA
+#if LIBAVCODEC_VERSION_MICRO >= 100
#include <libavutil/mastering_display_metadata.h>
#endif
@@ -275,9 +276,13 @@ bool hwdec_check_codec_support(const char *codec,
int hwdec_get_max_refs(struct lavc_ctx *ctx)
{
- if (ctx->avctx->codec_id == AV_CODEC_ID_H264 ||
- ctx->avctx->codec_id == AV_CODEC_ID_HEVC)
+ switch (ctx->avctx->codec_id) {
+ case AV_CODEC_ID_H264:
+ case AV_CODEC_ID_HEVC:
return 16;
+ case AV_CODEC_ID_VP9:
+ return 8;
+ }
return 2;
}
@@ -451,6 +456,7 @@ static int init(struct dec_video *vd, const char *decoder)
ctx->opts = vd->opts;
ctx->decoder = talloc_strdup(ctx, decoder);
ctx->hwdec_devs = vd->hwdec_devs;
+ ctx->hwdec_swpool = talloc_steal(ctx, mp_image_pool_new(17));
reinit(vd);
@@ -466,15 +472,12 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
{
vd_ffmpeg_ctx *ctx = vd->priv;
struct vd_lavc_params *lavc_param = vd->opts->vd_lavc_params;
- bool mp_rawvideo = false;
struct mp_codec_params *c = vd->codec;
assert(!ctx->avctx);
- if (strcmp(decoder, "mp-rawvideo") == 0) {
- mp_rawvideo = true;
+ if (strcmp(decoder, "mp-rawvideo") == 0)
decoder = "rawvideo";
- }
AVCodec *lavc_codec = avcodec_find_decoder_by_name(decoder);
if (!lavc_codec)
@@ -514,15 +517,16 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
if (ctx->hwdec->init && ctx->hwdec->init(ctx) < 0)
goto error;
ctx->max_delay_queue = ctx->hwdec->delay_queue;
+ ctx->hw_probing = true;
} else {
mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads);
}
- avctx->flags |= lavc_param->bitexact ? CODEC_FLAG_BITEXACT : 0;
- avctx->flags2 |= lavc_param->fast ? CODEC_FLAG2_FAST : 0;
+ avctx->flags |= lavc_param->bitexact ? AV_CODEC_FLAG_BITEXACT : 0;
+ avctx->flags2 |= lavc_param->fast ? AV_CODEC_FLAG2_FAST : 0;
if (lavc_param->show_all)
- avctx->flags |= CODEC_FLAG_OUTPUT_CORRUPT;
+ avctx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
avctx->skip_loop_filter = lavc_param->skip_loop_filter;
avctx->skip_idct = lavc_param->skip_idct;
@@ -533,23 +537,11 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
// Do this after the above avopt handling in case it changes values
ctx->skip_frame = avctx->skip_frame;
- avctx->codec_tag = c->codec_tag;
- avctx->coded_width = c->disp_w;
- avctx->coded_height = c->disp_h;
- avctx->bits_per_coded_sample = c->bits_per_coded_sample;
-
- mp_lavc_set_extradata(avctx, c->extradata, c->extradata_size);
-
- if (mp_rawvideo) {
- avctx->pix_fmt = imgfmt2pixfmt(c->codec_tag);
- avctx->codec_tag = 0;
- if (avctx->pix_fmt == AV_PIX_FMT_NONE && c->codec_tag)
- MP_ERR(vd, "Image format %s not supported by lavc.\n",
- mp_imgfmt_to_name(c->codec_tag));
+ if (mp_set_avctx_codec_headers(avctx, c) < 0) {
+ MP_ERR(vd, "Could not set codec parameters.\n");
+ goto error;
}
- mp_set_lav_codec_headers(avctx, c);
-
/* open it */
if (avcodec_open2(avctx, lavc_codec, NULL) < 0)
goto error;
@@ -578,6 +570,14 @@ static void flush_all(struct dec_video *vd)
talloc_free(ctx->delay_queue[n]);
ctx->num_delay_queue = 0;
+ for (int n = 0; n < ctx->num_sent_packets; n++)
+ talloc_free(ctx->sent_packets[n]);
+ ctx->num_sent_packets = 0;
+
+ for (int n = 0; n < ctx->num_requeue_packets; n++)
+ talloc_free(ctx->requeue_packets[n]);
+ ctx->num_requeue_packets = 0;
+
reset_avctx(vd);
}
@@ -587,6 +587,7 @@ static void uninit_avctx(struct dec_video *vd)
flush_all(vd);
av_frame_free(&ctx->pic);
+ av_buffer_unref(&ctx->cached_hw_frames_ctx);
if (ctx->avctx) {
if (avcodec_close(ctx->avctx) < 0)
@@ -604,14 +605,15 @@ static void uninit_avctx(struct dec_video *vd)
ctx->hwdec_failed = false;
ctx->hwdec_fail_count = 0;
ctx->max_delay_queue = 0;
+ ctx->hw_probing = false;
}
static void update_image_params(struct dec_video *vd, AVFrame *frame,
- struct mp_image_params *out_params)
+ struct mp_image_params *params)
{
vd_ffmpeg_ctx *ctx = vd->priv;
-#if HAVE_AVUTIL_MASTERING_METADATA
+#if LIBAVCODEC_VERSION_MICRO >= 100
// 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)
@@ -631,24 +633,62 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
}
#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,
- .color = {
- .space = avcol_spc_to_mp_csp(frame->colorspace),
- .levels = avcol_range_to_mp_csp_levels(frame->color_range),
- .primaries = avcol_pri_to_mp_csp_prim(frame->color_primaries),
- .gamma = avcol_trc_to_mp_csp_trc(frame->color_trc),
- .sig_peak = ctx->cached_hdr_peak,
- },
- .chroma_location =
- avchroma_location_to_mp(ctx->avctx->chroma_sample_location),
- .rotate = vd->codec->rotate,
- .stereo_in = vd->codec->stereo_mode,
- };
+ params->color.sig_peak = ctx->cached_hdr_peak;
+ params->rotate = vd->codec->rotate;
+ params->stereo_in = vd->codec->stereo_mode;
+}
+
+// Allocate and set AVCodecContext.hw_frames_ctx. Also caches them on redundant
+// calls (useful because seeks issue get_format, which clears hw_frames_ctx).
+// device_ctx: reference to an AVHWDeviceContext
+// av_sw_format: AV_PIX_FMT_ for the underlying hardware frame format
+// initial_pool_size: number of frames in the memory pool on creation
+// Return >=0 on success, <0 on error.
+int hwdec_setup_hw_frames_ctx(struct lavc_ctx *ctx, AVBufferRef *device_ctx,
+ int av_sw_format, int initial_pool_size)
+{
+ int w = ctx->avctx->coded_width;
+ int h = ctx->avctx->coded_height;
+ int av_hw_format = imgfmt2pixfmt(ctx->hwdec_fmt);
+
+ if (ctx->cached_hw_frames_ctx) {
+ AVHWFramesContext *fctx = (void *)ctx->cached_hw_frames_ctx->data;
+ if (fctx->width != w || fctx->height != h ||
+ fctx->sw_format != av_sw_format ||
+ fctx->format != av_hw_format)
+ {
+ av_buffer_unref(&ctx->cached_hw_frames_ctx);
+ }
+ }
+
+ if (!ctx->cached_hw_frames_ctx) {
+ ctx->cached_hw_frames_ctx = av_hwframe_ctx_alloc(device_ctx);
+ if (!ctx->cached_hw_frames_ctx)
+ return -1;
+
+ AVHWFramesContext *fctx = (void *)ctx->cached_hw_frames_ctx->data;
+
+ fctx->format = av_hw_format;
+ fctx->sw_format = av_sw_format;
+ fctx->width = w;
+ fctx->height = h;
+
+ fctx->initial_pool_size = initial_pool_size;
+
+ hwdec_lock(ctx);
+ int res = av_hwframe_ctx_init(ctx->cached_hw_frames_ctx);
+ hwdec_unlock(ctx);
+
+ if (res > 0) {
+ MP_ERR(ctx, "Failed to allocate hw frames.\n");
+ av_buffer_unref(&ctx->cached_hw_frames_ctx);
+ return -1;
+ }
+ }
+
+ assert(!ctx->avctx->hw_frames_ctx);
+ ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->cached_hw_frames_ctx);
+ return ctx->avctx->hw_frames_ctx ? 0 : -1;
}
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
@@ -680,7 +720,8 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
ctx->hwdec_h != avctx->coded_height ||
ctx->hwdec_fmt != ctx->hwdec->image_format ||
ctx->hwdec_profile != avctx->profile ||
- ctx->hwdec_request_reinit;
+ ctx->hwdec_request_reinit ||
+ ctx->hwdec->volatile_context;
ctx->hwdec_w = avctx->coded_width;
ctx->hwdec_h = avctx->coded_height;
ctx->hwdec_fmt = ctx->hwdec->image_format;
@@ -729,11 +770,11 @@ static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
int h = pic->height;
if (imgfmt != ctx->hwdec_fmt && w != ctx->hwdec_w && h != ctx->hwdec_h)
- return -1;
+ return AVERROR(EINVAL);
struct mp_image *mpi = ctx->hwdec->allocate_image(ctx, w, h);
if (!mpi)
- return -1;
+ return AVERROR(ENOMEM);
for (int i = 0; i < 4; i++) {
pic->data[i] = mpi->planes[i];
@@ -745,100 +786,114 @@ static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
return 0;
}
-static struct mp_image *read_output(struct dec_video *vd)
+static bool prepare_decoding(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
+ AVCodecContext *avctx = ctx->avctx;
+ struct vd_lavc_params *opts = ctx->opts->vd_lavc_params;
- if (!ctx->num_delay_queue)
- return NULL;
+ if (!avctx || ctx->hwdec_failed)
+ return false;
- struct mp_image *res = ctx->delay_queue[0];
- MP_TARRAY_REMOVE_AT(ctx->delay_queue, ctx->num_delay_queue, 0);
+ int drop = ctx->framedrop_flags;
+ if (drop) {
+ // hr-seek framedrop vs. normal framedrop
+ avctx->skip_frame = drop == 2 ? AVDISCARD_NONREF : opts->framedrop;
+ } else {
+ // normal playback
+ avctx->skip_frame = ctx->skip_frame;
+ }
- if (ctx->hwdec && ctx->hwdec->process_image)
- res = ctx->hwdec->process_image(ctx, res);
+ if (ctx->hwdec_request_reinit)
+ reset_avctx(vd);
- return res ? mp_img_swap_to_native(res) : NULL;
+ return true;
}
-static void decode(struct dec_video *vd, struct demux_packet *packet,
- int flags, struct mp_image **out_image)
+static void handle_err(struct dec_video *vd)
{
- int got_picture = 0;
- int ret;
vd_ffmpeg_ctx *ctx = vd->priv;
- AVCodecContext *avctx = ctx->avctx;
struct vd_lavc_params *opts = ctx->opts->vd_lavc_params;
- bool consumed = false;
- AVPacket pkt;
- if (!avctx)
- return;
+ MP_WARN(vd, "Error while decoding frame!\n");
- if (flags) {
- // hr-seek framedrop vs. normal framedrop
- avctx->skip_frame = flags == 2 ? AVDISCARD_NONREF : opts->framedrop;
- } else {
- // normal playback
- avctx->skip_frame = ctx->skip_frame;
+ if (ctx->hwdec) {
+ ctx->hwdec_fail_count += 1;
+ // The FFmpeg VT hwaccel is buggy and can crash after 1 broken frame.
+ bool vt = ctx->hwdec && ctx->hwdec->type == HWDEC_VIDEOTOOLBOX;
+ if (ctx->hwdec_fail_count >= opts->software_fallback || vt)
+ ctx->hwdec_failed = true;
}
+}
- mp_set_av_packet(&pkt, packet, &ctx->codec_timebase);
- ctx->flushing |= !pkt.data;
+static bool do_send_packet(struct dec_video *vd, struct demux_packet *pkt)
+{
+ vd_ffmpeg_ctx *ctx = vd->priv;
+ AVCodecContext *avctx = ctx->avctx;
- // Reset decoder if hw state got reset, or new data comes during flushing.
- if (ctx->hwdec_request_reinit || (pkt.data && ctx->flushing))
- reset_avctx(vd);
+ if (!prepare_decoding(vd))
+ return false;
+
+ AVPacket avpkt;
+ mp_set_av_packet(&avpkt, pkt, &ctx->codec_timebase);
hwdec_lock(ctx);
- ret = avcodec_send_packet(avctx, packet ? &pkt : NULL);
- if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
- if (ret >= 0)
- consumed = true;
- ret = avcodec_receive_frame(avctx, ctx->pic);
- if (ret >= 0)
- got_picture = 1;
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
- ret = 0;
- } else {
- consumed = true;
- }
+ int ret = avcodec_send_packet(avctx, pkt ? &avpkt : NULL);
hwdec_unlock(ctx);
- // Reset decoder if it was fully flushed. Caller might send more flush
- // packets, or even new actual packets.
- if (ctx->flushing && (ret < 0 || !got_picture))
- reset_avctx(vd);
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ return false;
- if (ret < 0) {
- MP_WARN(vd, "Error while decoding frame!\n");
- if (ctx->hwdec) {
- ctx->hwdec_fail_count += 1;
- // The FFmpeg VT hwaccel is buggy and can crash after 1 broken frame.
- bool vt = ctx->hwdec && ctx->hwdec->type == HWDEC_VIDEOTOOLBOX;
- if (ctx->hwdec_fail_count >= opts->software_fallback || vt)
- ctx->hwdec_failed = true;
- }
- if (!ctx->hwdec_failed && packet)
- packet->len = 0; // skip failed packet
- return;
+ if (ctx->hw_probing && ctx->num_sent_packets < 32) {
+ pkt = pkt ? demux_copy_packet(pkt) : NULL;
+ MP_TARRAY_APPEND(ctx, ctx->sent_packets, ctx->num_sent_packets, pkt);
}
- if (ctx->hwdec && ctx->hwdec_failed) {
- av_frame_unref(ctx->pic);
- return;
+ if (ret < 0)
+ handle_err(vd);
+ return true;
+}
+
+static bool send_packet(struct dec_video *vd, struct demux_packet *pkt)
+{
+ vd_ffmpeg_ctx *ctx = vd->priv;
+
+ if (ctx->num_requeue_packets) {
+ if (do_send_packet(vd, ctx->requeue_packets[0])) {
+ talloc_free(ctx->requeue_packets[0]);
+ MP_TARRAY_REMOVE_AT(ctx->requeue_packets, ctx->num_requeue_packets, 0);
+ }
+ return false;
}
- if (packet && consumed)
- packet->len = 0;
+ return do_send_packet(vd, pkt);
+}
- // Skipped frame, or delayed output due to multithreaded decoding.
- if (!got_picture) {
- if (!packet)
- *out_image = read_output(vd);
- return;
+// Returns whether decoder is still active (!EOF state).
+static bool decode_frame(struct dec_video *vd)
+{
+ vd_ffmpeg_ctx *ctx = vd->priv;
+ AVCodecContext *avctx = ctx->avctx;
+
+ if (!prepare_decoding(vd))
+ return true;
+
+ hwdec_lock(ctx);
+ int ret = avcodec_receive_frame(avctx, ctx->pic);
+ hwdec_unlock(ctx);
+
+ if (ret == AVERROR_EOF) {
+ // If flushing was initialized earlier and has ended now, make it start
+ // over in case we get new packets at some point in the future.
+ reset_avctx(vd);
+ return false;
+ } else if (ret < 0 && ret != AVERROR(EAGAIN)) {
+ handle_err(vd);
}
+ if (!ctx->pic->buf[0])
+ return true;
+
ctx->hwdec_fail_count = 0;
AVFrameSideData *sd = NULL;
@@ -853,7 +908,7 @@ static void decode(struct dec_video *vd, struct demux_packet *packet,
struct mp_image *mpi = mp_image_from_av_frame(ctx->pic);
if (!mpi) {
av_frame_unref(ctx->pic);
- return;
+ return true;
}
assert(mpi->planes[0] || mpi->planes[3]);
mpi->pts = mp_pts_from_av(ctx->pic->pts, &ctx->codec_timebase);
@@ -864,44 +919,83 @@ static void decode(struct dec_video *vd, struct demux_packet *packet,
mp_pts_from_av(av_frame_get_pkt_duration(ctx->pic), &ctx->codec_timebase);
#endif
- struct mp_image_params params;
- update_image_params(vd, ctx->pic, &params);
- mp_image_set_params(mpi, &params);
+ update_image_params(vd, ctx->pic, &mpi->params);
av_frame_unref(ctx->pic);
MP_TARRAY_APPEND(ctx, ctx->delay_queue, ctx->num_delay_queue, mpi);
- if (ctx->num_delay_queue > ctx->max_delay_queue)
- *out_image = read_output(vd);
+ return true;
}
-static struct mp_image *decode_with_fallback(struct dec_video *vd,
- struct demux_packet *packet, int flags)
+static bool receive_frame(struct dec_video *vd, struct mp_image **out_image)
{
vd_ffmpeg_ctx *ctx = vd->priv;
- if (!ctx->avctx)
- return NULL;
- struct mp_image *mpi = NULL;
- decode(vd, packet, flags, &mpi);
+ assert(!*out_image);
+
+ bool progress = decode_frame(vd);
+
if (ctx->hwdec_failed) {
// Failed hardware decoding? Try again in software.
+ struct demux_packet **pkts = ctx->sent_packets;
+ int num_pkts = ctx->num_sent_packets;
+ ctx->sent_packets = NULL;
+ ctx->num_sent_packets = 0;
+
force_fallback(vd);
- if (ctx->avctx)
- decode(vd, packet, flags, &mpi);
+
+ ctx->requeue_packets = pkts;
+ ctx->num_requeue_packets = num_pkts;
+ }
+
+ if (!ctx->num_delay_queue)
+ return progress;
+
+ if (ctx->num_delay_queue <= ctx->max_delay_queue && progress)
+ return true;
+
+ struct mp_image *res = ctx->delay_queue[0];
+ MP_TARRAY_REMOVE_AT(ctx->delay_queue, ctx->num_delay_queue, 0);
+
+ if (ctx->hwdec && ctx->hwdec->process_image)
+ res = ctx->hwdec->process_image(ctx, res);
+
+ res = res ? mp_img_swap_to_native(res) : NULL;
+ if (!res)
+ return progress;
+
+ if (ctx->hwdec && ctx->hwdec->copying && (res->fmt.flags & MP_IMGFLAG_HWACCEL))
+ {
+ struct mp_image *sw = mp_image_hw_download(res, ctx->hwdec_swpool);
+ mp_image_unrefp(&res);
+ res = sw;
+ if (!res) {
+ MP_ERR(vd, "Could not copy back hardware decoded frame.\n");
+ ctx->hwdec_fail_count = INT_MAX - 1; // force fallback
+ handle_err(vd);
+ return NULL;
+ }
}
- if (mpi && !ctx->hwdec_notified && vd->opts->hwdec_api != HWDEC_NONE) {
+ if (!ctx->hwdec_notified && vd->opts->hwdec_api != HWDEC_NONE) {
if (ctx->hwdec) {
MP_INFO(vd, "Using hardware decoding (%s).\n",
m_opt_choice_str(mp_hwdec_names, ctx->hwdec->type));
} else {
- MP_INFO(vd, "Using software decoding.\n");
+ MP_VERBOSE(vd, "Using software decoding.\n");
}
ctx->hwdec_notified = true;
}
- return mpi;
+ if (ctx->hw_probing) {
+ for (int n = 0; n < ctx->num_sent_packets; n++)
+ talloc_free(ctx->sent_packets[n]);
+ ctx->num_sent_packets = 0;
+ ctx->hw_probing = false;
+ }
+
+ *out_image = res;
+ return true;
}
static int control(struct dec_video *vd, int cmd, void *arg)
@@ -911,6 +1005,9 @@ static int control(struct dec_video *vd, int cmd, void *arg)
case VDCTRL_RESET:
flush_all(vd);
return CONTROL_TRUE;
+ case VDCTRL_SET_FRAMEDROP:
+ ctx->framedrop_flags = *(int *)arg;
+ return CONTROL_TRUE;
case VDCTRL_GET_BFRAMES: {
AVCodecContext *avctx = ctx->avctx;
if (!avctx)
@@ -950,5 +1047,6 @@ const struct vd_functions mpcodecs_vd_ffmpeg = {
.init = init,
.uninit = uninit,
.control = control,
- .decode = decode_with_fallback,
+ .send_packet = send_packet,
+ .receive_frame = receive_frame,
};
diff --git a/video/filter/vf.c b/video/filter/vf.c
index 41fbe9b208..94e6760603 100644
--- a/video/filter/vf.c
+++ b/video/filter/vf.c
@@ -20,6 +20,7 @@
#include <string.h>
#include <assert.h>
#include <sys/types.h>
+#include <libavutil/buffer.h>
#include <libavutil/common.h>
#include <libavutil/mem.h>
@@ -644,14 +645,20 @@ int vf_reconfig(struct vf_chain *c, const struct mp_image_params *params)
uint8_t unused[IMGFMT_END - IMGFMT_START];
update_formats(c, c->first, unused);
+ AVBufferRef *hwfctx = c->in_hwframes_ref;
struct vf_instance *failing = NULL;
for (struct vf_instance *vf = c->first; vf; vf = vf->next) {
+ av_buffer_unref(&vf->in_hwframes_ref);
+ av_buffer_unref(&vf->out_hwframes_ref);
+ vf->in_hwframes_ref = hwfctx ? av_buffer_ref(hwfctx) : NULL;
+ vf->out_hwframes_ref = hwfctx ? av_buffer_ref(hwfctx) : NULL;
r = vf_reconfig_wrapper(vf, &cur);
if (r < 0) {
failing = vf;
break;
}
cur = vf->fmt_out;
+ hwfctx = vf->out_hwframes_ref;
}
c->output_params = cur;
c->initialized = r < 0 ? -1 : 1;
@@ -665,6 +672,13 @@ int vf_reconfig(struct vf_chain *c, const struct mp_image_params *params)
return r;
}
+// Hack to get mp_image.hwctx before vf_reconfig()
+void vf_set_proto_frame(struct vf_chain *c, struct mp_image *img)
+{
+ av_buffer_unref(&c->in_hwframes_ref);
+ c->in_hwframes_ref = img && img->hwctx ? av_buffer_ref(img->hwctx) : NULL;
+}
+
struct vf_instance *vf_find_by_label(struct vf_chain *c, const char *label)
{
struct vf_instance *vf = c->first;
@@ -678,6 +692,8 @@ struct vf_instance *vf_find_by_label(struct vf_chain *c, const char *label)
static void vf_uninit_filter(vf_instance_t *vf)
{
+ av_buffer_unref(&vf->in_hwframes_ref);
+ av_buffer_unref(&vf->out_hwframes_ref);
if (vf->uninit)
vf->uninit(vf);
vf_forget_frames(vf);
@@ -729,6 +745,7 @@ void vf_destroy(struct vf_chain *c)
{
if (!c)
return;
+ av_buffer_unref(&c->in_hwframes_ref);
while (c->first) {
vf_instance_t *vf = c->first;
c->first = vf->next;
diff --git a/video/filter/vf.h b/video/filter/vf.h
index 901ccead95..241af02081 100644
--- a/video/filter/vf.h
+++ b/video/filter/vf.h
@@ -90,6 +90,9 @@ typedef struct vf_instance {
struct mp_image_params fmt_in, fmt_out;
+ // This is a dirty hack.
+ struct AVBufferRef *in_hwframes_ref, *out_hwframes_ref;
+
struct mp_image_pool *out_pool;
struct vf_priv_s *priv;
struct mp_log *log;
@@ -123,6 +126,9 @@ struct vf_chain {
struct mpv_global *global;
struct mp_hwdec_devices *hwdec_devs;
+ // This is a dirty hack.
+ struct AVBufferRef *in_hwframes_ref;
+
// Call when the filter chain wants new processing (for filters with
// asynchronous behavior) - must be immutable once filters are created,
// since they are supposed to call it from foreign threads.
@@ -150,6 +156,7 @@ enum vf_ctrl {
struct vf_chain *vf_new(struct mpv_global *global);
void vf_destroy(struct vf_chain *c);
+void vf_set_proto_frame(struct vf_chain *c, struct mp_image *img);
int vf_reconfig(struct vf_chain *c, const struct mp_image_params *params);
int vf_control_any(struct vf_chain *c, int cmd, void *arg);
int vf_control_by_label(struct vf_chain *c, int cmd, void *arg, bstr label);
diff --git a/video/filter/vf_lavfi.c b/video/filter/vf_lavfi.c
index a66bd2a971..d79bfd4011 100644
--- a/video/filter/vf_lavfi.c
+++ b/video/filter/vf_lavfi.c
@@ -26,6 +26,7 @@
#include <assert.h>
#include <libavutil/avstring.h>
+#include <libavutil/hwcontext.h>
#include <libavutil/mem.h>
#include <libavutil/mathematics.h>
#include <libavutil/rational.h>
@@ -44,6 +45,7 @@
#include "options/m_option.h"
#include "common/tags.h"
+#include "video/hwdec.h"
#include "video/img_format.h"
#include "video/mp_image.h"
#include "video/sws_utils.h"
@@ -108,7 +110,8 @@ static bool recreate_graph(struct vf_instance *vf, struct mp_image_params *fmt)
{
void *tmp = talloc_new(NULL);
struct vf_priv_s *p = vf->priv;
- AVFilterContext *in = NULL, *out = NULL, *f_format = NULL;
+ AVFilterContext *in = NULL, *out = NULL;
+ int ret;
if (bstr0(p->cfg_graph).len == 0) {
MP_FATAL(vf, "lavfi: no filter graph set\n");
@@ -130,53 +133,56 @@ static bool recreate_graph(struct vf_instance *vf, struct mp_image_params *fmt)
if (!outputs || !inputs)
goto error;
- // Build list of acceptable output pixel formats. libavfilter will insert
- // conversion filters if needed.
- char *fmtstr = talloc_strdup(tmp, "");
- for (int n = IMGFMT_START; n < IMGFMT_END; n++) {
- if (vf_next_query_format(vf, n)) {
- const char *name = av_get_pix_fmt_name(imgfmt2pixfmt(n));
- if (name) {
- const char *s = fmtstr[0] ? "|" : "";
- fmtstr = talloc_asprintf_append_buffer(fmtstr, "%s%s", s, name);
- }
- }
- }
-
char *sws_flags = talloc_asprintf(tmp, "flags=%"PRId64, p->cfg_sws_flags);
graph->scale_sws_opts = av_strdup(sws_flags);
- AVRational timebase = AV_TIME_BASE_Q;
-
- char *src_args = talloc_asprintf(tmp, "%d:%d:%d:%d:%d:%d:%d",
- fmt->w, fmt->h, imgfmt2pixfmt(fmt->imgfmt),
- timebase.num, timebase.den,
- fmt->p_w, fmt->p_h);
+ in = avfilter_graph_alloc_filter(graph, avfilter_get_by_name("buffer"), "src");
+ if (!in)
+ goto error;
- if (avfilter_graph_create_filter(&in, avfilter_get_by_name("buffer"),
- "src", src_args, NULL, graph) < 0)
+ AVBufferSrcParameters *in_params = av_buffersrc_parameters_alloc();
+ if (!in_params)
goto error;
- if (avfilter_graph_create_filter(&out, avfilter_get_by_name("buffersink"),
- "out", NULL, NULL, graph) < 0)
+ in_params->format = imgfmt2pixfmt(fmt->imgfmt);
+ in_params->time_base = AV_TIME_BASE_Q;
+ in_params->width = fmt->w;
+ in_params->height = fmt->h;
+ in_params->sample_aspect_ratio.num = fmt->p_w;
+ in_params->sample_aspect_ratio.den = fmt->p_h;
+ // Assume it's ignored for non-hwaccel formats.
+ in_params->hw_frames_ctx = vf->in_hwframes_ref;
+
+ ret = av_buffersrc_parameters_set(in, in_params);
+ av_free(in_params);
+ if (ret < 0)
goto error;
- if (avfilter_graph_create_filter(&f_format, avfilter_get_by_name("format"),
- "format", fmtstr, NULL, graph) < 0)
+ if (avfilter_init_str(in, NULL) < 0)
goto error;
- if (avfilter_link(f_format, 0, out, 0) < 0)
+ if (avfilter_graph_create_filter(&out, avfilter_get_by_name("buffersink"),
+ "out", NULL, NULL, graph) < 0)
goto error;
outputs->name = av_strdup("in");
outputs->filter_ctx = in;
inputs->name = av_strdup("out");
- inputs->filter_ctx = f_format;
+ inputs->filter_ctx = out;
if (graph_parse(graph, p->cfg_graph, inputs, outputs, NULL) < 0)
goto error;
+ if (vf->hwdec_devs) {
+ struct mp_hwdec_ctx *hwdec = hwdec_devices_get_first(vf->hwdec_devs);
+ for (int n = 0; n < graph->nb_filters; n++) {
+ AVFilterContext *filter = graph->filters[n];
+ if (hwdec && hwdec->av_device_ref)
+ filter->hw_device_ctx = av_buffer_ref(hwdec->av_device_ref);
+ }
+ }
+
if (avfilter_graph_config(graph, NULL) < 0)
goto error;
@@ -233,17 +239,25 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
out->p_w = l_out->sample_aspect_ratio.num;
out->p_h = l_out->sample_aspect_ratio.den;
out->imgfmt = pixfmt2imgfmt(l_out->format);
+ av_buffer_unref(&vf->out_hwframes_ref);
+#if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(6, 69, 100) && \
+ LIBAVFILTER_VERSION_MICRO >= 100
+ AVBufferRef *hw_frames_ctx = av_buffersink_get_hw_frames_ctx(p->out);
+#else
+ AVBufferRef *hw_frames_ctx = l_out->hw_frames_ctx;
+#endif
+ if (hw_frames_ctx) {
+ AVHWFramesContext *fctx = (void *)hw_frames_ctx->data;
+ out->hw_subfmt = pixfmt2imgfmt(fctx->sw_format);
+ vf->out_hwframes_ref = av_buffer_ref(hw_frames_ctx);
+ }
return 0;
}
static int query_format(struct vf_instance *vf, unsigned int fmt)
{
- // We accept all sws-convertable formats as inputs. Output formats are
- // handled in config(). The current public libavfilter API doesn't really
- // allow us to do anything more sophisticated.
- // This breaks with filters which accept input pixel formats not
- // supported by libswscale.
- return !!mp_sws_supported_format(fmt);
+ // Format negotiation is not possible with libavfilter.
+ return 1;
}
static AVFrame *mp_to_av(struct vf_instance *vf, struct mp_image *img)
@@ -275,7 +289,7 @@ static struct mp_image *av_to_mp(struct vf_instance *vf, AVFrame *av_frame)
static void get_metadata_from_av_frame(struct vf_instance *vf, AVFrame *frame)
{
-#if HAVE_AVFRAME_METADATA
+#if LIBAVUTIL_VERSION_MICRO >= 100
struct vf_priv_s *p = vf->priv;
if (!p->metadata)
p->metadata = talloc_zero(p, struct mp_tags);
@@ -298,6 +312,12 @@ static int filter_ext(struct vf_instance *vf, struct mp_image *mpi)
if (!p->graph)
return -1;
+ if (!mpi) {
+ if (p->eof)
+ return 0;
+ p->eof = true;
+ }
+
AVFrame *frame = mp_to_av(vf, mpi);
int r = av_buffersrc_add_frame(p->in, frame) < 0 ? -1 : 0;
av_frame_free(&frame);
diff --git a/video/hwdec.h b/video/hwdec.h
index f2fa7943af..9d1035cd6e 100644
--- a/video/hwdec.h
+++ b/video/hwdec.h
@@ -47,7 +47,14 @@ struct mp_hwdec_ctx {
// HWDEC_CUDA: CUcontext*
void *ctx;
- // Optional.
+ // libavutil-wrapped context, if available.
+ struct AVBufferRef *av_device_ref; // AVVAAPIDeviceContext*
+
+ // List of IMGFMT_s, terminated with 0. NULL if N/A.
+ int *supported_formats;
+
+ // Optional. Legacy. (New code should use AVHWFramesContext and
+ // mp_image_hw_download().)
// Allocates a software image from the pool, downloads the hw image from
// mpi, and returns it.
// pool can be NULL (then just use straight allocation).
diff --git a/video/mp_image.c b/video/mp_image.c
index 2c4627c33e..37d8f67343 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -27,6 +27,7 @@
#include <libavutil/mem.h>
#include <libavutil/common.h>
#include <libavutil/bswap.h>
+#include <libavutil/hwcontext.h>
#include <libavutil/rational.h>
#include <libavcodec/avcodec.h>
@@ -706,6 +707,20 @@ static void mp_image_copy_fields_from_av_frame(struct mp_image *dst,
dst->fields |= MP_IMGFIELD_TOP_FIRST;
if (src->repeat_pict == 1)
dst->fields |= MP_IMGFIELD_REPEAT_FIRST;
+
+ if (src->hw_frames_ctx) {
+ AVHWFramesContext *fctx = (void *)src->hw_frames_ctx->data;
+ dst->params.hw_subfmt = pixfmt2imgfmt(fctx->sw_format);
+ }
+
+ dst->params.color = (struct mp_colorspace){
+ .space = avcol_spc_to_mp_csp(src->colorspace),
+ .levels = avcol_range_to_mp_csp_levels(src->color_range),
+ .primaries = avcol_pri_to_mp_csp_prim(src->color_primaries),
+ .gamma = avcol_trc_to_mp_csp_trc(src->color_trc),
+ };
+
+ dst->params.chroma_location = avchroma_location_to_mp(src->chroma_location);
}
// Copy properties and data of the mp_image into the AVFrame, without taking
@@ -736,6 +751,11 @@ static void mp_image_copy_fields_to_av_frame(struct AVFrame *dst,
dst->colorspace = mp_csp_to_avcol_spc(src->params.color.space);
dst->color_range = mp_csp_levels_to_avcol_range(src->params.color.levels);
+ dst->color_primaries =
+ mp_csp_prim_to_avcol_pri(src->params.color.primaries);
+ dst->color_trc = mp_csp_trc_to_avcol_trc(src->params.color.gamma);
+
+ dst->chroma_location = mp_chroma_location_to_av(src->params.chroma_location);
}
// Create a new mp_image reference to av_frame.
diff --git a/video/mp_image_pool.c b/video/mp_image_pool.c
index 10966eeefc..9a848af925 100644
--- a/video/mp_image_pool.c
+++ b/video/mp_image_pool.c
@@ -23,12 +23,15 @@
#include <assert.h>
#include <libavutil/buffer.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/mem.h>
#include "mpv_talloc.h"
#include "common/common.h"
-#include "video/mp_image.h"
+#include "fmt-conversion.h"
+#include "mp_image.h"
#include "mp_image_pool.h"
static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -247,3 +250,62 @@ void mp_image_pool_set_lru(struct mp_image_pool *pool)
{
pool->use_lru = true;
}
+
+
+// Copies the contents of the HW surface img to system memory and retuns it.
+// If swpool is not NULL, it's used to allocate the target image.
+// img must be a hw surface with a AVHWFramesContext attached. If not, you
+// must use the legacy mp_hwdec_ctx.download_image.
+// The returned image is cropped as needed.
+// Returns NULL on failure.
+struct mp_image *mp_image_hw_download(struct mp_image *src,
+ struct mp_image_pool *swpool)
+{
+ if (!src->hwctx)
+ return NULL;
+ AVHWFramesContext *fctx = (void *)src->hwctx->data;
+
+ // Try to find the first format which we can apparently use.
+ int imgfmt = 0;
+ enum AVPixelFormat *fmts;
+ if (av_hwframe_transfer_get_formats(src->hwctx,
+ AV_HWFRAME_TRANSFER_DIRECTION_FROM, &fmts, 0) < 0)
+ return NULL;
+ for (int n = 0; fmts[n] != AV_PIX_FMT_NONE; n++) {
+ imgfmt = pixfmt2imgfmt(fmts[n]);
+ if (imgfmt)
+ break;
+ }
+ av_free(fmts);
+
+ if (!imgfmt)
+ return NULL;
+
+ struct mp_image *dst =
+ mp_image_pool_get(swpool, imgfmt, fctx->width, fctx->height);
+ if (!dst)
+ return NULL;
+
+ // Target image must be writable, so unref it.
+ AVFrame *dstav = mp_image_to_av_frame_and_unref(dst);
+ if (!dstav)
+ return NULL;
+
+ AVFrame *srcav = mp_image_to_av_frame(src);
+ if (!srcav) {
+ av_frame_unref(dstav);
+ return NULL;
+ }
+
+ int res = av_hwframe_transfer_data(dstav, srcav, 0);
+ av_frame_unref(srcav);
+ dst = mp_image_from_av_frame(dstav);
+ av_frame_unref(dstav);
+ if (res >= 0 && dst) {
+ mp_image_set_size(dst, src->w, src->h);
+ mp_image_copy_attributes(dst, src);
+ } else {
+ mp_image_unrefp(&dst);
+ }
+ return dst;
+}
diff --git a/video/mp_image_pool.h b/video/mp_image_pool.h
index 32beb89d9b..95e4ae57be 100644
--- a/video/mp_image_pool.h
+++ b/video/mp_image_pool.h
@@ -26,4 +26,7 @@ struct mp_image *mp_image_pool_new_copy(struct mp_image_pool *pool,
bool mp_image_pool_make_writeable(struct mp_image_pool *pool,
struct mp_image *img);
+struct mp_image *mp_image_hw_download(struct mp_image *img,
+ struct mp_image_pool *swpool);
+
#endif
diff --git a/video/out/cocoa/events_view.m b/video/out/cocoa/events_view.m
index d377597006..f76ca0d617 100644
--- a/video/out/cocoa/events_view.m
+++ b/video/out/cocoa/events_view.m
@@ -313,23 +313,14 @@
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
- if ([[pboard types] containsObject:NSURLPboardType]) {
- NSArray *pbitems = [pboard readObjectsForClasses:@[[NSURL class]]
- options:@{}];
- NSMutableArray* ar = [[[NSMutableArray alloc] init] autorelease];
- for (NSURL* url in pbitems) {
- if (url.fileURL) {
- [ar addObject:[url path]];
- } else {
- [ar addObject:[url absoluteString]];
- }
- }
- [self.adapter handleFilesArray:ar];
- return YES;
- } else if ([[pboard types] containsObject:NSFilenamesPboardType]) {
+ if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *pbitems = [pboard propertyListForType:NSFilenamesPboardType];
[self.adapter handleFilesArray:pbitems];
return YES;
+ } else if ([[pboard types] containsObject:NSURLPboardType]) {
+ NSURL *url = [NSURL URLFromPasteboard:pboard];
+ [self.adapter handleFilesArray:@[[url absoluteString]]];
+ return YES;
}
return NO;
}
diff --git a/video/out/cocoa/window.h b/video/out/cocoa/window.h
index 485831a932..af04b2b1c6 100644
--- a/video/out/cocoa/window.h
+++ b/video/out/cocoa/window.h
@@ -18,11 +18,12 @@
#import <Cocoa/Cocoa.h>
#import "video/out/cocoa/mpvadapter.h"
-@protocol MpvSizing
+@protocol MpvWindowUpdate
- (void)queueNewVideoSize:(NSSize)newSize;
+- (void)updateBorder:(int)border;
@end
-@interface MpvVideoWindow : NSWindow <NSWindowDelegate, MpvSizing>
+@interface MpvVideoWindow : NSWindow <NSWindowDelegate, MpvWindowUpdate>
@property(nonatomic, retain) MpvCocoaAdapter *adapter;
- (BOOL)canBecomeKeyWindow;
- (BOOL)canBecomeMainWindow;
diff --git a/video/out/cocoa/window.m b/video/out/cocoa/window.m
index 68e5222a03..1ff57acde9 100644
--- a/video/out/cocoa/window.m
+++ b/video/out/cocoa/window.m
@@ -49,20 +49,22 @@
styleMask:(NSUInteger)style_mask
backing:(NSBackingStoreType)buffering_type
defer:(BOOL)flag
+ screen:(NSScreen *)screen
{
if (self = [super initWithContentRect:content_rect
styleMask:style_mask
backing:buffering_type
- defer:flag]) {
+ defer:flag
+ screen:screen]) {
[self setBackgroundColor:[NSColor blackColor]];
[self setMinSize:NSMakeSize(50,50)];
[self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
- self.targetScreen = [self screen];
- self.currentScreen = [self screen];
+ self.targetScreen = screen;
+ self.currentScreen = screen;
_is_animating = 0;
_unfs_content_frame = [self convertRectToScreen:[[self contentView] frame]];
- _unfs_screen_frame = [[self screen] frame];
+ _unfs_screen_frame = [screen frame];
}
return self;
}
@@ -97,18 +99,28 @@
[super toggleFullScreen:sender];
if (![self.adapter isInFullScreenMode]) {
- [self setStyleMask:([self styleMask] | NSWindowStyleMaskFullScreen)];
- NSRect frame = [[self targetScreen] frame];
- [self setFrame:frame display:YES];
+ [self setToFullScreen];
} else {
- [self setStyleMask:([self styleMask] & ~NSWindowStyleMaskFullScreen)];
- NSRect frame = [self calculateWindowPositionForScreen:[self targetScreen]];
- [self setFrame:frame display:YES];
- [self setContentAspectRatio:_unfs_content_frame.size];
- [self setCenteredContentSize:_unfs_content_frame.size];
+ [self setToWindow];
}
}
+- (void)setToFullScreen
+{
+ [self setStyleMask:([self styleMask] | NSWindowStyleMaskFullScreen)];
+ NSRect frame = [[self targetScreen] frame];
+ [self setFrame:frame display:YES];
+}
+
+- (void)setToWindow
+{
+ [self setStyleMask:([self styleMask] & ~NSWindowStyleMaskFullScreen)];
+ NSRect frame = [self calculateWindowPositionForScreen:[self targetScreen]];
+ [self setFrame:frame display:YES];
+ [self setContentAspectRatio:_unfs_content_frame.size];
+ [self setCenteredContentSize:_unfs_content_frame.size];
+}
+
- (NSArray *)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
{
return [NSArray arrayWithObject:window];
@@ -139,11 +151,13 @@
- (void)windowDidFailToEnterFullScreen:(NSWindow *)window
{
_is_animating = 0;
+ [self setToWindow];
}
- (void)windowDidFailToExitFullScreen:(NSWindow *)window
{
_is_animating = 0;
+ [self setToFullScreen];
}
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
@@ -159,8 +173,10 @@
if (!_is_animating && ![[self currentScreen] isEqual:[self screen]]) {
self.previousScreen = [self screen];
}
+ if (![[self currentScreen] isEqual:[self screen]]) {
+ [self.adapter windowDidChangeScreen:notification];
+ }
self.currentScreen = [self screen];
- [self.adapter windowDidChangeScreen:notification];
}
- (void)windowDidChangeScreenProfile:(NSNotification *)notification
@@ -212,6 +228,34 @@
[self.adapter putCommand:cmd];
}
+- (void)updateBorder:(int)border
+{
+ int borderStyle = NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|
+ NSWindowStyleMaskMiniaturizable;
+ if (border) {
+ int window_mask = [self styleMask] & ~NSWindowStyleMaskBorderless;
+ window_mask |= borderStyle;
+ [self setStyleMask:window_mask];
+ } else {
+ int window_mask = [self styleMask] & ~borderStyle;
+ window_mask |= NSWindowStyleMaskBorderless;
+ [self setStyleMask:window_mask];
+ }
+
+ if (![self.adapter isInFullScreenMode]) {
+ // XXX: workaround to force redrawing of window decoration
+ if (border) {
+ NSRect frame = [self frame];
+ frame.size.width += 1;
+ [self setFrame:frame display:YES];
+ frame.size.width -= 1;
+ [self setFrame:frame display:YES];
+ }
+
+ [self setContentAspectRatio:_unfs_content_frame.size];
+ }
+}
+
- (NSRect)frameRect:(NSRect)f forCenteredContentSize:(NSSize)ns
{
NSRect cr = [self contentRectForFrameRect:f];
@@ -259,11 +303,12 @@
- (NSRect)constrainFrameRect:(NSRect)nf toScreen:(NSScreen *)screen
{
- if (_is_animating)
- screen = [self targetScreen];
+ if (_is_animating && ![self.adapter isInFullScreenMode])
+ return nf;
+ screen = screen ?: self.screen ?: [NSScreen mainScreen];
NSRect of = [self frame];
- NSRect vf = [screen ?: self.screen ?: [NSScreen mainScreen] visibleFrame];
+ NSRect vf = [_is_animating ? [self targetScreen] : screen visibleFrame];
NSRect ncf = [self contentRectForFrameRect:nf];
// Prevent the window's titlebar from exiting the screen on the top edge.
@@ -304,11 +349,6 @@
display:NO];
}
-- (void)updateWindowFrame:(NSSize)newSize
-{
- _unfs_content_frame = [self frameRect:_unfs_content_frame forCenteredContentSize:newSize];
-}
-
- (void)tryDequeueSize
{
if (_queued_video_size.width <= 0.0 || _queued_video_size.height <= 0.0)
@@ -321,9 +361,8 @@
- (void)queueNewVideoSize:(NSSize)newSize
{
- if ([self.adapter isInFullScreenMode]) {
- [self updateWindowFrame:newSize];
- } else {
+ _unfs_content_frame = [self frameRect:_unfs_content_frame forCenteredContentSize:newSize];
+ if (![self.adapter isInFullScreenMode]) {
if (NSEqualSizes(_queued_video_size, newSize))
return;
_queued_video_size = newSize;
diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m
index 164663c332..0420b0dda2 100644
--- a/video/out/cocoa_common.m
+++ b/video/out/cocoa_common.m
@@ -69,7 +69,6 @@ struct vo_cocoa_state {
NSOpenGLContext *nsgl_ctx;
NSScreen *current_screen;
- double screen_fps;
NSInteger window_level;
int fullscreen;
@@ -87,6 +86,11 @@ struct vo_cocoa_state {
uint32_t old_dwidth;
uint32_t old_dheight;
+ CVDisplayLinkRef link;
+ pthread_mutex_t sync_lock;
+ pthread_cond_t sync_wakeup;
+ uint64_t sync_counter;
+
pthread_mutex_t lock;
pthread_cond_t wakeup;
@@ -113,14 +117,36 @@ static void run_on_main_thread(struct vo *vo, void(^block)(void))
dispatch_sync(dispatch_get_main_queue(), block);
}
+static NSRect calculate_window_geometry(struct vo *vo, NSRect rect)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ struct mp_vo_opts *opts = vo->opts;
+
+ NSRect screenFrame = [s->current_screen frame];
+ rect.origin.y = screenFrame.size.height - (rect.origin.y + rect.size.height);
+
+ if(!opts->hidpi_window_scale) {
+ NSRect oldRect = rect;
+ rect = [s->current_screen convertRectFromBacking:rect];
+
+ CGFloat x_per = screenFrame.size.width - oldRect.size.width;
+ CGFloat y_per = screenFrame.size.height - oldRect.size.height;
+ if (x_per > 0) x_per = oldRect.origin.x/x_per;
+ if (y_per > 0) y_per = oldRect.origin.y/y_per;
+
+ rect.origin.x = (screenFrame.size.width - rect.size.width)*x_per;
+ rect.origin.y = (screenFrame.size.height - rect.size.height)*y_per;
+ }
+
+ return rect;
+}
+
static void queue_new_video_size(struct vo *vo, int w, int h)
{
struct vo_cocoa_state *s = vo->cocoa;
struct mp_vo_opts *opts = vo->opts;
- id<MpvSizing> win = (id<MpvSizing>) s->window;
- NSRect r = NSMakeRect(0, 0, w, h);
- if(!opts->hidpi_window_scale)
- r = [s->current_screen convertRectFromBacking:r];
+ id<MpvWindowUpdate> win = (id<MpvWindowUpdate>) s->window;
+ NSRect r = calculate_window_geometry(vo, NSMakeRect(0, 0, w, h));
[win queueNewVideoSize:NSMakeSize(r.size.width, r.size.height)];
}
@@ -283,6 +309,26 @@ static void vo_cocoa_update_screen_info(struct vo *vo)
}
}
+static void vo_cocoa_init_displaylink(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+
+ NSDictionary* sinfo = [s->current_screen deviceDescription];
+ NSNumber* sid = [sinfo objectForKey:@"NSScreenNumber"];
+ CGDirectDisplayID did = [sid longValue];
+
+ CVDisplayLinkCreateWithCGDisplay(did, &s->link);
+ CVDisplayLinkSetOutputCallback(s->link, &displayLinkCallback, vo);
+ CVDisplayLinkStart(s->link);
+}
+
+static void vo_cocoa_uninit_displaylink(struct vo_cocoa_state *s)
+{
+ if (CVDisplayLinkIsRunning(s->link))
+ CVDisplayLinkStop(s->link);
+ CVDisplayLinkRelease(s->link);
+}
+
void vo_cocoa_init(struct vo *vo)
{
struct vo_cocoa_state *s = talloc_zero(NULL, struct vo_cocoa_state);
@@ -299,8 +345,11 @@ void vo_cocoa_init(struct vo *vo)
}
pthread_mutex_init(&s->lock, NULL);
pthread_cond_init(&s->wakeup, NULL);
+ pthread_mutex_init(&s->sync_lock, NULL);
+ pthread_cond_init(&s->sync_wakeup, NULL);
vo->cocoa = s;
vo_cocoa_update_screen_info(vo);
+ vo_cocoa_init_displaylink(vo);
cocoa_init_light_sensor(vo);
cocoa_add_screen_reconfiguration_observer(vo);
if (!s->embedded) {
@@ -338,8 +387,22 @@ void vo_cocoa_uninit(struct vo *vo)
pthread_cond_signal(&s->wakeup);
pthread_mutex_unlock(&s->lock);
+ // close window beforehand to prevent undefined behavior when in fullscreen
+ // that resets the desktop to space 1
+ run_on_main_thread(vo, ^{
+ // if using --wid + libmpv there's no window to release
+ if (s->window) {
+ [s->window setDelegate:nil];
+ [s->window close];
+ }
+ });
+
run_on_main_thread(vo, ^{
enable_power_management(s);
+ vo_cocoa_uninit_displaylink(s);
+ pthread_mutex_lock(&s->sync_lock);
+ pthread_cond_signal(&s->sync_wakeup);
+ pthread_mutex_unlock(&s->sync_lock);
cocoa_uninit_light_sensor(s);
cocoa_rm_screen_reconfiguration_observer(vo);
@@ -353,60 +416,61 @@ void vo_cocoa_uninit(struct vo *vo)
[s->view removeFromSuperview];
[s->view release];
- // if using --wid + libmpv there's no window to release
- if (s->window) {
- [s->window setDelegate:nil];
- [s->window close];
- }
-
if (!s->embedded)
[s->blankCursor release];
+ pthread_cond_destroy(&s->sync_wakeup);
+ pthread_mutex_destroy(&s->sync_lock);
pthread_cond_destroy(&s->wakeup);
pthread_mutex_destroy(&s->lock);
talloc_free(s);
});
}
-static void vo_cocoa_update_screen_fps(struct vo *vo)
+static void vo_cocoa_update_displaylink(struct vo *vo)
{
struct vo_cocoa_state *s = vo->cocoa;
- NSDictionary* sinfo = [s->current_screen deviceDescription];
- NSNumber* sid = [sinfo objectForKey:@"NSScreenNumber"];
- CGDirectDisplayID did = [sid longValue];
+ vo_cocoa_uninit_displaylink(s);
+ vo_cocoa_init_displaylink(vo);
+}
- CVDisplayLinkRef link;
- CVDisplayLinkCreateWithCGDisplay(did, &link);
- CVDisplayLinkSetOutputCallback(link, &displayLinkCallback, NULL);
- CVDisplayLinkStart(link);
- CVDisplayLinkSetCurrentCGDisplay(link, did);
+static double vo_cocoa_update_screen_fps(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ double actual_fps = CVDisplayLinkGetActualOutputVideoRefreshPeriod(s->link);
+ const CVTime t = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(s->link);
- double display_period = CVDisplayLinkGetActualOutputVideoRefreshPeriod(link);
+ if (!(t.flags & kCVTimeIsIndefinite)) {
+ double nominal_fps = (t.timeScale / (double) t.timeValue);
- if (display_period > 0) {
- s->screen_fps = 1/display_period;
- } else {
- // Fallback to using Nominal refresh rate from DisplayLink,
- // CVDisplayLinkGet *Actual* OutputVideoRefreshPeriod seems to
- // return 0 on some Apple devices. Use the nominal refresh period
- // instead.
- const CVTime t = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
- if (!(t.flags & kCVTimeIsIndefinite)) {
- s->screen_fps = (t.timeScale / (double) t.timeValue);
- MP_VERBOSE(vo, "Falling back to %f for display sync.\n", s->screen_fps);
+ if (actual_fps > 0)
+ actual_fps = 1/actual_fps;
+
+ if (fabs(actual_fps - nominal_fps) > 0.1) {
+ MP_VERBOSE(vo, "Falling back to nominal display "
+ "refresh rate: %fHz\n", nominal_fps);
+ return nominal_fps;
+ } else {
+ return actual_fps;
}
}
- CVDisplayLinkRelease(link);
-
- flag_events(vo, VO_EVENT_WIN_STATE);
+ MP_WARN(vo, "Falling back to standard display refresh rate: 60Hz\n");
+ return 60.0;
}
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now,
const CVTimeStamp* outputTime, CVOptionFlags flagsIn,
CVOptionFlags* flagsOut, void* displayLinkContext)
{
+ struct vo *vo = displayLinkContext;
+ struct vo_cocoa_state *s = vo->cocoa;
+
+ pthread_mutex_lock(&s->sync_lock);
+ s->sync_counter += 1;
+ pthread_cond_signal(&s->sync_wakeup);
+ pthread_mutex_unlock(&s->sync_lock);
return kCVReturnSuccess;
}
@@ -422,8 +486,10 @@ static void vo_set_level(struct vo *vo, int ontop)
s->window_level = NSNormalWindowLevel;
}
- [[s->view window] setLevel:s->window_level];
- [s->window setLevel:s->window_level];
+ [s->window setLevel:s->window_level];
+ NSWindowCollectionBehavior behavior = [s->window collectionBehavior] &
+ ~NSWindowCollectionBehaviorTransient;
+ [s->window setCollectionBehavior:behavior|NSWindowCollectionBehaviorManaged];
}
static int vo_cocoa_ontop(struct vo *vo)
@@ -472,10 +538,8 @@ static void create_ui(struct vo *vo, struct mp_rect *win, int geo_flags)
if (s->embedded) {
parent = (NSView *) (intptr_t) opts->WinID;
} else {
- NSRect wr =
- NSMakeRect(win->x0, win->y0, win->x1 - win->x0, win->y1 - win->y0);
- if(!opts->hidpi_window_scale)
- wr = [s->current_screen convertRectFromBacking:wr];
+ NSRect wr = calculate_window_geometry(vo,
+ NSMakeRect(win->x0, win->y0, win->x1 - win->x0, win->y1 - win->y0));
s->window = create_window(wr, s->current_screen, opts->border, adapter);
parent = [s->window contentView];
}
@@ -532,13 +596,36 @@ static int cocoa_set_window_title(struct vo *vo)
return VO_TRUE;
}
+static int vo_cocoa_window_border(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ if (s->embedded)
+ return VO_NOTIMPL;
+
+ struct mp_vo_opts *opts = vo->opts;
+ id<MpvWindowUpdate> win = (id<MpvWindowUpdate>) s->window;
+ [win updateBorder:opts->border];
+ if (opts->border)
+ cocoa_set_window_title(vo);
+
+ return VO_TRUE;
+}
+
static void cocoa_screen_reconfiguration_observer(
CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *ctx)
{
if (flags & kCGDisplaySetModeFlag) {
struct vo *vo = ctx;
- MP_WARN(vo, "detected display mode change, updating screen info\n");
- vo_cocoa_update_screen_fps(vo);
+ struct vo_cocoa_state *s = vo->cocoa;
+
+ NSDictionary* sinfo = [s->current_screen deviceDescription];
+ NSNumber* sid = [sinfo objectForKey:@"NSScreenNumber"];
+ CGDirectDisplayID did = [sid longValue];
+
+ if (did == display) {
+ MP_VERBOSE(vo, "detected display mode change, updating screen refresh rate\n");
+ flag_events(vo, VO_EVENT_WIN_STATE);
+ }
}
}
@@ -569,8 +656,6 @@ int vo_cocoa_config_window(struct vo *vo)
struct mp_vo_opts *opts = vo->opts;
run_on_main_thread(vo, ^{
- vo_cocoa_update_screen_fps(vo);
-
NSRect r = [s->current_screen frame];
struct mp_rect screenrc = {0, 0, r.size.width, r.size.height};
struct vo_win_geometry geo;
@@ -666,6 +751,13 @@ void vo_cocoa_swap_buffers(struct vo *vo)
if (skip)
return;
+ pthread_mutex_lock(&s->sync_lock);
+ uint64_t old_counter = s->sync_counter;
+ while(old_counter == s->sync_counter) {
+ pthread_cond_wait(&s->sync_wakeup, &s->sync_lock);
+ }
+ pthread_mutex_unlock(&s->sync_lock);
+
pthread_mutex_lock(&s->lock);
s->frame_w = vo->dwidth;
s->frame_h = vo->dheight;
@@ -727,6 +819,8 @@ static int vo_cocoa_control_on_main_thread(struct vo *vo, int request, void *arg
return VO_TRUE;
case VOCTRL_ONTOP:
return vo_cocoa_ontop(vo);
+ case VOCTRL_BORDER:
+ return vo_cocoa_window_border(vo);
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
int *sz = arg;
NSSize size = [s->view frame].size;
@@ -764,10 +858,8 @@ static int vo_cocoa_control_on_main_thread(struct vo *vo, int request, void *arg
vo_cocoa_control_get_icc_profile(vo, arg);
return VO_TRUE;
case VOCTRL_GET_DISPLAY_FPS:
- if (s->screen_fps > 0.0) {
- *(double *)arg = s->screen_fps;
- return VO_TRUE;
- }
+ *(double *)arg = vo_cocoa_update_screen_fps(vo);
+ return VO_TRUE;
break;
case VOCTRL_GET_AMBIENT_LUX:
if (s->light_sensor != IO_OBJECT_NULL) {
@@ -885,7 +977,8 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
- (void)windowDidChangeScreen:(NSNotification *)notification
{
vo_cocoa_update_screen_info(self.vout);
- vo_cocoa_update_screen_fps(self.vout);
+ vo_cocoa_update_displaylink(self.vout);
+ flag_events(self.vout, VO_EVENT_WIN_STATE);
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification
@@ -915,6 +1008,7 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
- (void)didChangeWindowedScreenProfile:(NSNotification *)notification
{
+ vo_cocoa_update_screen_info(self.vout);
flag_events(self.vout, VO_EVENT_ICC_PROFILE_CHANGED);
}
diff --git a/video/out/drm_common.c b/video/out/drm_common.c
index 44e017e4b2..c7b4edf91f 100644
--- a/video/out/drm_common.c
+++ b/video/out/drm_common.c
@@ -19,7 +19,7 @@
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
-#include <sys/poll.h>
+#include <poll.h>
#include <sys/stat.h>
#include <sys/vt.h>
#include <unistd.h>
diff --git a/video/out/opengl/angle_dynamic.h b/video/out/opengl/angle_dynamic.h
index 87ad85c268..12a0c692eb 100644
--- a/video/out/opengl/angle_dynamic.h
+++ b/video/out/opengl/angle_dynamic.h
@@ -45,9 +45,12 @@
(EGLDisplay, EGLint)) \
FN(eglSwapBuffers, EGLBoolean (*EGLAPIENTRY PFN_eglSwapBuffers) \
(EGLDisplay, EGLSurface)) \
+ FN(eglSwapInterval, EGLBoolean (*EGLAPIENTRY PFN_eglSwapInterval) \
+ (EGLDisplay, EGLint)) \
FN(eglReleaseTexImage, EGLBoolean (*EGLAPIENTRY PFN_eglReleaseTexImage) \
(EGLDisplay, EGLSurface, EGLint)) \
- FN(eglTerminate, EGLBoolean (*EGLAPIENTRY PFN_eglTerminate)(EGLDisplay))
+ FN(eglTerminate, EGLBoolean (*EGLAPIENTRY PFN_eglTerminate)(EGLDisplay)) \
+ FN(eglWaitClient, EGLBoolean (*EGLAPIENTRY PFN_eglWaitClient)(void))
#define ANGLE_EXT_DECL(NAME, VAR) \
extern VAR;
@@ -76,7 +79,9 @@ bool angle_load(void);
#define eglQueryString PFN_eglQueryString
#define eglReleaseTexImage PFN_eglReleaseTexImage
#define eglSwapBuffers PFN_eglSwapBuffers
+#define eglSwapInterval PFN_eglSwapInterval
#define eglTerminate PFN_eglTerminate
+#define eglWaitClient PFN_eglWaitClient
#endif
#endif
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index fb3471cd3b..f7fce4fae5 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -57,7 +57,6 @@ static const struct mpgl_driver *const backends[] = {
#endif
#if HAVE_EGL_ANGLE
&mpgl_driver_angle,
- &mpgl_driver_angle_es2,
#endif
#if HAVE_GL_WIN32
&mpgl_driver_w32,
@@ -159,6 +158,7 @@ static MPGLContext *init_backend(struct vo *vo, const struct mpgl_driver *driver
*ctx = (MPGLContext) {
.gl = talloc_zero(ctx, GL),
.vo = vo,
+ .global = vo->global,
.driver = driver,
};
if (probing)
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index d0588ddbd9..0a02bd2867 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -32,7 +32,6 @@ enum {
VOFLAG_GL_DEBUG = 1 << 2, // Hint to request debug OpenGL context
VOFLAG_ALPHA = 1 << 3, // Hint to request alpha framebuffer
VOFLAG_SW = 1 << 4, // Hint to accept a software GL renderer
- VOFLAG_ANGLE_DCOMP = 1 << 5, // Whether DirectComposition is allowed
VOFLAG_PROBING = 1 << 6, // The backend is being auto-probed.
};
@@ -78,14 +77,12 @@ typedef struct MPGLContext {
GL *gl;
struct vo *vo;
const struct mpgl_driver *driver;
+ struct mpv_global *global;
// For hwdec_vaegl.c.
const char *native_display_type;
void *native_display;
- // Windows-specific hack. See vo_opengl dwmflush suboption.
- int dwm_flush_opt;
-
// Flip the rendered image vertically. This is useful for dxinterop.
bool flip_v;
diff --git a/video/out/opengl/context_angle.c b/video/out/opengl/context_angle.c
index 44aed340e3..7a011a80a4 100644
--- a/video/out/opengl/context_angle.c
+++ b/video/out/opengl/context_angle.c
@@ -19,14 +19,21 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <d3d11.h>
-#include <dxgi.h>
+#include <dxgi1_2.h>
+#include <dwmapi.h>
#include "angle_dynamic.h"
+#include "egl_helpers.h"
#include "common/common.h"
+#include "options/m_config.h"
#include "video/out/w32_common.h"
+#include "osdep/windows_utils.h"
#include "context.h"
+#ifndef EGL_D3D_TEXTURE_ANGLE
+#define EGL_D3D_TEXTURE_ANGLE 0x33A3
+#endif
#ifndef EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE
#define EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE 0x33A7
#define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8
@@ -36,79 +43,190 @@
// Windows 8 enum value, not present in mingw-w64 headers
#define DXGI_ADAPTER_FLAG_SOFTWARE (2)
+enum {
+ RENDERER_AUTO,
+ RENDERER_D3D9,
+ RENDERER_D3D11,
+};
+
+struct angle_opts {
+ int renderer;
+ int d3d11_warp;
+ int d3d11_feature_level;
+ int egl_windowing;
+ int swapchain_length; // Currently only works with DXGI 1.2+
+ int max_frame_latency;
+};
+
+#define OPT_BASE_STRUCT struct angle_opts
+const struct m_sub_options angle_conf = {
+ .opts = (const struct m_option[]) {
+ OPT_CHOICE("angle-renderer", renderer, 0,
+ ({"auto", RENDERER_AUTO},
+ {"d3d9", RENDERER_D3D9},
+ {"d3d11", RENDERER_D3D11})),
+ OPT_CHOICE("angle-d3d11-warp", d3d11_warp, 0,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1})),
+ OPT_CHOICE("angle-d3d11-feature-level", d3d11_feature_level, 0,
+ ({"11_0", D3D_FEATURE_LEVEL_11_0},
+ {"10_1", D3D_FEATURE_LEVEL_10_1},
+ {"10_0", D3D_FEATURE_LEVEL_10_0},
+ {"9_3", D3D_FEATURE_LEVEL_9_3})),
+ OPT_CHOICE("angle-egl-windowing", egl_windowing, 0,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1})),
+ OPT_INTRANGE("angle-swapchain-length", swapchain_length, 0, 2, 16),
+ OPT_INTRANGE("angle-max-frame-latency", max_frame_latency, 0, 1, 16),
+ {0}
+ },
+ .defaults = &(const struct angle_opts) {
+ .renderer = RENDERER_AUTO,
+ .d3d11_warp = -1,
+ .d3d11_feature_level = D3D_FEATURE_LEVEL_11_0,
+ .egl_windowing = -1,
+ .swapchain_length = 6,
+ .max_frame_latency = 3,
+ },
+ .size = sizeof(struct angle_opts),
+};
+
struct priv {
+ IDXGIFactory1 *dxgi_factory;
+ IDXGIFactory2 *dxgi_factory2;
+ IDXGIAdapter1 *dxgi_adapter;
+ IDXGIDevice1 *dxgi_device;
+ IDXGISwapChain *dxgi_swapchain;
+ IDXGISwapChain1 *dxgi_swapchain1;
+
+ ID3D11Device *d3d11_device;
+ ID3D11DeviceContext *d3d11_context;
+ ID3D11Texture2D *d3d11_backbuffer;
+ D3D_FEATURE_LEVEL d3d11_level;
+
+ EGLConfig egl_config;
EGLDisplay egl_display;
+ EGLDeviceEXT egl_device;
EGLContext egl_context;
- EGLSurface egl_surface;
- bool use_es2;
+ EGLSurface egl_window; // For the EGL windowing surface only
+ EGLSurface egl_backbuffer; // For the DXGI swap chain based surface
+
+ int sc_width, sc_height; // Swap chain width and height
+ int swapinterval;
+
bool sw_adapter_msg_shown;
- PFNEGLPOSTSUBBUFFERNVPROC eglPostSubBufferNV;
+
+ struct angle_opts *opts;
};
-static void angle_uninit(MPGLContext *ctx)
+static __thread struct MPGLContext *current_ctx;
+
+static void update_sizes(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
+ p->sc_width = ctx->vo->dwidth ? ctx->vo->dwidth : 1;
+ p->sc_height = ctx->vo->dheight ? ctx->vo->dheight : 1;
+}
- if (p->egl_context) {
+static void d3d11_backbuffer_release(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (p->egl_backbuffer) {
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
- eglDestroyContext(p->egl_display, p->egl_context);
+ eglDestroySurface(p->egl_display, p->egl_backbuffer);
}
- p->egl_context = EGL_NO_CONTEXT;
- if (p->egl_display)
- eglTerminate(p->egl_display);
- vo_w32_uninit(ctx->vo);
+ p->egl_backbuffer = EGL_NO_SURFACE;
+
+ SAFE_RELEASE(p->d3d11_backbuffer);
}
-static EGLConfig select_fb_config_egl(struct MPGLContext *ctx)
+static bool d3d11_backbuffer_get(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+ HRESULT hr;
- EGLint attributes[] = {
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_DEPTH_SIZE, 0,
- EGL_NONE
+ hr = IDXGISwapChain_GetBuffer(p->dxgi_swapchain, 0, &IID_ID3D11Texture2D,
+ (void**)&p->d3d11_backbuffer);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't get swap chain back buffer\n");
+ return false;
+ }
+
+ EGLint pbuffer_attributes[] = {
+ EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
+ EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
+ EGL_NONE,
};
+ p->egl_backbuffer = eglCreatePbufferFromClientBuffer(p->egl_display,
+ EGL_D3D_TEXTURE_ANGLE, p->d3d11_backbuffer, p->egl_config,
+ pbuffer_attributes);
+ if (!p->egl_backbuffer) {
+ MP_FATAL(vo, "Couldn't create EGL pbuffer\n");
+ return false;
+ }
- EGLint config_count;
- EGLConfig config;
+ eglMakeCurrent(p->egl_display, p->egl_backbuffer, p->egl_backbuffer,
+ p->egl_context);
+ return true;
+}
- eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count);
+static void d3d11_backbuffer_resize(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+ HRESULT hr;
- if (!config_count) {
- MP_FATAL(ctx->vo, "Could find EGL configuration!\n");
- return NULL;
- }
+ int old_sc_width = p->sc_width;
+ int old_sc_height = p->sc_height;
+
+ update_sizes(ctx);
+ // Avoid unnecessary resizing
+ if (old_sc_width == p->sc_width && old_sc_height == p->sc_height)
+ return;
+
+ // All references to backbuffers must be released before ResizeBuffers
+ // (including references held by ANGLE)
+ d3d11_backbuffer_release(ctx);
+
+ // The DirectX runtime may report errors related to the device like
+ // DXGI_ERROR_DEVICE_REMOVED at this point
+ hr = IDXGISwapChain_ResizeBuffers(p->dxgi_swapchain, 0, p->sc_width,
+ p->sc_height, DXGI_FORMAT_UNKNOWN, 0);
+ if (FAILED(hr))
+ MP_FATAL(vo, "Couldn't resize swapchain: %s\n", mp_HRESULT_to_str(hr));
- return config;
+ if (!d3d11_backbuffer_get(ctx))
+ MP_FATAL(vo, "Couldn't get back buffer after resize\n");
}
-static bool create_context_egl(MPGLContext *ctx, EGLConfig config, int version)
+static void d3d11_device_destroy(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
- EGLint context_attributes[] = {
- EGL_CONTEXT_CLIENT_VERSION, version,
- EGL_NONE
- };
-
- p->egl_context = eglCreateContext(p->egl_display, config,
- EGL_NO_CONTEXT, context_attributes);
+ PFNEGLRELEASEDEVICEANGLEPROC eglReleaseDeviceANGLE =
+ (PFNEGLRELEASEDEVICEANGLEPROC)eglGetProcAddress("eglReleaseDeviceANGLE");
- if (p->egl_context == EGL_NO_CONTEXT) {
- MP_VERBOSE(ctx->vo, "Could not create EGL GLES %d context!\n", version);
- return false;
- }
+ if (p->egl_display)
+ eglTerminate(p->egl_display);
+ p->egl_display = EGL_NO_DISPLAY;
- eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
- p->egl_context);
+ if (p->egl_device && eglReleaseDeviceANGLE)
+ eglReleaseDeviceANGLE(p->egl_device);
+ p->egl_device = 0;
- return true;
+ SAFE_RELEASE(p->d3d11_device);
+ SAFE_RELEASE(p->dxgi_device);
+ SAFE_RELEASE(p->dxgi_adapter);
+ SAFE_RELEASE(p->dxgi_factory);
+ SAFE_RELEASE(p->dxgi_factory2);
}
-static void show_sw_adapter_msg(struct MPGLContext *ctx)
+static void show_sw_adapter_msg(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
if (p->sw_adapter_msg_shown)
@@ -117,180 +235,302 @@ static void show_sw_adapter_msg(struct MPGLContext *ctx)
p->sw_adapter_msg_shown = true;
}
-static void d3d_init(struct MPGLContext *ctx)
+static bool d3d11_device_create(MPGLContext *ctx, int flags)
{
- HRESULT hr;
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
- IDXGIDevice *dxgi_dev = NULL;
- IDXGIAdapter *dxgi_adapter = NULL;
- IDXGIAdapter1 *dxgi_adapter1 = NULL;
- IDXGIFactory *dxgi_factory = NULL;
-
- PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT =
- (PFNEGLQUERYDISPLAYATTRIBEXTPROC)eglGetProcAddress("eglQueryDisplayAttribEXT");
- PFNEGLQUERYDEVICEATTRIBEXTPROC eglQueryDeviceAttribEXT =
- (PFNEGLQUERYDEVICEATTRIBEXTPROC)eglGetProcAddress("eglQueryDeviceAttribEXT");
- if (!eglQueryDisplayAttribEXT || !eglQueryDeviceAttribEXT) {
- MP_VERBOSE(vo, "Missing EGL_EXT_device_query\n");
- goto done;
+ struct angle_opts *o = p->opts;
+ HRESULT hr;
+
+ HMODULE d3d11_dll = LoadLibraryW(L"d3d11.dll");
+ if (!d3d11_dll) {
+ MP_FATAL(vo, "Failed to load d3d11.dll\n");
+ return false;
}
- EGLAttrib dev_attr;
- if (!eglQueryDisplayAttribEXT(p->egl_display, EGL_DEVICE_EXT, &dev_attr)) {
- MP_VERBOSE(vo, "Missing EGL_EXT_device_query\n");
- goto done;
+ PFN_D3D11_CREATE_DEVICE D3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)
+ GetProcAddress(d3d11_dll, "D3D11CreateDevice");
+ if (!D3D11CreateDevice) {
+ MP_FATAL(vo, "D3D11CreateDevice entry point not found\n");
+ return false;
}
- // If ANGLE is in D3D11 mode, get the underlying ID3D11Device
- EGLDeviceEXT dev = (EGLDeviceEXT)dev_attr;
- EGLAttrib d3d11_dev_attr;
- if (eglQueryDeviceAttribEXT(dev, EGL_D3D11_DEVICE_ANGLE, &d3d11_dev_attr)) {
- ID3D11Device *d3d11_dev = (ID3D11Device*)d3d11_dev_attr;
+ D3D_FEATURE_LEVEL *levels = (D3D_FEATURE_LEVEL[]) {
+ D3D_FEATURE_LEVEL_11_0,
+ D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0,
+ D3D_FEATURE_LEVEL_9_3,
+ };
+ int level_count = 4;
- hr = ID3D11Device_QueryInterface(d3d11_dev, &IID_IDXGIDevice,
- (void**)&dxgi_dev);
- if (FAILED(hr)) {
- MP_ERR(vo, "Device is not a IDXGIDevice\n");
- goto done;
- }
+ // Only try feature levels less than or equal to the user specified level
+ while (level_count && levels[0] > o->d3d11_feature_level) {
+ levels++;
+ level_count--;
+ }
- hr = IDXGIDevice_GetAdapter(dxgi_dev, &dxgi_adapter);
- if (FAILED(hr)) {
- MP_ERR(vo, "Couldn't get IDXGIAdapter\n");
- goto done;
- }
+ // Try a HW adapter first unless WARP is forced
+ hr = E_FAIL;
+ if ((FAILED(hr) && o->d3d11_warp == -1) || o->d3d11_warp == 0) {
+ hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, levels,
+ level_count, D3D11_SDK_VERSION, &p->d3d11_device, &p->d3d11_level,
+ &p->d3d11_context);
+ }
+ // Try WARP if it is forced or if the HW adapter failed
+ if ((FAILED(hr) && o->d3d11_warp == -1) || o->d3d11_warp == 1) {
+ hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_WARP, NULL, 0, levels,
+ level_count, D3D11_SDK_VERSION, &p->d3d11_device, &p->d3d11_level,
+ &p->d3d11_context);
+ if (SUCCEEDED(hr))
+ show_sw_adapter_msg(ctx);
+ }
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't create Direct3D 11 device: %s\n",
+ mp_HRESULT_to_str(hr));
+ return false;
+ }
- // Windows 8 can choose a software adapter even if mpv didn't ask for
- // one. If this is the case, show a warning message.
- hr = IDXGIAdapter_QueryInterface(dxgi_adapter, &IID_IDXGIAdapter1,
- (void**)&dxgi_adapter1);
- if (SUCCEEDED(hr)) {
- DXGI_ADAPTER_DESC1 desc;
- hr = IDXGIAdapter1_GetDesc1(dxgi_adapter1, &desc);
- if (SUCCEEDED(hr)) {
- if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
- show_sw_adapter_msg(ctx);
-
- // If the primary display adapter is a software adapter, the
- // DXGI_ADAPTER_FLAG_SOFTWARE won't be set, but the device IDs
- // should still match the Microsoft Basic Render Driver
- if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c)
- show_sw_adapter_msg(ctx);
- }
- }
+ hr = ID3D11Device_QueryInterface(p->d3d11_device, &IID_IDXGIDevice1,
+ (void**)&p->dxgi_device);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't get DXGI device\n");
+ return false;
+ }
- hr = IDXGIAdapter_GetParent(dxgi_adapter, &IID_IDXGIFactory,
- (void**)&dxgi_factory);
- if (FAILED(hr)) {
- MP_ERR(vo, "Couldn't get IDXGIFactory\n");
- goto done;
- }
+ IDXGIDevice1_SetMaximumFrameLatency(p->dxgi_device, o->max_frame_latency);
- // Prevent DXGI from making changes to the VO window, otherwise in
- // non-DirectComposition mode it will hook the Alt+Enter keystroke and
- // make it trigger an ugly transition to exclusive fullscreen mode
- // instead of running the user-set command.
- IDXGIFactory_MakeWindowAssociation(dxgi_factory, vo_w32_hwnd(vo),
- DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER |
- DXGI_MWA_NO_PRINT_SCREEN);
+ hr = IDXGIDevice1_GetParent(p->dxgi_device, &IID_IDXGIAdapter1,
+ (void**)&p->dxgi_adapter);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't get DXGI adapter\n");
+ return false;
}
-done:
- if (dxgi_dev)
- IDXGIDevice_Release(dxgi_dev);
- if (dxgi_adapter)
- IDXGIAdapter_Release(dxgi_adapter);
- if (dxgi_adapter1)
- IDXGIAdapter1_Release(dxgi_adapter1);
- if (dxgi_factory)
- IDXGIFactory_Release(dxgi_factory);
+ // Query some properties of the adapter in order to warn the user if they
+ // are using a software adapter
+ DXGI_ADAPTER_DESC1 desc;
+ hr = IDXGIAdapter1_GetDesc1(p->dxgi_adapter, &desc);
+ if (SUCCEEDED(hr)) {
+ if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
+ show_sw_adapter_msg(ctx);
+
+ // If the primary display adapter is a software adapter, the
+ // DXGI_ADAPTER_FLAG_SOFTWARE won't be set, but the device IDs
+ // should still match the Microsoft Basic Render Driver
+ if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c)
+ show_sw_adapter_msg(ctx);
+ }
+
+ hr = IDXGIAdapter1_GetParent(p->dxgi_adapter, &IID_IDXGIFactory1,
+ (void**)&p->dxgi_factory);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't get DXGI factory\n");
+ return false;
+ }
+
+ IDXGIFactory1_QueryInterface(p->dxgi_factory, &IID_IDXGIFactory2,
+ (void**)&p->dxgi_factory2);
+
+ PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
+ (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
+ if (!eglGetPlatformDisplayEXT) {
+ MP_FATAL(vo, "Missing EGL_EXT_platform_base\n");
+ return false;
+ }
+ PFNEGLCREATEDEVICEANGLEPROC eglCreateDeviceANGLE =
+ (PFNEGLCREATEDEVICEANGLEPROC)eglGetProcAddress("eglCreateDeviceANGLE");
+ if (!eglCreateDeviceANGLE) {
+ MP_FATAL(vo, "Missing EGL_EXT_platform_device\n");
+ return false;
+ }
+
+ p->egl_device = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE,
+ p->d3d11_device, NULL);
+ if (!p->egl_device) {
+ MP_FATAL(vo, "Couldn't create EGL device\n");
+ return false;
+ }
+
+ p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT,
+ p->egl_device, NULL);
+ if (!p->egl_display) {
+ MP_FATAL(vo, "Couldn't get EGL display\n");
+ return false;
+ }
+
+ return true;
}
-static void *get_proc_address(const GLubyte *proc_name)
+static void d3d11_swapchain_surface_destroy(MPGLContext *ctx)
{
- return eglGetProcAddress(proc_name);
+ struct priv *p = ctx->priv;
+ SAFE_RELEASE(p->dxgi_swapchain);
+ SAFE_RELEASE(p->dxgi_swapchain1);
+ d3d11_backbuffer_release(ctx);
}
-static int angle_init(struct MPGLContext *ctx, int flags)
+static bool d3d11_swapchain_create_1_2(MPGLContext *ctx, int flags)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
+ HRESULT hr;
- if (!angle_load()) {
- MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n");
- goto fail;
+ update_sizes(ctx);
+ DXGI_SWAP_CHAIN_DESC1 desc1 = {
+ .Width = p->sc_width,
+ .Height = p->sc_height,
+ .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
+ .SampleDesc = { .Count = 1 },
+ .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT |
+ DXGI_USAGE_SHADER_INPUT,
+ .BufferCount = p->opts->swapchain_length,
+ .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
+ };
+
+ hr = IDXGIFactory2_CreateSwapChainForHwnd(p->dxgi_factory2,
+ (IUnknown*)p->d3d11_device, vo_w32_hwnd(vo), &desc1, NULL, NULL,
+ &p->dxgi_swapchain1);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't create DXGI 1.2+ swap chain: %s\n",
+ mp_HRESULT_to_str(hr));
+ return false;
}
- if (!vo_w32_init(vo))
- goto fail;
+ hr = IDXGISwapChain1_QueryInterface(p->dxgi_swapchain1,
+ &IID_IDXGISwapChain, (void**)&p->dxgi_swapchain);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't create DXGI 1.2+ swap chain\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool d3d11_swapchain_create_1_1(MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+ HRESULT hr;
- HDC dc = GetDC(vo_w32_hwnd(vo));
- if (!dc) {
- MP_FATAL(vo, "Couldn't get DC\n");
+ update_sizes(ctx);
+ DXGI_SWAP_CHAIN_DESC desc = {
+ .BufferDesc = {
+ .Width = p->sc_width,
+ .Height = p->sc_height,
+ .Format = DXGI_FORMAT_R8G8B8A8_UNORM
+ },
+ .SampleDesc = { .Count = 1 },
+ .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT |
+ DXGI_USAGE_SHADER_INPUT,
+ .BufferCount = 1,
+ .OutputWindow = vo_w32_hwnd(vo),
+ .Windowed = TRUE,
+ .SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
+ };
+
+ hr = IDXGIFactory1_CreateSwapChain(p->dxgi_factory,
+ (IUnknown*)p->d3d11_device, &desc, &p->dxgi_swapchain);
+ if (FAILED(hr)) {
+ MP_FATAL(vo, "Couldn't create DXGI 1.1 swap chain: %s\n",
+ mp_HRESULT_to_str(hr));
+ return false;
+ }
+
+ return true;
+}
+
+static bool d3d11_swapchain_surface_create(MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ if (p->dxgi_factory2) {
+ // Create a DXGI 1.2+ (Windows 8+) swap chain if possible
+ if (!d3d11_swapchain_create_1_2(ctx, flags))
+ goto fail;
+ } else if (p->dxgi_factory) {
+ // Fall back to DXGI 1.1 (Windows 7)
+ if (!d3d11_swapchain_create_1_1(ctx, flags))
+ goto fail;
+ } else {
goto fail;
}
+ // Prevent DXGI from making changes to the VO window, otherwise it will
+ // hook the Alt+Enter keystroke and make it trigger an ugly transition to
+ // exclusive fullscreen mode instead of running the user-set command.
+ IDXGIFactory_MakeWindowAssociation(p->dxgi_factory, vo_w32_hwnd(vo),
+ DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER |
+ DXGI_MWA_NO_PRINT_SCREEN);
+
+ if (!d3d11_backbuffer_get(ctx))
+ goto fail;
+
+ // EGL_D3D_TEXTURE_ANGLE pbuffers are always flipped vertically
+ ctx->flip_v = true;
+ return true;
+
+fail:
+ d3d11_swapchain_surface_destroy(ctx);
+ return false;
+}
+
+static void d3d9_device_destroy(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (p->egl_display)
+ eglTerminate(p->egl_display);
+ p->egl_display = EGL_NO_DISPLAY;
+}
+
+static bool d3d9_device_create(MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
(PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if (!eglGetPlatformDisplayEXT) {
MP_FATAL(vo, "Missing EGL_EXT_platform_base\n");
- goto fail;
+ return false;
}
- EGLint d3d_types[] = {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
- EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
- EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE};
- EGLint d3d_dev_types[] = {EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
- EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
- EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE};
- for (int i = 0; i < MP_ARRAY_SIZE(d3d_types); i++) {
- EGLint display_attributes[] = {
- EGL_PLATFORM_ANGLE_TYPE_ANGLE,
- d3d_types[i],
- EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
- d3d_dev_types[i],
- EGL_NONE,
- };
-
- p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, dc,
- display_attributes);
- if (p->egl_display == EGL_NO_DISPLAY)
- continue;
-
- if (!eglInitialize(p->egl_display, NULL, NULL)) {
- p->egl_display = EGL_NO_DISPLAY;
- continue;
- }
-
- if (d3d_dev_types[i] == EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE)
- show_sw_adapter_msg(ctx);
- break;
- }
+ EGLint display_attributes[] = {
+ EGL_PLATFORM_ANGLE_TYPE_ANGLE,
+ EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
+ EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
+ EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
+ EGL_NONE,
+ };
+ p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
+ EGL_DEFAULT_DISPLAY, display_attributes);
if (p->egl_display == EGL_NO_DISPLAY) {
MP_FATAL(vo, "Couldn't get display\n");
- goto fail;
+ return false;
}
- const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
- if (exts)
- MP_DBG(ctx->vo, "EGL extensions: %s\n", exts);
+ return true;
+}
- eglBindAPI(EGL_OPENGL_ES_API);
- if (eglGetError() != EGL_SUCCESS) {
- MP_FATAL(vo, "Couldn't bind GLES API\n");
- goto fail;
+static void egl_window_surface_destroy(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ if (p->egl_window) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
}
+}
- EGLConfig config = select_fb_config_egl(ctx);
- if (!config)
- goto fail;
+static bool egl_window_surface_create(MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
int window_attribs_len = 0;
EGLint *window_attribs = NULL;
EGLint flip_val;
- if (eglGetConfigAttrib(p->egl_display, config,
+ if (eglGetConfigAttrib(p->egl_display, p->egl_config,
EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &flip_val))
{
if (flip_val == EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) {
@@ -303,88 +543,303 @@ static int angle_init(struct MPGLContext *ctx, int flags)
}
}
- // EGL_DIRECT_COMPOSITION_ANGLE enables the use of flip-mode present, which
- // avoids a copy of the video image and lowers vsync jitter, though the
- // extension is only present on Windows 8 and up, and might have subpar
- // behavior with some drivers (Intel? symptom - whole desktop is black for
- // some seconds after spending some minutes in fullscreen and then leaving
- // fullscreen).
- if ((flags & VOFLAG_ANGLE_DCOMP) &&
- strstr(exts, "EGL_ANGLE_direct_composition"))
- {
- MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len,
- EGL_DIRECT_COMPOSITION_ANGLE);
- MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_TRUE);
- MP_VERBOSE(vo, "Using DirectComposition.\n");
- }
-
MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_NONE);
- p->egl_surface = eglCreateWindowSurface(p->egl_display, config,
- vo_w32_hwnd(vo), window_attribs);
+ p->egl_window = eglCreateWindowSurface(p->egl_display, p->egl_config,
+ vo_w32_hwnd(vo), window_attribs);
talloc_free(window_attribs);
- if (p->egl_surface == EGL_NO_SURFACE) {
- MP_FATAL(ctx->vo, "Could not create EGL surface!\n");
+ if (!p->egl_window) {
+ MP_FATAL(vo, "Could not create EGL surface!\n");
goto fail;
}
- if (!(!p->use_es2 && create_context_egl(ctx, config, 3)) &&
- !create_context_egl(ctx, config, 2))
- {
- MP_FATAL(ctx->vo, "Could not create EGL context!\n");
+ eglMakeCurrent(p->egl_display, p->egl_window, p->egl_window,
+ p->egl_context);
+ return true;
+fail:
+ egl_window_surface_destroy(ctx);
+ return false;
+}
+
+static void angle_uninit(struct MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ DwmEnableMMCSS(FALSE);
+
+ // Uninit the EGL surface implementation that is being used. Note: This may
+ // result in the *_destroy function being called twice since it is also
+ // called when the surface create function fails. This is fine because the
+ // *_destroy functions are idempotent.
+ if (p->dxgi_swapchain)
+ d3d11_swapchain_surface_destroy(ctx);
+ else
+ egl_window_surface_destroy(ctx);
+
+ if (p->egl_context) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroyContext(p->egl_display, p->egl_context);
+ }
+ p->egl_context = EGL_NO_CONTEXT;
+
+ // Uninit the EGL device implementation that is being used
+ if (p->d3d11_device)
+ d3d11_device_destroy(ctx);
+ else
+ d3d9_device_destroy(ctx);
+
+ vo_w32_uninit(ctx->vo);
+}
+
+static int GLAPIENTRY angle_swap_interval(int interval)
+{
+ if (!current_ctx)
+ return 0;
+ struct priv *p = current_ctx->priv;
+
+ if (p->dxgi_swapchain) {
+ p->swapinterval = MPCLAMP(interval, 0, 4);
+ return 1;
+ } else {
+ return eglSwapInterval(p->egl_display, interval);
+ }
+}
+
+static void *get_proc_address(const GLubyte *proc_name)
+{
+ return eglGetProcAddress(proc_name);
+}
+
+static int angle_init(struct MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ p->opts = mp_get_config_group(ctx, ctx->global, &angle_conf);
+ struct angle_opts *o = p->opts;
+
+ // DWM MMCSS cargo-cult. The dxinterop backend also does this.
+ DwmEnableMMCSS(TRUE);
+
+ if (!angle_load()) {
+ MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n");
+ goto fail;
+ }
+
+ // Create the underlying EGL device implementation
+ bool device_ok = false;
+ if ((!device_ok && !o->renderer) || o->renderer == RENDERER_D3D11) {
+ device_ok = d3d11_device_create(ctx, flags);
+ if (device_ok) {
+ MP_VERBOSE(vo, "Using Direct3D 11 feature level %u_%u\n",
+ ((unsigned)p->d3d11_level) >> 12,
+ (((unsigned)p->d3d11_level) >> 8) & 0xf);
+ }
+ }
+ if ((!device_ok && !o->renderer) || o->renderer == RENDERER_D3D9) {
+ device_ok = d3d9_device_create(ctx, flags);
+ if (device_ok)
+ MP_VERBOSE(vo, "Using Direct3D 9\n");
+ }
+ if (!device_ok)
goto fail;
+
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(vo, "Couldn't initialize EGL\n");
+ return false;
}
- // Configure the underlying Direct3D device
- d3d_init(ctx);
+ if (!vo_w32_init(vo))
+ goto fail;
+
+ const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
+ if (exts)
+ MP_DBG(ctx->vo, "EGL extensions: %s\n", exts);
- if (strstr(exts, "EGL_NV_post_sub_buffer")) {
- p->eglPostSubBufferNV =
- (PFNEGLPOSTSUBBUFFERNVPROC)eglGetProcAddress("eglPostSubBufferNV");
+ if (!mpegl_create_context(p->egl_display, vo->log, flags | VOFLAG_GLES,
+ &p->egl_context, &p->egl_config))
+ {
+ MP_FATAL(vo, "Could not create EGL context!\n");
+ goto fail;
}
+ // Create the underlying EGL surface implementation
+ bool surface_ok = false;
+ if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 0) {
+ surface_ok = d3d11_swapchain_surface_create(ctx, flags);
+ if (surface_ok) {
+ if (p->dxgi_swapchain1) {
+ MP_VERBOSE(vo, "Using DXGI 1.2+\n");
+ } else {
+ MP_VERBOSE(vo, "Using DXGI 1.1\n");
+ }
+ }
+ }
+ if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 1) {
+ surface_ok = egl_window_surface_create(ctx, flags);
+ if (surface_ok)
+ MP_VERBOSE(vo, "Using EGL windowing\n");
+ }
+ if (!surface_ok)
+ goto fail;
+
mpgl_load_functions(ctx->gl, get_proc_address, NULL, vo->log);
- return 0;
+ current_ctx = ctx;
+ ctx->gl->SwapInterval = angle_swap_interval;
+
+ return 0;
fail:
angle_uninit(ctx);
return -1;
}
-static int angle_init_es2(struct MPGLContext *ctx, int flags)
-{
- struct priv *p = ctx->priv;
- p->use_es2 = true;
- if (ctx->vo->probing) {
- MP_VERBOSE(ctx->vo, "Not using this by default.\n");
- return -1;
- }
- return angle_init(ctx, flags);
-}
-
static int angle_reconfig(struct MPGLContext *ctx)
{
vo_w32_config(ctx->vo);
return 0;
}
+static struct mp_image *d3d11_screenshot(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ ID3D11Texture2D *frontbuffer = NULL;
+ ID3D11Texture2D *staging = NULL;
+ struct mp_image *img = NULL;
+ HRESULT hr;
+
+ if (!p->dxgi_swapchain1)
+ goto done;
+
+ // Validate the swap chain. This screenshot method will only work on DXGI
+ // 1.2+ flip/sequential swap chains. It's probably not possible at all with
+ // discard swap chains, since by definition, the backbuffer contents is
+ // discarded on Present().
+ DXGI_SWAP_CHAIN_DESC1 scd;
+ hr = IDXGISwapChain1_GetDesc1(p->dxgi_swapchain1, &scd);
+ if (FAILED(hr))
+ goto done;
+ if (scd.SwapEffect != DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL)
+ goto done;
+
+ // Get the last buffer that was presented with Present(). This should be
+ // the n-1th buffer for a swap chain of length n.
+ hr = IDXGISwapChain_GetBuffer(p->dxgi_swapchain, scd.BufferCount - 1,
+ &IID_ID3D11Texture2D, (void**)&frontbuffer);
+ if (FAILED(hr))
+ goto done;
+
+ D3D11_TEXTURE2D_DESC td;
+ ID3D11Texture2D_GetDesc(frontbuffer, &td);
+ if (td.SampleDesc.Count > 1)
+ goto done;
+
+ // Validate the backbuffer format and convert to an mpv IMGFMT
+ enum mp_imgfmt fmt;
+ switch (td.Format) {
+ case DXGI_FORMAT_B8G8R8A8_UNORM: fmt = IMGFMT_BGR0; break;
+ case DXGI_FORMAT_R8G8B8A8_UNORM: fmt = IMGFMT_RGB0; break;
+ default:
+ goto done;
+ }
+
+ // Create a staging texture based on the frontbuffer with CPU access
+ td.BindFlags = 0;
+ td.MiscFlags = 0;
+ td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ td.Usage = D3D11_USAGE_STAGING;
+ hr = ID3D11Device_CreateTexture2D(p->d3d11_device, &td, 0, &staging);
+ if (FAILED(hr))
+ goto done;
+
+ ID3D11DeviceContext_CopyResource(p->d3d11_context,
+ (ID3D11Resource*)staging, (ID3D11Resource*)frontbuffer);
+
+ // Attempt to map the staging texture to CPU-accessible memory
+ D3D11_MAPPED_SUBRESOURCE lock;
+ hr = ID3D11DeviceContext_Map(p->d3d11_context, (ID3D11Resource*)staging,
+ 0, D3D11_MAP_READ, 0, &lock);
+ if (FAILED(hr))
+ goto done;
+
+ img = mp_image_alloc(fmt, td.Width, td.Height);
+ if (!img)
+ return NULL;
+ for (int i = 0; i < td.Height; i++) {
+ memcpy(img->planes[0] + img->stride[0] * i,
+ (char*)lock.pData + lock.RowPitch * i, td.Width * 4);
+ }
+
+ ID3D11DeviceContext_Unmap(p->d3d11_context, (ID3D11Resource*)staging, 0);
+
+done:
+ SAFE_RELEASE(frontbuffer);
+ SAFE_RELEASE(staging);
+ return img;
+}
+
static int angle_control(MPGLContext *ctx, int *events, int request, void *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);
+ // Try a D3D11-specific method of taking a window screenshot
+ if (request == VOCTRL_SCREENSHOT_WIN) {
+ struct mp_image *img = d3d11_screenshot(ctx);
+ if (img) {
+ *(struct mp_image **)arg = img;
+ return true;
+ }
+ }
+ int r = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (p->dxgi_swapchain)
+ d3d11_backbuffer_resize(ctx);
+ else
+ eglWaitClient(); // Should get ANGLE to resize its swapchain
+ }
return r;
}
+static void d3d11_swap_buffers(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ // Calling Present() on a flip-sequential swap chain will silently change
+ // the underlying storage of the back buffer to point to the next buffer in
+ // the chain. This results in the RTVs for the back buffer becoming
+ // unbound. Since ANGLE doesn't know we called Present(), it will continue
+ // using the unbound RTVs, so we must save and restore them ourselves.
+ ID3D11RenderTargetView *rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {0};
+ ID3D11DepthStencilView *dsv = NULL;
+ ID3D11DeviceContext_OMGetRenderTargets(p->d3d11_context,
+ MP_ARRAY_SIZE(rtvs), rtvs, &dsv);
+
+ HRESULT hr = IDXGISwapChain_Present(p->dxgi_swapchain, p->swapinterval, 0);
+ if (FAILED(hr))
+ MP_FATAL(ctx->vo, "Couldn't present: %s\n", mp_HRESULT_to_str(hr));
+
+ // Restore the RTVs and release the objects
+ ID3D11DeviceContext_OMSetRenderTargets(p->d3d11_context,
+ MP_ARRAY_SIZE(rtvs), rtvs, dsv);
+ for (int i = 0; i < 8; i++)
+ SAFE_RELEASE(rtvs[i]);
+ SAFE_RELEASE(dsv);
+}
+
+static void egl_swap_buffers(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_window);
+}
+
static void angle_swap_buffers(MPGLContext *ctx)
{
struct priv *p = ctx->priv;
- eglSwapBuffers(p->egl_display, p->egl_surface);
+ if (p->dxgi_swapchain)
+ d3d11_swap_buffers(ctx);
+ else
+ egl_swap_buffers(ctx);
}
const struct mpgl_driver mpgl_driver_angle = {
@@ -396,13 +851,3 @@ const struct mpgl_driver mpgl_driver_angle = {
.control = angle_control,
.uninit = angle_uninit,
};
-
-const struct mpgl_driver mpgl_driver_angle_es2 = {
- .name = "angle-es2",
- .priv_size = sizeof(struct priv),
- .init = angle_init_es2,
- .reconfig = angle_reconfig,
- .swap_buffers = angle_swap_buffers,
- .control = angle_control,
- .uninit = angle_uninit,
-};
diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c
index e5fd7b8354..cf23423619 100644
--- a/video/out/opengl/context_drm_egl.c
+++ b/video/out/opengl/context_drm_egl.c
@@ -20,7 +20,7 @@
#include <fcntl.h>
#include <signal.h>
#include <string.h>
-#include <sys/poll.h>
+#include <poll.h>
#include <time.h>
#include <unistd.h>
diff --git a/video/out/opengl/context_dxinterop.c b/video/out/opengl/context_dxinterop.c
index a4b979d8d4..e98b0d55ae 100644
--- a/video/out/opengl/context_dxinterop.c
+++ b/video/out/opengl/context_dxinterop.c
@@ -235,12 +235,12 @@ static int d3d_size_dependent_create(MPGLContext *ctx)
hr = IDirect3DSwapChain9_QueryInterface(sw9, &IID_IDirect3DSwapChain9Ex,
(void**)&p->swapchain);
if (FAILED(hr)) {
- IDirect3DSwapChain9_Release(sw9);
+ SAFE_RELEASE(sw9);
MP_ERR(ctx->vo, "Obtained swap chain is not IDirect3DSwapChain9Ex: %s\n",
mp_HRESULT_to_str(hr));
return -1;
}
- IDirect3DSwapChain9_Release(sw9);
+ SAFE_RELEASE(sw9);
hr = IDirect3DSwapChain9Ex_GetBackBuffer(p->swapchain, 0,
D3DBACKBUFFER_TYPE_MONO, &p->backbuffer);
@@ -315,15 +315,10 @@ static void d3d_size_dependent_destroy(MPGLContext *ctx)
if (p->texture)
gl->DeleteTextures(1, &p->texture);
p->texture = 0;
- if (p->rtarget)
- IDirect3DSurface9_Release(p->rtarget);
- p->rtarget = NULL;
- if (p->backbuffer)
- IDirect3DSurface9_Release(p->backbuffer);
- p->backbuffer = NULL;
- if (p->swapchain)
- IDirect3DSwapChain9Ex_Release(p->swapchain);
- p->swapchain = NULL;
+
+ SAFE_RELEASE(p->rtarget);
+ SAFE_RELEASE(p->backbuffer);
+ SAFE_RELEASE(p->swapchain);
}
static void fill_presentparams(MPGLContext *ctx, D3DPRESENT_PARAMETERS *pparams)
@@ -422,10 +417,8 @@ static void d3d_destroy(MPGLContext *ctx)
if (p->device_h)
gl->DXCloseDeviceNV(p->device_h);
- if (p->device)
- IDirect3DDevice9Ex_Release(p->device);
- if (p->d3d9ex)
- IDirect3D9Ex_Release(p->d3d9ex);
+ SAFE_RELEASE(p->device);
+ SAFE_RELEASE(p->d3d9ex);
if (p->d3d9_dll)
FreeLibrary(p->d3d9_dll);
}
diff --git a/video/out/opengl/context_w32.c b/video/out/opengl/context_w32.c
index 3a0118e420..4c477642db 100644
--- a/video/out/opengl/context_w32.c
+++ b/video/out/opengl/context_w32.c
@@ -18,6 +18,8 @@
#include <assert.h>
#include <windows.h>
#include <dwmapi.h>
+
+#include "options/m_config.h"
#include "video/out/w32_common.h"
#include "video/out/win32/exclusive_hack.h"
#include "context.h"
@@ -301,10 +303,14 @@ static void w32_swap_buffers(MPGLContext *ctx)
// default if we don't DwmFLush
int new_swapinterval = w32_ctx->opt_swapinterval;
- if (ctx->dwm_flush_opt >= 0) {
- if ((ctx->dwm_flush_opt == 1 && !ctx->vo->opts->fullscreen) ||
- (ctx->dwm_flush_opt == 2) ||
- (ctx->dwm_flush_opt == 0 && compositor_active(ctx)))
+ int dwm_flush_opt;
+ mp_read_option_raw(ctx->global, "opengl-dwmflush", &m_option_type_choice,
+ &dwm_flush_opt);
+
+ if (dwm_flush_opt >= 0) {
+ if ((dwm_flush_opt == 1 && !ctx->vo->opts->fullscreen) ||
+ (dwm_flush_opt == 2) ||
+ (dwm_flush_opt == 0 && compositor_active(ctx)))
{
if (DwmFlush() == S_OK)
new_swapinterval = 0;
diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c
index 3864e2887f..127ddcaa93 100644
--- a/video/out/opengl/context_wayland.c
+++ b/video/out/opengl/context_wayland.c
@@ -161,9 +161,6 @@ static void waylandgl_swap_buffers(MPGLContext *ctx)
{
struct vo_wayland_state *wl = ctx->vo->wayland;
- if (!wl->frame.callback)
- vo_wayland_request_frame(ctx->vo, NULL, NULL);
-
vo_wayland_wait_events(ctx->vo, 0);
eglSwapBuffers(wl->egl_context.egl.dpy, wl->egl_context.egl_surface);
diff --git a/video/out/opengl/context_x11.c b/video/out/opengl/context_x11.c
index 48533fe701..c71e0b327a 100644
--- a/video/out/opengl/context_x11.c
+++ b/video/out/opengl/context_x11.c
@@ -160,17 +160,13 @@ static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, int flags
if (flags & VOFLAG_ALPHA) {
for (int n = 0; n < fbcount; n++) {
XVisualInfo *v = glXGetVisualFromFBConfig(vo->x11->display, fbc[n]);
- if (!v)
- continue;
- // This is a heuristic at best. Note that normal 8 bit Visuals use
- // a depth of 24, even if the pixels are padded to 32 bit. If the
- // depth is higher than 24, the remaining bits must be alpha.
- // Note: vinfo->bits_per_rgb appears to be useless (is always 8).
- unsigned long mask = v->depth == sizeof(unsigned long) * 8 ?
- (unsigned long)-1 : (1UL << v->depth) - 1;
- if (mask & ~(v->red_mask | v->green_mask | v->blue_mask)) {
- fbconfig = fbc[n];
- break;
+ if (v) {
+ bool is_rgba = vo_x11_is_rgba_visual(v);
+ XFree(v);
+ if (is_rgba) {
+ fbconfig = fbc[n];
+ break;
+ }
}
}
}
@@ -235,7 +231,10 @@ static int glx_init(struct MPGLContext *ctx, int flags)
MP_ERR(vo, "no GLX support present\n");
goto uninit;
}
- MP_VERBOSE(vo, "GLX chose FB config with ID 0x%x\n", (int)(intptr_t)fbc);
+
+ int fbid = -1;
+ if (!glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_FBCONFIG_ID, &fbid))
+ MP_VERBOSE(vo, "GLX chose FB config with ID 0x%x\n", fbid);
glx_ctx->fbc = fbc;
glx_ctx->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c
index 7a9e4d31fc..2cf249fe1a 100644
--- a/video/out/opengl/context_x11egl.c
+++ b/video/out/opengl/context_x11egl.c
@@ -49,6 +49,29 @@ static void mpegl_uninit(MPGLContext *ctx)
vo_x11_uninit(ctx->vo);
}
+static int pick_xrgba_config(void *user_data, EGLConfig *configs, int num_configs)
+{
+ struct MPGLContext *ctx = user_data;
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ for (int n = 0; n < num_configs; n++) {
+ int vID = 0, num;
+ eglGetConfigAttrib(p->egl_display, configs[n], EGL_NATIVE_VISUAL_ID, &vID);
+ XVisualInfo template = {.visualid = vID};
+ XVisualInfo *vi = XGetVisualInfo(vo->x11->display, VisualIDMask,
+ &template, &num);
+ if (vi) {
+ bool is_rgba = vo_x11_is_rgba_visual(vi);
+ XFree(vi);
+ if (is_rgba)
+ return n;
+ }
+ }
+
+ return 0;
+}
+
static int mpegl_init(struct MPGLContext *ctx, int flags)
{
struct priv *p = ctx->priv;
@@ -64,13 +87,20 @@ static int mpegl_init(struct MPGLContext *ctx, int flags)
goto uninit;
}
+ struct mpegl_opts opts = {
+ .vo_flags = flags,
+ .user_data = ctx,
+ .refine_config = (flags & VOFLAG_ALPHA) ? pick_xrgba_config : NULL,
+ };
+
EGLConfig config;
- if (!mpegl_create_context(p->egl_display, vo->log, flags, &p->egl_context,
- &config))
+ if (!mpegl_create_context_opts(p->egl_display, vo->log, &opts,
+ &p->egl_context, &config))
goto uninit;
int vID, n;
eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &vID);
+ MP_VERBOSE(vo, "chose visual 0x%x\n", vID);
XVisualInfo template = {.visualid = vID};
XVisualInfo *vi = XGetVisualInfo(vo->x11->display, VisualIDMask, &template, &n);
diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c
index 31a31dfb24..2ed1fd44d6 100644
--- a/video/out/opengl/egl_helpers.c
+++ b/video/out/opengl/egl_helpers.c
@@ -21,6 +21,12 @@
#include "common.h"
#include "context.h"
+#if HAVE_EGL_ANGLE
+// On Windows, egl_helpers.c is only used by ANGLE, where the EGL functions may
+// be loaded dynamically from ANGLE DLLs
+#include "angle_dynamic.h"
+#endif
+
// EGL 1.5
#ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK
#define EGL_CONTEXT_MAJOR_VERSION 0x3098
@@ -31,23 +37,22 @@
#define EGL_OPENGL_ES3_BIT 0x00000040
#endif
+// es_version = 0 (desktop), 2/3 (ES major version)
static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
- int vo_flags, bool es3,
+ int es_version, struct mpegl_opts *opts,
EGLContext *out_context, EGLConfig *out_config)
{
int msgl = probing ? MSGL_V : MSGL_FATAL;
- bool es = vo_flags & VOFLAG_GLES;
-
EGLenum api = EGL_OPENGL_API;
EGLint rend = EGL_OPENGL_BIT;
const char *name = "Desktop OpenGL";
- if (es) {
+ if (es_version == 2) {
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES2_BIT;
name = "GLES 2.0";
}
- if (es3) {
+ if (es_version == 3) {
api = EGL_OPENGL_ES_API;
rend = EGL_OPENGL_ES3_BIT;
name = "GLES 3.x";
@@ -66,27 +71,37 @@ static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
- EGL_ALPHA_SIZE, (vo_flags & VOFLAG_ALPHA ) ? 1 : 0,
- EGL_DEPTH_SIZE, 0,
+ EGL_ALPHA_SIZE, (opts->vo_flags & VOFLAG_ALPHA ) ? 1 : 0,
EGL_RENDERABLE_TYPE, rend,
EGL_NONE
};
- EGLint config_count;
- EGLConfig config;
+ EGLint num_configs;
+ if (!eglChooseConfig(display, attributes, NULL, 0, &num_configs))
+ num_configs = 0;
- eglChooseConfig(display, attributes, &config, 1, &config_count);
+ EGLConfig *configs = talloc_array(NULL, EGLConfig, num_configs);
+ if (!eglChooseConfig(display, attributes, configs, num_configs, &num_configs))
+ num_configs = 0;
- if (!config_count) {
- mp_msg(log, msgl, "Could not find EGL configuration!\n");
+ if (!num_configs) {
+ talloc_free(configs);
+ mp_msg(log, msgl, "Could not choose EGLConfig!\n");
return false;
}
+ int chosen = 0;
+ if (opts->refine_config)
+ chosen = opts->refine_config(opts->user_data, configs, num_configs);
+ EGLConfig config = configs[chosen];
+
+ talloc_free(configs);
+
EGLContext *ctx = NULL;
- if (es) {
+ if (es_version) {
EGLint attrs[] = {
- EGL_CONTEXT_CLIENT_VERSION, es3 ? 3 : 2,
+ EGL_CONTEXT_CLIENT_VERSION, es_version,
EGL_NONE
};
@@ -134,6 +149,18 @@ static bool create_context(EGLDisplay display, struct mp_log *log, bool probing,
bool mpegl_create_context(EGLDisplay display, struct mp_log *log, int vo_flags,
EGLContext *out_context, EGLConfig *out_config)
{
+ return mpegl_create_context_opts(display, log,
+ &(struct mpegl_opts){.vo_flags = vo_flags}, out_context, out_config);
+}
+
+// Create a context and return it and the config it was created with. If it
+// returns false, the out_* pointers are set to NULL.
+bool mpegl_create_context_opts(EGLDisplay display, struct mp_log *log,
+ struct mpegl_opts *opts,
+ EGLContext *out_context, EGLConfig *out_config)
+{
+ assert(opts);
+
*out_context = NULL;
*out_config = NULL;
@@ -143,26 +170,25 @@ bool mpegl_create_context(EGLDisplay display, struct mp_log *log, int vo_flags,
mp_verbose(log, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n",
STR_OR_ERR(version), STR_OR_ERR(vendor), STR_OR_ERR(apis));
- int clean_flags = vo_flags & ~(unsigned)(VOFLAG_GLES | VOFLAG_NO_GLES);
- bool probing = vo_flags & VOFLAG_PROBING;
+ bool probing = opts->vo_flags & VOFLAG_PROBING;
int msgl = probing ? MSGL_V : MSGL_FATAL;
- bool try_desktop = !(vo_flags & VOFLAG_NO_GLES);
+ bool try_gles = !(opts->vo_flags & VOFLAG_NO_GLES);
- if (!(vo_flags & VOFLAG_GLES)) {
+ if (!(opts->vo_flags & VOFLAG_GLES)) {
// Desktop OpenGL
- if (create_context(display, log, try_desktop | probing, clean_flags, false,
+ if (create_context(display, log, try_gles | probing, 0, opts,
out_context, out_config))
return true;
}
- if (try_desktop) {
+ if (try_gles) {
// ES 3.x
- if (create_context(display, log, true, clean_flags | VOFLAG_GLES, true,
+ if (create_context(display, log, true, 3, opts,
out_context, out_config))
return true;
// ES 2.0
- if (create_context(display, log, probing, clean_flags | VOFLAG_GLES, false,
+ if (create_context(display, log, probing, 2, opts,
out_context, out_config))
return true;
}
diff --git a/video/out/opengl/egl_helpers.h b/video/out/opengl/egl_helpers.h
index 04197493f3..ea751d4c5e 100644
--- a/video/out/opengl/egl_helpers.h
+++ b/video/out/opengl/egl_helpers.h
@@ -11,4 +11,20 @@ struct mp_log;
bool mpegl_create_context(EGLDisplay display, struct mp_log *log, int vo_flags,
EGLContext *out_context, EGLConfig *out_config);
+struct mpegl_opts {
+ // combination of VOFLAG_* values.
+ int vo_flags;
+
+ // for callbacks
+ void *user_data;
+
+ // if set, pick the desired config from the given list and return its index
+ // defaults to 0 (they are sorted by eglChooseConfig)
+ int (*refine_config)(void *user_data, EGLConfig *configs, int num_configs);
+};
+
+bool mpegl_create_context_opts(EGLDisplay display, struct mp_log *log,
+ struct mpegl_opts *opts,
+ EGLContext *out_context, EGLConfig *out_config);
+
#endif
diff --git a/video/out/opengl/hwdec.c b/video/out/opengl/hwdec.c
index 261931a8d4..ab365df8ff 100644
--- a/video/out/opengl/hwdec.c
+++ b/video/out/opengl/hwdec.c
@@ -22,6 +22,7 @@
#include "common/common.h"
#include "common/msg.h"
+#include "options/m_config.h"
#include "hwdec.h"
extern const struct gl_hwdec_driver gl_hwdec_vaegl;
@@ -110,6 +111,68 @@ struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl,
return NULL;
}
+// Load by option name.
+struct gl_hwdec *gl_hwdec_load(struct mp_log *log, GL *gl,
+ struct mpv_global *g,
+ struct mp_hwdec_devices *devs,
+ const char *name)
+{
+ int g_hwdec_api;
+ mp_read_option_raw(g, "hwdec", &m_option_type_choice, &g_hwdec_api);
+ if (!name || !name[0])
+ name = m_opt_choice_str(mp_hwdec_names, g_hwdec_api);
+
+ int api_id = HWDEC_NONE;
+ for (int n = 0; mp_hwdec_names[n].name; n++) {
+ if (name && strcmp(mp_hwdec_names[n].name, name) == 0)
+ api_id = mp_hwdec_names[n].value;
+ }
+
+ for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
+ const struct gl_hwdec_driver *drv = mpgl_hwdec_drivers[n];
+ if (name && strcmp(drv->name, name) == 0) {
+ struct gl_hwdec *r = load_hwdec_driver(log, gl, g, devs, drv, false);
+ if (r)
+ return r;
+ }
+ }
+
+ return gl_hwdec_load_api(log, gl, g, devs, api_id);
+}
+
+int gl_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param)
+{
+ bool help = bstr_equals0(param, "help");
+ if (help)
+ mp_info(log, "Available hwdecs:\n");
+ for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
+ const struct gl_hwdec_driver *drv = mpgl_hwdec_drivers[n];
+ const char *api_name = m_opt_choice_str(mp_hwdec_names, drv->api);
+ if (help) {
+ mp_info(log, " %s [%s]\n", drv->name, api_name);
+ } else if (bstr_equals0(param, drv->name) ||
+ bstr_equals0(param, api_name))
+ {
+ return 1;
+ }
+ }
+ if (help) {
+ mp_info(log, " auto (loads best)\n"
+ " (other --hwdec values)\n"
+ "Setting an empty string means use --hwdec.\n");
+ return M_OPT_EXIT;
+ }
+ if (!param.len)
+ return 1; // "" is treated specially
+ for (int n = 0; mp_hwdec_names[n].name; n++) {
+ if (bstr_equals0(param, mp_hwdec_names[n].name))
+ return 1;
+ }
+ mp_fatal(log, "No hwdec backend named '%.*s' found!\n", BSTR_P(param));
+ return M_OPT_INVALID;
+}
+
void gl_hwdec_uninit(struct gl_hwdec *hwdec)
{
if (hwdec)
diff --git a/video/out/opengl/hwdec.h b/video/out/opengl/hwdec.h
index 6a1bb98bff..948b42bc74 100644
--- a/video/out/opengl/hwdec.h
+++ b/video/out/opengl/hwdec.h
@@ -81,6 +81,14 @@ struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl,
struct mp_hwdec_devices *devs,
enum hwdec_type api);
+struct gl_hwdec *gl_hwdec_load(struct mp_log *log, GL *gl,
+ struct mpv_global *g,
+ struct mp_hwdec_devices *devs,
+ const char *name);
+
+int gl_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param);
+
void gl_hwdec_uninit(struct gl_hwdec *hwdec);
bool gl_hwdec_test_format(struct gl_hwdec *hwdec, int imgfmt);
diff --git a/video/out/opengl/hwdec_cuda.c b/video/out/opengl/hwdec_cuda.c
index 4edad96c7a..40c4e19041 100644
--- a/video/out/opengl/hwdec_cuda.c
+++ b/video/out/opengl/hwdec_cuda.c
@@ -28,16 +28,21 @@
*/
#include "cuda_dynamic.h"
-#include "video/mp_image_pool.h"
+
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_cuda.h>
+
+#include "formats.h"
#include "hwdec.h"
#include "video.h"
struct priv {
struct mp_hwdec_ctx hwctx;
struct mp_image layout;
- GLuint gl_textures[2];
- CUgraphicsResource cu_res[2];
- CUarray cu_array[2];
+ GLuint gl_textures[4];
+ CUgraphicsResource cu_res[4];
+ CUarray cu_array[4];
+ int sample_count[4];
int sample_width;
CUcontext cuda_ctx;
@@ -66,79 +71,14 @@ static int check_cu(struct gl_hwdec *hw, CUresult err, const char *func)
#define CHECK_CU(x) check_cu(hw, (x), #x)
-static struct mp_image *cuda_download_image(struct mp_hwdec_ctx *ctx,
- struct mp_image *hw_image,
- struct mp_image_pool *swpool)
-{
- CUcontext cuda_ctx = ctx->ctx;
- CUcontext dummy;
- CUresult err, eerr;
-
- if (hw_image->imgfmt != IMGFMT_CUDA)
- return NULL;
-
- int sample_width;
- switch (hw_image->params.hw_subfmt) {
- case IMGFMT_NV12:
- sample_width = 1;
- break;
- case IMGFMT_P010:
- case IMGFMT_P016:
- sample_width = 2;
- break;
- default:
- return NULL;
- }
-
- struct mp_image *out = mp_image_pool_get(swpool,
- hw_image->params.hw_subfmt,
- hw_image->w, hw_image->h);
- if (!out)
- return NULL;
-
- err = cuCtxPushCurrent(cuda_ctx);
- if (err != CUDA_SUCCESS)
- goto error;
-
- mp_image_set_size(out, hw_image->w, hw_image->h);
- mp_image_copy_attributes(out, hw_image);
-
- for (int n = 0; n < 2; n++) {
- CUDA_MEMCPY2D cpy = {
- .srcMemoryType = CU_MEMORYTYPE_DEVICE,
- .dstMemoryType = CU_MEMORYTYPE_HOST,
- .srcDevice = (CUdeviceptr)hw_image->planes[n],
- .dstHost = out->planes[n],
- .srcPitch = hw_image->stride[n],
- .dstPitch = out->stride[n],
- .WidthInBytes = mp_image_plane_w(out, n) *
- (n + 1) * sample_width,
- .Height = mp_image_plane_h(out, n),
- };
-
- err = cuMemcpy2D(&cpy);
- if (err != CUDA_SUCCESS) {
- goto error;
- }
- }
-
- error:
- eerr = cuCtxPopCurrent(&dummy);
- if (eerr != CUDA_SUCCESS || err != CUDA_SUCCESS) {
- talloc_free(out);
- return NULL;
- }
-
- return out;
-}
-
static int cuda_create(struct gl_hwdec *hw)
{
CUdevice device;
CUcontext cuda_ctx = NULL;
+ AVBufferRef *hw_device_ctx = NULL;
CUcontext dummy;
unsigned int device_count;
- int ret = 0, eret = 0;
+ int ret = 0;
if (hw->gl->version < 210 && hw->gl->es < 300) {
MP_VERBOSE(hw, "need OpenGL >= 2.1 or OpenGL-ES >= 3.0\n");
@@ -169,20 +109,39 @@ static int cuda_create(struct gl_hwdec *hw)
p->cuda_ctx = cuda_ctx;
+ hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
+ if (!hw_device_ctx)
+ goto error;
+
+ AVHWDeviceContext *device_ctx = (void *)hw_device_ctx->data;
+
+ AVCUDADeviceContext *device_hwctx = device_ctx->hwctx;
+ device_hwctx->cuda_ctx = cuda_ctx;
+
+ ret = av_hwdevice_ctx_init(hw_device_ctx);
+ if (ret < 0) {
+ MP_ERR(hw, "av_hwdevice_ctx_init failed\n");
+ goto error;
+ }
+
+ ret = CHECK_CU(cuCtxPopCurrent(&dummy));
+ if (ret < 0)
+ goto error;
+
p->hwctx = (struct mp_hwdec_ctx) {
.type = HWDEC_CUDA,
.ctx = cuda_ctx,
- .download_image = cuda_download_image,
+ .av_device_ref = hw_device_ctx,
};
p->hwctx.driver_name = hw->driver->name;
hwdec_devices_add(hw->devs, &p->hwctx);
+ return 0;
error:
- eret = CHECK_CU(cuCtxPopCurrent(&dummy));
- if (eret < 0)
- return eret;
+ av_buffer_unref(&hw_device_ctx);
+ CHECK_CU(cuCtxPopCurrent(&dummy));
- return ret;
+ return -1;
}
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
@@ -198,21 +157,25 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
mp_image_set_params(&p->layout, params);
- GLint luma_format, chroma_format;
- GLenum type;
+ for (int n = 0; n < 4; n++)
+ p->sample_count[n] = 0;
+
switch (params->imgfmt) {
case IMGFMT_NV12:
- luma_format = GL_R8;
- chroma_format = GL_RG8;
- type = GL_UNSIGNED_BYTE;
p->sample_width = 1;
+ p->sample_count[0] = 1;
+ p->sample_count[1] = 2;
break;
case IMGFMT_P010:
case IMGFMT_P016:
- luma_format = GL_R16;
- chroma_format = GL_RG16;
- type = GL_UNSIGNED_SHORT;
p->sample_width = 2;
+ p->sample_count[0] = 1;
+ p->sample_count[1] = 2;
+ break;
+ case IMGFMT_420P:
+ p->sample_width = 1;
+ for (int n = 0; n < 3; n++)
+ p->sample_count[n] = 1;
break;
default:
MP_ERR(hw, "Unsupported format: %s\n", mp_imgfmt_to_name(params->imgfmt));
@@ -223,18 +186,24 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
if (ret < 0)
return ret;
- gl->GenTextures(2, p->gl_textures);
- for (int n = 0; n < 2; n++) {
+ gl->GenTextures(4, p->gl_textures);
+ for (int n = 0; n < 4; n++) {
+ if (!p->sample_count[n])
+ break;
+
+ const struct gl_format *fmt =
+ gl_find_unorm_format(gl, p->sample_width, p->sample_count[n]);
+
gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]);
GLenum filter = GL_NEAREST;
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
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);
- gl->TexImage2D(GL_TEXTURE_2D, 0, n == 0 ? luma_format : chroma_format,
+ gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format,
mp_image_plane_w(&p->layout, n),
mp_image_plane_h(&p->layout, n),
- 0, n == 0 ? GL_RED : GL_RG, type, NULL);
+ 0, fmt->format, fmt->type, NULL);
gl->BindTexture(GL_TEXTURE_2D, 0);
ret = CHECK_CU(cuGraphicsGLRegisterImage(&p->cu_res[n], p->gl_textures[n],
@@ -273,17 +242,19 @@ static void destroy(struct gl_hwdec *hw)
// Don't bail if any CUDA calls fail. This is all best effort.
CHECK_CU(cuCtxPushCurrent(p->cuda_ctx));
- for (int n = 0; n < 2; n++) {
+ for (int n = 0; n < 4; n++) {
if (p->cu_res[n] > 0)
CHECK_CU(cuGraphicsUnregisterResource(p->cu_res[n]));
+ p->cu_res[n] = 0;
}
CHECK_CU(cuCtxPopCurrent(&dummy));
CHECK_CU(cuCtxDestroy(p->cuda_ctx));
- gl->DeleteTextures(2, p->gl_textures);
+ gl->DeleteTextures(4, p->gl_textures);
hwdec_devices_remove(hw->devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
}
static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
@@ -299,7 +270,10 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
*out_frame = (struct gl_hwdec_frame) { 0, };
- for (int n = 0; n < 2; n++) {
+ for (int n = 0; n < 4; n++) {
+ if (!p->sample_count[n])
+ break;
+
// widthInBytes must account for the chroma plane
// elements being two samples wide.
CUDA_MEMCPY2D cpy = {
@@ -310,7 +284,7 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
.srcY = 0,
.dstArray = p->cu_array[n],
.WidthInBytes = mp_image_plane_w(&p->layout, n) *
- (n + 1) * p->sample_width,
+ p->sample_count[n] * p->sample_width,
.Height = mp_image_plane_h(&p->layout, n),
};
ret = CHECK_CU(cuMemcpy2D(&cpy));
diff --git a/video/out/opengl/hwdec_vaegl.c b/video/out/opengl/hwdec_vaegl.c
index 96a35fcb22..a66ad7f390 100644
--- a/video/out/opengl/hwdec_vaegl.c
+++ b/video/out/opengl/hwdec_vaegl.c
@@ -24,6 +24,11 @@
#include <va/va_drmcommon.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+
+#include "config.h"
+
#include "hwdec.h"
#include "video/vaapi.h"
#include "video/img_fourcc.h"
@@ -118,6 +123,8 @@ struct priv {
VAImage current_image;
bool buffer_acquired;
int current_mpfmt;
+ int *formats;
+ bool probing_formats; // temporary during init
EGLImageKHR (EGLAPIENTRY *CreateImageKHR)(EGLDisplay, EGLContext,
EGLenum, EGLClientBuffer,
@@ -126,7 +133,7 @@ struct priv {
void (EGLAPIENTRY *EGLImageTargetTexture2DOES)(GLenum, GLeglImageOES);
};
-static bool test_format(struct gl_hwdec *hw);
+static void determine_working_formats(struct gl_hwdec *hw);
static void unmap_frame(struct gl_hwdec *hw)
{
@@ -139,8 +146,6 @@ static void unmap_frame(struct gl_hwdec *hw)
p->images[n] = 0;
}
- va_lock(p->ctx);
-
if (p->buffer_acquired) {
status = vaReleaseBufferHandle(p->display, p->current_image.buf);
CHECK_VA_STATUS(p, "vaReleaseBufferHandle()");
@@ -151,8 +156,6 @@ static void unmap_frame(struct gl_hwdec *hw)
CHECK_VA_STATUS(p, "vaDestroyImage()");
p->current_image.image_id = VA_INVALID_ID;
}
-
- va_unlock(p->ctx);
}
static void destroy_textures(struct gl_hwdec *hw)
@@ -219,6 +222,11 @@ static int create(struct gl_hwdec *hw)
vaTerminate(p->display);
return -1;
}
+ if (!p->ctx->av_device_ref) {
+ MP_VERBOSE(hw, "libavutil vaapi code rejected the driver?\n");
+ destroy(hw);
+ return -1;
+ }
if (hw->probing && va_guess_if_emulated(p->ctx)) {
destroy(hw);
@@ -227,16 +235,27 @@ static int create(struct gl_hwdec *hw)
MP_VERBOSE(p, "using VAAPI EGL interop\n");
- if (!test_format(hw)) {
+ determine_working_formats(hw);
+ if (!p->formats || !p->formats[0]) {
destroy(hw);
return -1;
}
+ p->ctx->hwctx.supported_formats = p->formats;
p->ctx->hwctx.driver_name = hw->driver->name;
hwdec_devices_add(hw->devs, &p->ctx->hwctx);
return 0;
}
+static bool check_fmt(struct priv *p, int fmt)
+{
+ for (int n = 0; p->formats[n]; n++) {
+ if (p->formats[n] == fmt)
+ return true;
+ }
+ return false;
+}
+
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
{
struct priv *p = hw->priv;
@@ -256,16 +275,13 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
gl->BindTexture(GL_TEXTURE_2D, 0);
p->current_mpfmt = params->hw_subfmt;
- if (p->current_mpfmt != IMGFMT_NV12 &&
- p->current_mpfmt != IMGFMT_420P)
- {
+
+ if (!p->probing_formats && !check_fmt(p, p->current_mpfmt)) {
MP_FATAL(p, "unsupported VA image format %s\n",
mp_imgfmt_to_name(p->current_mpfmt));
return -1;
}
- MP_VERBOSE(p, "hw format: %s\n", mp_imgfmt_to_name(p->current_mpfmt));
-
params->imgfmt = p->current_mpfmt;
params->hw_subfmt = 0;
@@ -290,19 +306,10 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
unmap_frame(hw);
- va_lock(p->ctx);
-
status = vaDeriveImage(p->display, va_surface_id(hw_image), va_image);
if (!CHECK_VA_STATUS(p, "vaDeriveImage()"))
goto err;
- int mpfmt = va_fourcc_to_imgfmt(va_image->format.fourcc);
- if (p->current_mpfmt != mpfmt) {
- MP_FATAL(p, "mid-stream hwdec format change (%s -> %s) not supported\n",
- mp_imgfmt_to_name(p->current_mpfmt), mp_imgfmt_to_name(mpfmt));
- goto err;
- }
-
VABufferInfo buffer_info = {.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME};
status = vaAcquireBufferHandle(p->display, va_image->buf, &buffer_info);
if (!CHECK_VA_STATUS(p, "vaAcquireBufferHandle()"))
@@ -311,19 +318,41 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
struct mp_image layout = {0};
mp_image_set_params(&layout, &hw_image->params);
- mp_image_setfmt(&layout, mpfmt);
-
- // (it would be nice if we could use EGL_IMAGE_INTERNAL_FORMAT_EXT)
- int drm_fmts[4] = {MP_FOURCC('R', '8', ' ', ' '), // DRM_FORMAT_R8
- MP_FOURCC('G', 'R', '8', '8'), // DRM_FORMAT_GR88
- MP_FOURCC('R', 'G', '2', '4'), // DRM_FORMAT_RGB888
- MP_FOURCC('R', 'A', '2', '4')}; // DRM_FORMAT_RGBA8888
+ mp_image_setfmt(&layout, p->current_mpfmt);
+ struct mp_imgfmt_desc fmt = layout.fmt;
+
+ int drm_fmts[8] = {
+ // 1 bytes per component, 1-4 components
+ MP_FOURCC('R', '8', ' ', ' '), // DRM_FORMAT_R8
+ MP_FOURCC('G', 'R', '8', '8'), // DRM_FORMAT_GR88
+ 0, // untested (DRM_FORMAT_RGB888?)
+ 0, // untested (DRM_FORMAT_RGBA8888?)
+ // 2 bytes per component, 1-4 components
+ MP_FOURCC('R', '1', '6', ' '), // proposed DRM_FORMAT_R16
+ MP_FOURCC('G', 'R', '3', '2'), // proposed DRM_FORMAT_GR32
+ 0, // N/A
+ 0, // N/A
+ };
for (int n = 0; n < layout.num_planes; n++) {
int attribs[20] = {EGL_NONE};
int num_attribs = 0;
- ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, drm_fmts[layout.fmt.bytes[n] - 1]);
+ int fmt_index = -1;
+ int cbits = fmt.component_bits;
+ if ((fmt.flags & (MP_IMGFLAG_YUV_P | MP_IMGFLAG_YUV_NV)) &&
+ (fmt.flags & MP_IMGFLAG_NE) && cbits >= 8 && cbits <= 16)
+ {
+ // Regular planar and semi-planar formats.
+ fmt_index = fmt.components[n] - 1 + 4 * ((cbits + 7) / 8 - 1);
+ } else if (fmt.id == IMGFMT_RGB0 || fmt.id == IMGFMT_BGR0) {
+ fmt_index = 3 + 4 * ((cbits + 7) / 8 - 1);
+ }
+
+ if (fmt_index < 0 || fmt_index >= 8 || !drm_fmts[fmt_index])
+ goto err;
+
+ ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, drm_fmts[fmt_index]);
ADD_ATTRIB(EGL_WIDTH, mp_image_plane_w(&layout, n));
ADD_ATTRIB(EGL_HEIGHT, mp_image_plane_h(&layout, n));
ADD_ATTRIB(EGL_DMA_BUF_PLANE0_FD_EXT, buffer_info.handle);
@@ -350,37 +379,93 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
if (va_image->format.fourcc == VA_FOURCC_YV12)
MPSWAP(struct gl_hwdec_plane, out_frame->planes[1], out_frame->planes[2]);
- va_unlock(p->ctx);
return 0;
err:
- va_unlock(p->ctx);
- MP_FATAL(p, "mapping VAAPI EGL image failed\n");
+ if (!p->probing_formats)
+ MP_FATAL(p, "mapping VAAPI EGL image failed\n");
unmap_frame(hw);
return -1;
}
-static bool test_format(struct gl_hwdec *hw)
+static bool try_format(struct gl_hwdec *hw, struct mp_image *surface)
{
- struct priv *p = hw->priv;
bool ok = false;
+ struct mp_image_params params = surface->params;
+ if (reinit(hw, &params) >= 0) {
+ struct gl_hwdec_frame frame = {0};
+ ok = map_frame(hw, surface, &frame) >= 0;
+ }
+ unmap_frame(hw);
+ return ok;
+}
- struct mp_image_pool *alloc = mp_image_pool_new(1);
- va_pool_set_allocator(alloc, p->ctx, VA_RT_FORMAT_YUV420);
- struct mp_image *surface = mp_image_pool_get(alloc, IMGFMT_VAAPI, 64, 64);
- if (surface) {
- va_surface_init_subformat(surface);
- struct mp_image_params params = surface->params;
- if (reinit(hw, &params) >= 0) {
- struct gl_hwdec_frame frame = {0};
- ok = map_frame(hw, surface, &frame) >= 0;
+static void determine_working_formats(struct gl_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+ int num_formats = 0;
+ int *formats = NULL;
+
+ p->probing_formats = true;
+
+ if (HAVE_VAAPI_HWACCEL_OLD) {
+ struct mp_image_pool *alloc = mp_image_pool_new(1);
+ va_pool_set_allocator(alloc, p->ctx, VA_RT_FORMAT_YUV420);
+ struct mp_image *s = mp_image_pool_get(alloc, IMGFMT_VAAPI, 64, 64);
+ if (s) {
+ va_surface_init_subformat(s);
+ if (try_format(hw, s))
+ MP_TARRAY_APPEND(p, formats, num_formats, IMGFMT_NV12);
}
- unmap_frame(hw);
+ talloc_free(s);
+ talloc_free(alloc);
+ } else {
+ AVHWFramesConstraints *fc =
+ av_hwdevice_get_hwframe_constraints(p->ctx->av_device_ref, NULL);
+ if (!fc) {
+ MP_WARN(hw, "failed to retrieve libavutil frame constaints\n");
+ goto done;
+ }
+ for (int n = 0; fc->valid_sw_formats[n] != AV_PIX_FMT_NONE; n++) {
+ AVBufferRef *fref = NULL;
+ fref = av_hwframe_ctx_alloc(p->ctx->av_device_ref);
+ if (!fref)
+ goto err;
+ AVHWFramesContext *fctx = (void *)fref->data;
+ struct mp_image *s = NULL;
+ AVFrame *frame = NULL;
+ fctx->format = AV_PIX_FMT_VAAPI;
+ fctx->sw_format = fc->valid_sw_formats[n];
+ fctx->width = 128;
+ fctx->height = 128;
+ if (av_hwframe_ctx_init(fref) < 0)
+ goto err;
+ frame = av_frame_alloc();
+ if (!frame)
+ goto err;
+ if (av_hwframe_get_buffer(fref, frame, 0) < 0)
+ goto err;
+ s = mp_image_from_av_frame(frame);
+ if (!s || !mp_image_params_valid(&s->params))
+ goto err;
+ if (try_format(hw, s))
+ MP_TARRAY_APPEND(p, formats, num_formats, s->params.hw_subfmt);
+ err:
+ talloc_free(s);
+ av_frame_free(&frame);
+ av_buffer_unref(&fref);
+ }
+ av_hwframe_constraints_free(&fc);
}
- talloc_free(surface);
- talloc_free(alloc);
- return ok;
+done:
+ MP_TARRAY_APPEND(p, formats, num_formats, 0); // terminate it
+ p->formats = formats;
+ p->probing_formats = false;
+
+ MP_VERBOSE(hw, "Supported formats:\n");
+ for (int n = 0; formats[n]; n++)
+ MP_VERBOSE(hw, " %s\n", mp_imgfmt_to_name(formats[n]));
}
const struct gl_hwdec_driver gl_hwdec_vaegl = {
diff --git a/video/out/opengl/hwdec_vaglx.c b/video/out/opengl/hwdec_vaglx.c
index ac817d79c4..6248a64434 100644
--- a/video/out/opengl/hwdec_vaglx.c
+++ b/video/out/opengl/hwdec_vaglx.c
@@ -181,14 +181,12 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
if (!p->pixmap)
return -1;
- va_lock(p->ctx);
status = vaPutSurface(p->display, va_surface_id(hw_image), p->pixmap,
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.color.space));
CHECK_VA_STATUS(p, "vaPutSurface()");
- va_unlock(p->ctx);
*out_frame = (struct gl_hwdec_frame){
.planes = {
diff --git a/video/out/opengl/hwdec_vdpau.c b/video/out/opengl/hwdec_vdpau.c
index 06dc71e5e9..712997ed7a 100644
--- a/video/out/opengl/hwdec_vdpau.c
+++ b/video/out/opengl/hwdec_vdpau.c
@@ -158,7 +158,8 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
p->vdpgl_initialized = true;
- p->direct_mode = params->hw_subfmt == IMGFMT_NV12;
+ p->direct_mode = params->hw_subfmt == IMGFMT_NV12 ||
+ params->hw_subfmt == IMGFMT_420P;
gl->GenTextures(4, p->gl_textures);
for (int n = 0; n < 4; n++) {
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 3bd67c29fe..f9f1f870e5 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -1685,7 +1685,7 @@ static void pass_read_video(struct gl_video *p)
GLSLF("// merging plane %d ...\n", i);
copy_img_tex(p, &num, tex[i]);
first = MPMIN(first, i);
- memset(&tex[i], 0, sizeof(tex[i]));
+ tex[i] = (struct img_tex){0};
}
}
@@ -1694,7 +1694,7 @@ static void pass_read_video(struct gl_video *p)
copy_img_tex(p, &num, tex[n]);
finish_pass_fbo(p, &p->merge_fbo[n], tex[n].w, tex[n].h, 0);
tex[first] = img_tex_fbo(&p->merge_fbo[n], tex[n].type, num);
- memset(&tex[n], 0, sizeof(tex[n]));
+ tex[n] = (struct img_tex){0};
}
}
@@ -1775,6 +1775,8 @@ static void pass_read_video(struct gl_video *p)
{0.0, (ref.y1 - ref.y0) / (rect.y1 - rect.y0)}},
.t = {ref.x0, ref.y0},
};
+ MP_DBG(p, "-> fix[%d] = {%f %f} + off {%f %f}\n", n,
+ fix.m[0][0], fix.m[1][1], fix.t[0], fix.t[1]);
// Since the scale in texture space is different from the scale in
// absolute terms, we have to scale the coefficients down to be
@@ -1784,8 +1786,11 @@ static void pass_read_video(struct gl_video *p)
{0.0, (float)tex[n].h / p->texture_h}},
.t = {-rect.x0, -rect.y0},
};
+ if (p->image_params.rotate % 180 == 90)
+ MPSWAP(double, scale.m[0][0], scale.m[1][1]);
+
gl_transform_trans(scale, &fix);
- MP_DBG(p, "-> fix[%d] = {%f %f} + off {%f %f}\n", n,
+ MP_DBG(p, "-> scaled[%d] = {%f %f} + off {%f %f}\n", n,
fix.m[0][0], fix.m[1][1], fix.t[0], fix.t[1]);
// Since the texture transform is a function of the texture coordinates
diff --git a/video/out/vo.c b/video/out/vo.c
index 64bf7ab051..f0c865d269 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -808,7 +808,7 @@ static bool render_frame(struct vo *vo)
pthread_mutex_unlock(&in->lock);
wakeup_core(vo); // core can queue new video now
- MP_STATS(vo, "start video");
+ MP_STATS(vo, "start video-draw");
if (vo->driver->draw_frame) {
vo->driver->draw_frame(vo, frame);
@@ -816,12 +816,15 @@ static bool render_frame(struct vo *vo)
vo->driver->draw_image(vo, mp_image_new_ref(frame->current));
}
+ MP_STATS(vo, "end video-draw");
+
wait_until(vo, target);
+ MP_STATS(vo, "start video-flip");
+
vo->driver->flip_page(vo);
- MP_STATS(vo, "end video");
- MP_STATS(vo, "video_end");
+ MP_STATS(vo, "end video-flip");
pthread_mutex_lock(&in->lock);
in->dropped_frame = prev_drop_count < vo->in->drop_count;
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
index 92357587c5..3436e0fc5c 100644
--- a/video/out/vo_drm.c
+++ b/video/out/vo_drm.c
@@ -20,12 +20,10 @@
#include <fcntl.h>
#include <stdbool.h>
#include <sys/mman.h>
-#include <sys/poll.h>
+#include <poll.h>
#include <unistd.h>
#include <libswscale/swscale.h>
-#include <xf86drm.h>
-#include <xf86drmMode.h>
#include "drm_common.h"
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index 7f65a1eafc..4917a251c6 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -54,8 +54,6 @@ struct vo_opengl_opts {
int use_gl_debug;
int allow_sw;
int swap_interval;
- int dwm_flush;
- int allow_direct_composition;
int vsync_fences;
char *backend;
int es;
@@ -383,16 +381,11 @@ static int preinit(struct vo *vo)
if (p->opts.allow_sw)
vo_flags |= VOFLAG_SW;
- if (p->opts.allow_direct_composition)
- vo_flags |= VOFLAG_ANGLE_DCOMP;
-
p->glctx = mpgl_init(vo, p->opts.backend, vo_flags);
if (!p->glctx)
goto err_out;
p->gl = p->glctx->gl;
- p->glctx->dwm_flush_opt = p->opts.dwm_flush;
-
if (p->gl->SwapInterval) {
p->gl->SwapInterval(p->opts.swap_interval);
} else {
@@ -411,14 +404,9 @@ static int preinit(struct vo *vo)
hwdec_devices_set_loader(vo->hwdec_devs, call_request_hwdec_api, vo);
- int hwdec = vo->opts->hwdec_preload_api;
- if (hwdec == HWDEC_NONE)
- hwdec = vo->global->opts->hwdec_api;
- if (hwdec != HWDEC_NONE) {
- p->hwdec = gl_hwdec_load_api(p->vo->log, p->gl, vo->global,
- vo->hwdec_devs, hwdec);
- gl_video_set_hwdec(p->renderer, p->hwdec);
- }
+ p->hwdec = gl_hwdec_load(p->vo->log, p->gl, vo->global,
+ vo->hwdec_devs, vo->opts->gl_hwdec_interop);
+ gl_video_set_hwdec(p->renderer, p->hwdec);
return 0;
@@ -447,9 +435,6 @@ const struct vo_driver video_out_opengl = {
OPT_FLAG("opengl-glfinish", opts.use_glFinish, 0),
OPT_FLAG("opengl-waitvsync", opts.waitvsync, 0),
OPT_INT("opengl-swapinterval", opts.swap_interval, 0),
- OPT_CHOICE("opengl-dwmflush", opts.dwm_flush, 0,
- ({"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})),
- OPT_FLAG("opengl-dcomposition", opts.allow_direct_composition, 0),
OPT_FLAG("opengl-debug", opts.use_gl_debug, 0),
OPT_STRING_VALIDATE("opengl-backend", opts.backend, 0,
mpgl_validate_backend_opt),
@@ -464,7 +449,6 @@ const struct vo_driver video_out_opengl = {
.priv_defaults = &(const struct gl_priv){
.opts = {
.swap_interval = 1,
- .allow_direct_composition = 1,
},
},
};
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index 852b7900da..9ecdf5eaed 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -74,12 +74,10 @@ struct mpv_opengl_cb_context {
bool flip;
bool force_update;
bool imgfmt_supported[IMGFMT_END - IMGFMT_START];
- struct mp_vo_opts vo_opts;
bool update_new_opts;
bool eq_changed;
struct mp_csp_equalizer eq;
struct vo *active;
- int hwdec_api;
// --- This is only mutable while initialized=false, during which nothing
// except the OpenGL context manager is allowed to access it.
@@ -91,6 +89,8 @@ struct mpv_opengl_cb_context {
GL *gl;
struct gl_video *renderer;
struct gl_hwdec *hwdec;
+ struct m_config_cache *vo_opts_cache;
+ struct mp_vo_opts *vo_opts;
};
static void update(struct vo_priv *p);
@@ -128,28 +128,12 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
ctx->log = mp_log_new(ctx, g->log, "opengl-cb");
ctx->client_api = client_api;
- ctx->hwdec_api = g->opts->vo->hwdec_preload_api;
- if (ctx->hwdec_api == HWDEC_NONE)
- ctx->hwdec_api = g->opts->hwdec_api;
+ ctx->vo_opts_cache = m_config_cache_alloc(ctx, ctx->global, &vo_sub_opts);
+ ctx->vo_opts = ctx->vo_opts_cache->opts;
return ctx;
}
-// To be called from VO thread, with p->ctx->lock held.
-static void copy_vo_opts(struct vo *vo)
-{
- struct vo_priv *p = vo->priv;
-
- // We're being lazy: none of the options we need use dynamic data, so
- // copy the struct with an assignment.
- // Just remove all the dynamic data to avoid confusion.
- struct mp_vo_opts opts = *vo->opts;
- opts.video_driver_list = NULL;
- opts.winname = NULL;
- opts.sws_opts = NULL;
- p->ctx->vo_opts = opts;
-}
-
void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx,
mpv_opengl_cb_update_fn callback,
void *callback_ctx)
@@ -181,9 +165,11 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts,
if (!ctx->renderer)
return MPV_ERROR_UNSUPPORTED;
+ m_config_cache_update(ctx->vo_opts_cache);
+
ctx->hwdec_devs = hwdec_devices_create();
- ctx->hwdec = gl_hwdec_load_api(ctx->log, ctx->gl, ctx->global,
- ctx->hwdec_devs, ctx->hwdec_api);
+ ctx->hwdec = gl_hwdec_load(ctx->log, ctx->gl, ctx->global,
+ ctx->hwdec_devs, ctx->vo_opts->gl_hwdec_interop);
gl_video_set_hwdec(ctx->renderer, ctx->hwdec);
pthread_mutex_lock(&ctx->lock);
@@ -252,9 +238,11 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
ctx->vp_w = vp_w;
ctx->vp_h = vp_h;
+ m_config_cache_update(ctx->vo_opts_cache);
+
struct mp_rect src, dst;
struct mp_osd_res osd;
- mp_get_src_dst_rects(ctx->log, &ctx->vo_opts, vo->driver->caps,
+ mp_get_src_dst_rects(ctx->log, ctx->vo_opts, vo->driver->caps,
&ctx->img_params, vp_w, abs(vp_h),
1.0, &src, &dst, &osd);
@@ -475,7 +463,6 @@ static int control(struct vo *vo, uint32_t request, void *data)
}
case VOCTRL_SET_PANSCAN:
pthread_mutex_lock(&p->ctx->lock);
- copy_vo_opts(vo);
p->ctx->force_update = true;
update(p);
pthread_mutex_unlock(&p->ctx->lock);
@@ -522,7 +509,6 @@ static int preinit(struct vo *vo)
p->ctx->active = vo;
p->ctx->reconfigured = true;
p->ctx->update_new_opts = true;
- copy_vo_opts(vo);
memset(p->ctx->eq.values, 0, sizeof(p->ctx->eq.values));
p->ctx->eq_changed = true;
pthread_mutex_unlock(&p->ctx->lock);
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
index 4aa1ef37df..85b8159f68 100644
--- a/video/out/vo_vaapi.c
+++ b/video/out/vo_vaapi.c
@@ -205,8 +205,6 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
if (surface == VA_INVALID_ID)
return false;
- va_lock(p->mpvaapi);
-
for (int n = 0; n < MAX_OSD_PARTS; n++) {
struct vaapi_osd_part *part = &p->osd_parts[n];
if (part->active) {
@@ -252,8 +250,6 @@ static bool render_to_screen(struct priv *p, struct mp_image *mpi)
}
}
- va_unlock(p->mpvaapi);
-
return true;
}
@@ -424,8 +420,6 @@ static void draw_osd(struct vo *vo)
if (!p->osd_format.fourcc)
return;
- va_lock(p->mpvaapi);
-
struct mp_osd_res vid_res = osd_res_from_image_params(vo->params);
struct mp_osd_res *res;
@@ -438,8 +432,6 @@ static void draw_osd(struct vo *vo)
for (int n = 0; n < MAX_OSD_PARTS; n++)
p->osd_parts[n].active = false;
osd_draw(vo->osd, *res, pts, 0, osd_formats, draw_osd_cb, p);
-
- va_unlock(p->mpvaapi);
}
static int get_displayattribtype(const char *name)
@@ -520,9 +512,7 @@ static int set_equalizer(struct priv *p, const char *name, int value)
MP_VERBOSE(p, "Changing '%s' (range [%d, %d]) to %d\n", name,
attr->max_value, attr->min_value, attr->value);
- va_lock(p->mpvaapi);
status = vaSetDisplayAttributes(p->display, attr, 1);
- va_unlock(p->mpvaapi);
if (!CHECK_VA_STATUS(p, "vaSetDisplayAttributes()"))
return VO_FALSE;
return VO_TRUE;
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index 0de9148257..287bb24618 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -21,6 +21,7 @@
#include <assert.h>
#include <windows.h>
#include <windowsx.h>
+#include <dwmapi.h>
#include <ole2.h>
#include <shobjidl.h>
#include <avrt.h>
@@ -47,8 +48,25 @@
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
+#ifndef WM_DPICHANGED
+#define WM_DPICHANGED (0x02E0)
+#endif
+
+#ifndef DPI_ENUMS_DECLARED
+typedef enum MONITOR_DPI_TYPE {
+ MDT_EFFECTIVE_DPI = 0,
+ MDT_ANGULAR_DPI = 1,
+ MDT_RAW_DPI = 2,
+ MDT_DEFAULT = MDT_EFFECTIVE_DPI
+} MONITOR_DPI_TYPE;
+#endif
+
static __thread struct vo_w32_state *w32_thread_context;
+struct w32_api {
+ HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
+};
+
struct vo_w32_state {
struct mp_log *log;
struct vo *vo;
@@ -59,6 +77,8 @@ struct vo_w32_state {
bool terminate;
struct mp_dispatch_queue *dispatch; // used to run stuff on the GUI thread
+ struct w32_api api; // stores functions from dynamically loaded DLLs
+
HWND window;
HWND parent; // 0 normally, set in embedding mode
HHOOK parent_win_hook;
@@ -81,6 +101,7 @@ struct vo_w32_state {
bool window_bounds_initialized;
bool current_fs;
+ bool toggle_fs; // whether the current fullscreen state needs to be switched
// currently known window state
int window_x;
@@ -92,6 +113,8 @@ struct vo_w32_state {
uint32_t o_dwidth;
uint32_t o_dheight;
+ int dpi;
+
bool disable_screensaver;
bool cursor_visible;
atomic_uint event_flags;
@@ -118,6 +141,10 @@ struct vo_w32_state {
// updates on move/resize/displaychange
double display_fps;
+ bool snapped;
+ int snap_dx;
+ int snap_dy;
+
HANDLE avrt_handle;
};
@@ -630,6 +657,26 @@ done:
return name;
}
+static void update_dpi(struct vo_w32_state *w32)
+{
+ UINT dpiX, dpiY;
+ if (w32->api.pGetDpiForMonitor && w32->api.pGetDpiForMonitor(w32->monitor,
+ MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) {
+ w32->dpi = (int)dpiX;
+ MP_VERBOSE(w32, "DPI detected from the new API: %d\n", w32->dpi);
+ return;
+ }
+ HDC hdc = GetDC(NULL);
+ if (hdc) {
+ w32->dpi = GetDeviceCaps(hdc, LOGPIXELSX);
+ ReleaseDC(NULL, hdc);
+ MP_VERBOSE(w32, "DPI detected from the old API: %d\n", w32->dpi);
+ } else {
+ w32->dpi = 96;
+ MP_VERBOSE(w32, "Couldn't determine DPI, falling back to %d\n", w32->dpi);
+ }
+}
+
static void update_display_info(struct vo_w32_state *w32)
{
HMONITOR monitor = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY);
@@ -637,6 +684,8 @@ static void update_display_info(struct vo_w32_state *w32)
return;
w32->monitor = monitor;
+ update_dpi(w32);
+
MONITORINFOEXW mi = { .cbSize = sizeof mi };
GetMonitorInfoW(monitor, (MONITORINFO*)&mi);
@@ -697,6 +746,81 @@ static void update_playback_state(struct vo_w32_state *w32)
TBPF_NORMAL);
}
+static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
+{
+ if (!w32->opts->snap_window) {
+ w32->snapped = false;
+ return false;
+ }
+
+ RECT rect;
+ POINT cursor;
+ if (!GetWindowRect(w32->window, &rect) || !GetCursorPos(&cursor))
+ return false;
+ // Check for aero snapping
+ if ((rc->right - rc->left != rect.right - rect.left) ||
+ (rc->bottom - rc->top != rect.bottom - rect.top))
+ return false;
+
+ MONITORINFO mi = { .cbSize = sizeof(mi) };
+ if (!GetMonitorInfoW(w32->monitor, &mi))
+ return false;
+ // Get the work area to let the window snap to taskbar
+ RECT wr = mi.rcWork;
+
+ // Check for invisible borders and adjust the work area size
+ RECT frame = {0};
+ if (DwmGetWindowAttribute(w32->window, DWMWA_EXTENDED_FRAME_BOUNDS,
+ &frame, sizeof(RECT)) == S_OK) {
+ wr.left -= frame.left - rect.left;
+ wr.top -= frame.top - rect.top;
+ wr.right += rect.right - frame.right;
+ wr.bottom += rect.bottom - frame.bottom;
+ }
+
+ // Let the window to unsnap by changing its position,
+ // otherwise it will stick to the screen edges forever
+ rect = *rc;
+ if (w32->snapped) {
+ OffsetRect(&rect, cursor.x - rect.left - w32->snap_dx,
+ cursor.y - rect.top - w32->snap_dy);
+ }
+
+ int threshold = (w32->dpi * 16) / 96;
+ bool snapped = false;
+ // Adjust X position
+ if (abs(rect.left - wr.left) < threshold) {
+ snapped = true;
+ OffsetRect(&rect, wr.left - rect.left, 0);
+ } else if (abs(rect.right - wr.right) < threshold) {
+ snapped = true;
+ OffsetRect(&rect, wr.right - rect.right, 0);
+ }
+ // Adjust Y position
+ if (abs(rect.top - wr.top) < threshold) {
+ snapped = true;
+ OffsetRect(&rect, 0, wr.top - rect.top);
+ } else if (abs(rect.bottom - wr.bottom) < threshold) {
+ snapped = true;
+ OffsetRect(&rect, 0, wr.bottom - rect.bottom);
+ }
+
+ if (!w32->snapped && snapped) {
+ w32->snap_dx = cursor.x - rc->left;
+ w32->snap_dy = cursor.y - rc->top;
+ }
+
+ w32->snapped = snapped;
+ *rc = rect;
+ return true;
+}
+
+static void toggle_fullscreen(struct vo_w32_state *w32)
+{
+ w32->toggle_fs = true;
+ signal_events(w32, VO_EVENT_FULLSCREEN_STATE);
+}
+
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
@@ -730,6 +854,24 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
MP_DBG(w32, "move window: %d:%d\n", w32->window_x, w32->window_y);
break;
}
+ case WM_MOVING: {
+ RECT *rc = (RECT*)lParam;
+ if (snap_to_screen_edges(w32, rc))
+ return TRUE;
+ break;
+ }
+ case WM_ENTERSIZEMOVE:
+ if (w32->snapped) {
+ // Save the cursor offset from the window borders,
+ // so the player window can be unsnapped later
+ RECT rc;
+ POINT cursor;
+ if (GetWindowRect(w32->window, &rc) && GetCursorPos(&cursor)) {
+ w32->snap_dx = cursor.x - rc.left;
+ w32->snap_dy = cursor.y - rc.top;
+ }
+ }
+ break;
case WM_SIZE: {
RECT r;
if (GetClientRect(w32->window, &r) && r.right > 0 && r.bottom > 0) {
@@ -767,6 +909,9 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
return TRUE;
}
break;
+ case WM_DPICHANGED:
+ update_display_info(w32);
+ break;
case WM_CLOSE:
// Don't actually allow it to destroy the window, or whatever else it
// is that will make us lose WM_USER wakeups.
@@ -786,7 +931,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
PostQuitMessage(0);
break;
case WM_SYSCOMMAND:
- switch (wParam) {
+ switch (wParam & 0xFFF0) {
case SC_SCREENSAVE:
case SC_MONITORPOWER:
if (w32->disable_screensaver) {
@@ -794,6 +939,12 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
return 0;
}
break;
+ case SC_RESTORE:
+ if (IsMaximized(w32->window) && w32->current_fs) {
+ toggle_fullscreen(w32);
+ return 0;
+ }
+ break;
}
break;
case WM_NCHITTEST:
@@ -1133,9 +1284,10 @@ static void reinit_window_state(struct vo_w32_state *w32)
if (w32->parent)
return;
- bool new_fs = w32->opts->fullscreen;
+ bool new_fs = w32->toggle_fs ? !w32->current_fs : w32->opts->fullscreen;
bool toggle_fs = w32->current_fs != new_fs;
w32->current_fs = new_fs;
+ w32->toggle_fs = false;
if (w32->taskbar_list) {
ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list,
@@ -1333,6 +1485,13 @@ static void thread_disable_ime(void)
FreeLibrary(imm32);
}
+static void w32_api_load(struct vo_w32_state *w32)
+{
+ HMODULE shcore_dll = LoadLibraryW(L"shcore.dll");
+ w32->api.pGetDpiForMonitor = !shcore_dll ? NULL :
+ (void *)GetProcAddress(shcore_dll, "GetDpiForMonitor");
+}
+
static void *gui_thread(void *ptr)
{
struct vo_w32_state *w32 = ptr;
@@ -1341,6 +1500,7 @@ static void *gui_thread(void *ptr)
mpthread_set_name("win32 window");
+ w32_api_load(w32);
thread_disable_ime();
w32_thread_context = w32;
@@ -1549,6 +1709,9 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
case VOCTRL_BORDER:
reinit_window_state(w32);
return VO_TRUE;
+ case VOCTRL_GET_FULLSCREEN:
+ *(bool *)arg = w32->toggle_fs != w32->current_fs;
+ return VO_TRUE;
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
int *s = arg;
@@ -1648,6 +1811,8 @@ static void do_control(void *ptr)
w32->vo->dwidth = w32->dw;
w32->vo->dheight = w32->dh;
}
+ if (*events & VO_EVENT_FULLSCREEN_STATE)
+ reinit_window_state(w32);
}
int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
@@ -1661,6 +1826,8 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
vo->dheight = w32->dh;
mp_dispatch_unlock(w32->dispatch);
}
+ if (*events & VO_EVENT_FULLSCREEN_STATE)
+ reinit_window_state(w32);
return VO_TRUE;
} else {
int r;
diff --git a/video/out/win_state.c b/video/out/win_state.c
index 29eaa1b663..3546daadec 100644
--- a/video/out/win_state.c
+++ b/video/out/win_state.c
@@ -67,13 +67,17 @@ static void apply_autofit(int *w, int *h, int scr_w, int scr_h,
// Compute the "suggested" window size and position and return it in *out_geo.
// screen is the bounding box of the current screen within the virtual desktop.
// Does not change *vo.
+// screen: position of the screen on virtual desktop on which the window
+// should be placed
+// dpi_scale: the DPI multiplier to get from virtual to real coordinates
+// (>1 for "hidpi")
// Use vo_apply_window_geometry() to copy the result into the vo.
// NOTE: currently, all windowing backends do their own handling of window
// geometry additional to this code. This is to deal with initial window
// placement, fullscreen handling, avoiding resize on reconfig() with no
// size change, multi-monitor stuff, and possibly more.
-void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen,
- struct vo_win_geometry *out_geo)
+void vo_calc_window_geometry2(struct vo *vo, const struct mp_rect *screen,
+ double dpi_scale, struct vo_win_geometry *out_geo)
{
struct mp_vo_opts *opts = vo->opts;
@@ -86,12 +90,15 @@ void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen,
if (vo->params)
params = *vo->params;
+ if (!opts->hidpi_window_scale)
+ dpi_scale = 1;
+
int d_w, d_h;
mp_image_params_get_dsize(&params, &d_w, &d_h);
if ((vo->driver->caps & VO_CAP_ROTATE90) && params.rotate % 180 == 90)
MPSWAP(int, d_w, d_h);
- d_w = MPCLAMP(d_w * opts->window_scale, 1, 16000);
- d_h = MPCLAMP(d_h * opts->window_scale, 1, 16000);
+ d_w = MPCLAMP(d_w * opts->window_scale * dpi_scale, 1, 16000);
+ d_h = MPCLAMP(d_h * opts->window_scale * dpi_scale, 1, 16000);
int scr_w = screen->x1 - screen->x0;
int scr_h = screen->y1 - screen->y0;
@@ -118,6 +125,12 @@ void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen,
out_geo->flags |= VO_WIN_FORCE_POS;
}
+void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen,
+ struct vo_win_geometry *out_geo)
+{
+ vo_calc_window_geometry2(vo, screen, 1.0, out_geo);
+}
+
// Copy the parameters in *geo to the vo fields.
// (Doesn't do anything else - windowing backends should trigger VO_EVENT_RESIZE
// to ensure that the VO reinitializes rendering properly.)
diff --git a/video/out/win_state.h b/video/out/win_state.h
index 6c3988b42b..d495377bac 100644
--- a/video/out/win_state.h
+++ b/video/out/win_state.h
@@ -25,6 +25,8 @@ struct vo_win_geometry {
void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen,
struct vo_win_geometry *out_geo);
+void vo_calc_window_geometry2(struct vo *vo, const struct mp_rect *screen,
+ double dpi_scale, struct vo_win_geometry *out_geo);
void vo_apply_window_geometry(struct vo *vo, const struct vo_win_geometry *geo);
#endif
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index 46e68d6e71..6f3cc41637 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -544,6 +544,7 @@ int vo_x11_init(struct vo *vo)
.screensaver_enabled = true,
.xrandr_event = -1,
.wakeup_pipe = {-1, -1},
+ .dpi_scale = 1,
};
vo->x11 = x11;
@@ -595,6 +596,22 @@ int vo_x11_init(struct vo *vo)
x11->ws_width, x11->ws_height, dispName,
x11->display_is_local ? "local" : "remote");
+ int w_mm = DisplayWidthMM(x11->display, x11->screen);
+ int h_mm = DisplayHeightMM(x11->display, x11->screen);
+ double dpi_x = x11->ws_width * 25.4 / w_mm;
+ double dpi_y = x11->ws_height * 25.4 / h_mm;
+ double base_dpi = 96;
+ if (isfinite(dpi_x) && isfinite(dpi_y)) {
+ int s_x = lrint(MPCLAMP(dpi_x / base_dpi, 0, 10));
+ int s_y = lrint(MPCLAMP(dpi_y / base_dpi, 0, 10));
+ if (s_x == s_y && s_x > 1 && s_x < 10) {
+ x11->dpi_scale = s_x;
+ MP_VERBOSE(x11, "Assuming DPI scale %d for prescaling. This can "
+ "be disabled with --hidpi-window-scale=no.\n",
+ x11->dpi_scale);
+ }
+ }
+
x11->wm_type = vo_wm_detect(vo);
x11->event_fd = ConnectionNumber(x11->display);
@@ -1604,7 +1621,7 @@ void vo_x11_config_vo_window(struct vo *vo)
vo_x11_update_screeninfo(vo);
struct vo_win_geometry geo;
- vo_calc_window_geometry(vo, &x11->screenrc, &geo);
+ vo_calc_window_geometry2(vo, &x11->screenrc, x11->dpi_scale, &geo);
vo_apply_window_geometry(vo, &geo);
struct mp_rect rc = geo.win;
@@ -2044,3 +2061,15 @@ bool vo_x11_screen_is_composited(struct vo *vo)
Atom NET_WM_CM = XInternAtom(x11->display, buf, False);
return XGetSelectionOwner(x11->display, NET_WM_CM) != None;
}
+
+// Return whether the given visual has alpha (when compositing is used).
+bool vo_x11_is_rgba_visual(XVisualInfo *v)
+{
+ // This is a heuristic at best. Note that normal 8 bit Visuals use
+ // a depth of 24, even if the pixels are padded to 32 bit. If the
+ // depth is higher than 24, the remaining bits must be alpha.
+ // Note: vinfo->bits_per_rgb appears to be useless (is always 8).
+ unsigned long mask = v->depth == sizeof(unsigned long) * 8 ?
+ (unsigned long)-1 : (1UL << v->depth) - 1;
+ return mask & ~(v->red_mask | v->green_mask | v->blue_mask);
+}
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index de2a805efd..e69640cc64 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -54,6 +54,7 @@ struct vo_x11_state {
int display_is_local;
int ws_width;
int ws_height;
+ int dpi_scale;
struct mp_rect screenrc;
char *window_title;
@@ -139,4 +140,6 @@ void vo_x11_wait_events(struct vo *vo, int64_t until_time_us);
void vo_x11_silence_xlib(int dir);
+bool vo_x11_is_rgba_visual(XVisualInfo *v);
+
#endif /* MPLAYER_X11_COMMON_H */
diff --git a/video/sws_utils.c b/video/sws_utils.c
index 45918b19d1..33eae6efa9 100644
--- a/video/sws_utils.c
+++ b/video/sws_utils.c
@@ -217,7 +217,7 @@ int mp_sws_reinit(struct mp_sws_context *ctx)
av_opt_set_double(ctx->sws, "param0", ctx->params[0], 0);
av_opt_set_double(ctx->sws, "param1", ctx->params[1], 0);
-#if HAVE_AVCODEC_CHROMA_POS_API
+#if LIBAVCODEC_VERSION_MICRO >= 100
int cr_src = mp_chroma_location_to_av(src->chroma_location);
int cr_dst = mp_chroma_location_to_av(dst->chroma_location);
int cr_xpos, cr_ypos;
diff --git a/video/vaapi.c b/video/vaapi.c
index 604fffa738..5c8ce4c693 100644
--- a/video/vaapi.c
+++ b/video/vaapi.c
@@ -17,6 +17,8 @@
#include <assert.h>
+#include "config.h"
+
#include "vaapi.h"
#include "common/common.h"
#include "common/msg.h"
@@ -25,6 +27,9 @@
#include "img_format.h"
#include "mp_image_pool.h"
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+
bool check_va_status(struct mp_log *log, VAStatus status, const char *msg)
{
if (status != VA_STATUS_SUCCESS) {
@@ -108,23 +113,67 @@ static void va_get_formats(struct mp_vaapi_ctx *ctx)
ctx->image_formats = formats;
}
-struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
- bool probing)
+// VA message callbacks are global and do not have a context parameter, so it's
+// impossible to know from which VADisplay they originate. Try to route them
+// to existing mpv/libmpv instances within this process.
+static pthread_mutex_t va_log_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct mp_vaapi_ctx **va_mpv_clients;
+static int num_va_mpv_clients;
+
+static void va_message_callback(const char *msg, int mp_level)
{
- struct mp_vaapi_ctx *res = NULL;
- struct mp_log *log = mp_log_new(NULL, plog, "/vaapi");
- int major_version, minor_version;
- int status = vaInitialize(display, &major_version, &minor_version);
- if (status != VA_STATUS_SUCCESS && probing)
- goto error;
- if (!check_va_status(log, status, "vaInitialize()"))
- goto error;
+ pthread_mutex_lock(&va_log_mutex);
+
+ if (num_va_mpv_clients) {
+ struct mp_log *dst = va_mpv_clients[num_va_mpv_clients - 1]->log;
+ mp_msg(dst, mp_level, "libva: %s", msg);
+ } else {
+ // We can't get or call the original libva handler (vaSet... return
+ // them, but it might be from some other lib etc.). So just do what
+ // libva happened to do at the time of this writing.
+ if (mp_level <= MSGL_ERR) {
+ fprintf(stderr, "libva error: %s", msg);
+ } else {
+ fprintf(stderr, "libva info: %s", msg);
+ }
+ }
+
+ pthread_mutex_unlock(&va_log_mutex);
+}
+
+static void va_error_callback(const char *msg)
+{
+ va_message_callback(msg, MSGL_ERR);
+}
+
+static void va_info_callback(const char *msg)
+{
+ va_message_callback(msg, MSGL_V);
+}
+
+static void open_lavu_vaapi_device(struct mp_vaapi_ctx *ctx)
+{
+ ctx->av_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
+ if (!ctx->av_device_ref)
+ return;
+
+ AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
+ AVVAAPIDeviceContext *vactx = hwctx->hwctx;
- mp_verbose(log, "VA API version %d.%d\n", major_version, minor_version);
+ vactx->display = ctx->display;
- res = talloc_ptrtype(NULL, res);
+ if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0)
+ av_buffer_unref(&ctx->av_device_ref);
+
+ ctx->hwctx.av_device_ref = ctx->av_device_ref;
+}
+
+struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
+ bool probing)
+{
+ struct mp_vaapi_ctx *res = talloc_ptrtype(NULL, res);
*res = (struct mp_vaapi_ctx) {
- .log = talloc_steal(res, log),
+ .log = mp_log_new(res, plog, "/vaapi"),
.display = display,
.hwctx = {
.type = HWDEC_VAAPI,
@@ -132,18 +181,40 @@ struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog,
.download_image = ctx_download_image,
},
};
- mpthread_mutex_init_recursive(&res->lock);
+
+ pthread_mutex_lock(&va_log_mutex);
+ MP_TARRAY_APPEND(NULL, va_mpv_clients, num_va_mpv_clients, res);
+ pthread_mutex_unlock(&va_log_mutex);
+
+ // Check some random symbol added after message callbacks.
+ // VA_MICRO_VERSION wasn't bumped at the time.
+#ifdef VA_FOURCC_I010
+ vaSetErrorCallback(va_error_callback);
+ vaSetInfoCallback(va_info_callback);
+#endif
+
+ int major_version, minor_version;
+ int status = vaInitialize(display, &major_version, &minor_version);
+ if (status != VA_STATUS_SUCCESS && probing)
+ goto error;
+ if (!check_va_status(res->log, status, "vaInitialize()"))
+ goto error;
+
+ MP_VERBOSE(res, "VA API version %d.%d\n", major_version, minor_version);
va_get_formats(res);
if (!res->image_formats)
goto error;
+
+ // For now, some code will still work even if libavutil fails on old crap
+ // libva drivers (such as the vdpau wraper). So don't error out on failure.
+ open_lavu_vaapi_device(res);
+
return res;
error:
- if (res && res->display)
- vaTerminate(res->display);
- talloc_free(log);
- talloc_free(res);
+ res->display = NULL; // do not vaTerminate this
+ va_destroy(res);
return NULL;
}
@@ -153,7 +224,21 @@ void va_destroy(struct mp_vaapi_ctx *ctx)
if (ctx) {
if (ctx->display)
vaTerminate(ctx->display);
- pthread_mutex_destroy(&ctx->lock);
+
+ if (ctx->destroy_native_ctx)
+ ctx->destroy_native_ctx(ctx->native_ctx);
+
+ pthread_mutex_lock(&va_log_mutex);
+ for (int n = 0; n < num_va_mpv_clients; n++) {
+ if (va_mpv_clients[n] == ctx) {
+ MP_TARRAY_REMOVE_AT(va_mpv_clients, num_va_mpv_clients, n);
+ break;
+ }
+ }
+ if (num_va_mpv_clients == 0)
+ TA_FREEP(&va_mpv_clients); // avoid triggering leak detectors
+ pthread_mutex_unlock(&va_log_mutex);
+
talloc_free(ctx);
}
}
@@ -209,22 +294,27 @@ int va_surface_rt_format(struct mp_image *mpi)
// padded surfaces for example.)
void va_surface_get_uncropped_size(struct mp_image *mpi, int *out_w, int *out_h)
{
- struct va_surface *s = va_surface_in_mp_image(mpi);
- *out_w = s ? s->w : 0;
- *out_h = s ? s->h : 0;
+ if (mpi->hwctx) {
+ AVHWFramesContext *fctx = (void *)mpi->hwctx->data;
+ *out_w = fctx->width;
+ *out_h = fctx->height;
+ } else {
+ struct va_surface *s = va_surface_in_mp_image(mpi);
+ *out_w = s ? s->w : 0;
+ *out_h = s ? s->h : 0;
+ }
}
static void release_va_surface(void *arg)
{
struct va_surface *surface = arg;
- va_lock(surface->ctx);
if (surface->id != VA_INVALID_ID) {
if (surface->image.image_id != VA_INVALID_ID)
vaDestroyImage(surface->display, surface->image.image_id);
vaDestroySurfaces(surface->display, &surface->id, 1);
}
- va_unlock(surface->ctx);
+
talloc_free(surface);
}
@@ -233,9 +323,7 @@ static struct mp_image *alloc_surface(struct mp_vaapi_ctx *ctx, int rt_format,
{
VASurfaceID id = VA_INVALID_ID;
VAStatus status;
- va_lock(ctx);
status = vaCreateSurfaces(ctx->display, rt_format, w, h, &id, 1, NULL, 0);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaCreateSurfaces()"))
return NULL;
@@ -270,11 +358,8 @@ static void va_surface_image_destroy(struct va_surface *surface)
surface->is_derived = false;
}
-static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
+static int va_surface_image_alloc(struct va_surface *p, VAImageFormat *format)
{
- struct va_surface *p = va_surface_in_mp_image(img);
- if (!format || !p)
- return -1;
VADisplay *display = p->display;
if (p->image.image_id != VA_INVALID_ID &&
@@ -282,7 +367,6 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
return 0;
int r = 0;
- va_lock(p->ctx);
va_surface_image_destroy(p);
@@ -308,7 +392,6 @@ static int va_surface_image_alloc(struct mp_image *img, VAImageFormat *format)
}
}
- va_unlock(p->ctx);
return r;
}
@@ -327,7 +410,7 @@ int va_surface_alloc_imgfmt(struct mp_image *img, int imgfmt)
VAImageFormat *format = va_image_format_from_imgfmt(p->ctx, imgfmt);
if (!format)
return -1;
- if (va_surface_image_alloc(img, format) < 0)
+ if (va_surface_image_alloc(p, format) < 0)
return -1;
return 0;
}
@@ -338,9 +421,7 @@ bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi
if (imgfmt == IMGFMT_NONE)
return false;
void *data = NULL;
- va_lock(ctx);
const VAStatus status = vaMapBuffer(ctx->display, image->buf, &data);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaMapBuffer()"))
return false;
@@ -363,9 +444,7 @@ bool va_image_map(struct mp_vaapi_ctx *ctx, VAImage *image, struct mp_image *mpi
bool va_image_unmap(struct mp_vaapi_ctx *ctx, VAImage *image)
{
- va_lock(ctx);
const VAStatus status = vaUnmapBuffer(ctx->display, image->buf);
- va_unlock(ctx);
return CHECK_VA_STATUS(ctx, "vaUnmapBuffer()");
}
@@ -389,12 +468,10 @@ int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
va_image_unmap(p->ctx, &p->image);
if (!p->is_derived) {
- va_lock(p->ctx);
VAStatus status = vaPutImage(p->display, p->id,
p->image.image_id,
0, 0, sw_src->w, sw_src->h,
0, 0, sw_src->w, sw_src->h);
- va_unlock(p->ctx);
if (!CHECK_VA_STATUS(p->ctx, "vaPutImage()"))
return -1;
}
@@ -404,14 +481,10 @@ int va_surface_upload(struct mp_image *va_dst, struct mp_image *sw_src)
return 0;
}
-static struct mp_image *try_download(struct mp_image *src,
+static struct mp_image *try_download(struct va_surface *p, struct mp_image *src,
struct mp_image_pool *pool)
{
VAStatus status;
- struct va_surface *p = va_surface_in_mp_image(src);
- if (!p)
- return NULL;
-
VAImage *image = &p->image;
if (image->image_id == VA_INVALID_ID ||
@@ -419,10 +492,8 @@ static struct mp_image *try_download(struct mp_image *src,
return NULL;
if (!p->is_derived) {
- va_lock(p->ctx);
status = vaGetImage(p->display, p->id, 0, 0,
p->w, p->h, image->image_id);
- va_unlock(p->ctx);
if (status != VA_STATUS_SUCCESS)
return NULL;
}
@@ -434,9 +505,7 @@ static struct mp_image *try_download(struct mp_image *src,
mp_image_set_size(&tmp, src->w, src->h); // copy only visible part
dst = mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h);
if (dst) {
- va_lock(p->ctx);
mp_check_gpu_memcpy(p->ctx->log, &p->ctx->gpu_memcpy_message);
- va_unlock(p->ctx);
mp_image_copy_gpu(dst, &tmp);
mp_image_copy_attributes(dst, src);
@@ -453,19 +522,23 @@ static struct mp_image *try_download(struct mp_image *src,
struct mp_image *va_surface_download(struct mp_image *src,
struct mp_image_pool *pool)
{
- struct va_surface *p = va_surface_in_mp_image(src);
- if (!p)
+ if (!src || src->imgfmt != IMGFMT_VAAPI)
return NULL;
+ struct va_surface *p = va_surface_in_mp_image(src);
+ if (!p) {
+ // We might still be able to get to the cheese if this is a surface
+ // produced by libavutil's vaapi glue code.
+ return mp_image_hw_download(src, pool);
+ }
+ struct mp_image *mpi = NULL;
struct mp_vaapi_ctx *ctx = p->ctx;
- va_lock(ctx);
VAStatus status = vaSyncSurface(p->display, p->id);
- va_unlock(ctx);
if (!CHECK_VA_STATUS(ctx, "vaSyncSurface()"))
- return NULL;
+ goto done;
- struct mp_image *mpi = try_download(src, pool);
+ mpi = try_download(p, src, pool);
if (mpi)
- return mpi;
+ goto done;
// We have no clue which format will work, so try them all.
// Make sure to start with the most preferred format (nv12), to avoid
@@ -474,16 +547,19 @@ struct mp_image *va_surface_download(struct mp_image *src,
VAImageFormat *format =
va_image_format_from_imgfmt(ctx, va_to_imgfmt[n].mp);
if (format) {
- if (va_surface_image_alloc(src, format) < 0)
+ if (va_surface_image_alloc(p, format) < 0)
continue;
- mpi = try_download(src, pool);
+ mpi = try_download(p, src, pool);
if (mpi)
- return mpi;
+ goto done;
}
}
- MP_ERR(ctx, "failed to get surface data.\n");
- return NULL;
+done:
+
+ if (!mpi)
+ MP_ERR(ctx, "failed to get surface data.\n");
+ return mpi;
}
// Set the hw_subfmt from the surface's real format. Because of this bug:
@@ -503,8 +579,6 @@ void va_surface_init_subformat(struct mp_image *mpi)
VAImage va_image = { .image_id = VA_INVALID_ID };
- va_lock(p->ctx);
-
status = vaDeriveImage(p->display, va_surface_id(mpi), &va_image);
if (status != VA_STATUS_SUCCESS)
goto err;
@@ -514,8 +588,7 @@ void va_surface_init_subformat(struct mp_image *mpi)
status = vaDestroyImage(p->display, va_image.image_id);
CHECK_VA_STATUS(p->ctx, "vaDestroyImage()");
-err:
- va_unlock(p->ctx);
+err: ;
}
struct pool_alloc_ctx {
@@ -548,8 +621,118 @@ void va_pool_set_allocator(struct mp_image_pool *pool, struct mp_vaapi_ctx *ctx,
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx)
{
- va_lock(ctx);
const char *s = vaQueryVendorString(ctx->display);
- va_unlock(ctx);
return s && strstr(s, "VDPAU backend");
}
+
+struct va_native_display {
+ void (*create)(VADisplay **out_display, void **out_native_ctx);
+ void (*destroy)(void *native_ctx);
+};
+
+#if HAVE_VAAPI_X11
+#include <X11/Xlib.h>
+#include <va/va_x11.h>
+
+static void x11_destroy(void *native_ctx)
+{
+ XCloseDisplay(native_ctx);
+}
+
+static void x11_create(VADisplay **out_display, void **out_native_ctx)
+{
+ void *native_display = XOpenDisplay(NULL);
+ if (!native_display)
+ return;
+ *out_display = vaGetDisplay(native_display);
+ if (*out_display) {
+ *out_native_ctx = native_display;
+ } else {
+ XCloseDisplay(native_display);
+ }
+}
+
+static const struct va_native_display disp_x11 = {
+ .create = x11_create,
+ .destroy = x11_destroy,
+};
+#endif
+
+#if HAVE_VAAPI_DRM
+#include <unistd.h>
+#include <fcntl.h>
+#include <va/va_drm.h>
+
+struct va_native_display_drm {
+ int drm_fd;
+};
+
+static void drm_destroy(void *native_ctx)
+{
+ struct va_native_display_drm *ctx = native_ctx;
+ close(ctx->drm_fd);
+ talloc_free(ctx);
+}
+
+static void drm_create(VADisplay **out_display, void **out_native_ctx)
+{
+ static const char *drm_device_paths[] = {
+ "/dev/dri/renderD128",
+ "/dev/dri/card0",
+ NULL
+ };
+
+ for (int i = 0; drm_device_paths[i]; i++) {
+ int drm_fd = open(drm_device_paths[i], O_RDWR);
+ if (drm_fd < 0)
+ continue;
+
+ struct va_native_display_drm *ctx = talloc_ptrtype(NULL, ctx);
+ ctx->drm_fd = drm_fd;
+ *out_display = vaGetDisplayDRM(drm_fd);
+ if (out_display) {
+ *out_native_ctx = ctx;
+ return;
+ }
+
+ close(drm_fd);
+ talloc_free(ctx);
+ }
+}
+
+static const struct va_native_display disp_drm = {
+ .create = drm_create,
+ .destroy = drm_destroy,
+};
+#endif
+
+static const struct va_native_display *const native_displays[] = {
+#if HAVE_VAAPI_DRM
+ &disp_drm,
+#endif
+#if HAVE_VAAPI_X11
+ &disp_x11,
+#endif
+ NULL
+};
+
+struct mp_vaapi_ctx *va_create_standalone(struct mp_log *plog, bool probing)
+{
+ for (int n = 0; native_displays[n]; n++) {
+ VADisplay *display = NULL;
+ void *native_ctx = NULL;
+ native_displays[n]->create(&display, &native_ctx);
+ if (display) {
+ struct mp_vaapi_ctx *ctx = va_initialize(display, plog, probing);
+ if (!ctx) {
+ vaTerminate(display);
+ native_displays[n]->destroy(native_ctx);
+ return NULL;
+ }
+ ctx->native_ctx = native_ctx;
+ ctx->destroy_native_ctx = native_displays[n]->destroy;
+ return ctx;
+ }
+ }
+ return NULL;
+}
diff --git a/video/vaapi.h b/video/vaapi.h
index 3f0d1dca37..de7d6d98d8 100644
--- a/video/vaapi.h
+++ b/video/vaapi.h
@@ -33,18 +33,18 @@ struct mp_vaapi_ctx {
struct mp_hwdec_ctx hwctx;
struct mp_log *log;
VADisplay display;
+ struct AVBufferRef *av_device_ref; // AVVAAPIDeviceContext*
struct va_image_formats *image_formats;
bool gpu_memcpy_message;
- pthread_mutex_t lock;
+ // Internal, for va_create_standalone()
+ void *native_ctx;
+ void (*destroy_native_ctx)(void *native_ctx);
};
bool check_va_status(struct mp_log *log, VAStatus status, const char *msg);
#define CHECK_VA_STATUS(ctx, msg) check_va_status((ctx)->log, status, msg)
-#define va_lock(ctx) pthread_mutex_lock(&(ctx)->lock)
-#define va_unlock(ctx) pthread_mutex_unlock(&(ctx)->lock)
-
int va_get_colorspace_flag(enum mp_csp csp);
struct mp_vaapi_ctx * va_initialize(VADisplay *display, struct mp_log *plog, bool probing);
@@ -73,4 +73,6 @@ void va_surface_init_subformat(struct mp_image *mpi);
bool va_guess_if_emulated(struct mp_vaapi_ctx *ctx);
+struct mp_vaapi_ctx *va_create_standalone(struct mp_log *plog, bool probing);
+
#endif
diff --git a/video/vdpau.c b/video/vdpau.c
index dffb02e22f..f4c85a0bab 100644
--- a/video/vdpau.c
+++ b/video/vdpau.c
@@ -17,6 +17,9 @@
#include <assert.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vdpau.h>
+
#include "vdpau.h"
#include "osdep/threads.h"
@@ -32,47 +35,10 @@ static struct mp_image *download_image_yuv(struct mp_hwdec_ctx *hwctx,
struct mp_image *mpi,
struct mp_image_pool *swpool)
{
- struct mp_vdpau_ctx *ctx = hwctx->ctx;
- struct vdp_functions *vdp = &ctx->vdp;
- VdpStatus vdp_st;
-
if (mpi->imgfmt != IMGFMT_VDPAU || mp_vdpau_mixed_frame_get(mpi))
return NULL;
- VdpVideoSurface surface = (uintptr_t)mpi->planes[3];
-
- VdpChromaType s_chroma_type;
- uint32_t s_w, s_h;
- vdp_st = vdp->video_surface_get_parameters(surface, &s_chroma_type, &s_w, &s_h);
- CHECK_VDP_ERROR_NORETURN(ctx,
- "Error when calling vdp_video_surface_get_parameters");
- if (vdp_st != VDP_STATUS_OK)
- return NULL;
-
- // Don't bother supporting other types for now.
- if (s_chroma_type != VDP_CHROMA_TYPE_420)
- return NULL;
-
- // The allocation needs to be uncropped, because get_bits writes to it.
- struct mp_image *out = mp_image_pool_get(swpool, IMGFMT_NV12, s_w, s_h);
- if (!out)
- return NULL;
-
- mp_image_set_size(out, mpi->w, mpi->h);
- mp_image_copy_attributes(out, mpi);
-
- vdp_st = vdp->video_surface_get_bits_y_cb_cr(surface,
- VDP_YCBCR_FORMAT_NV12,
- (void * const *)out->planes,
- out->stride);
- CHECK_VDP_ERROR_NORETURN(ctx,
- "Error when calling vdp_output_surface_get_bits_y_cb_cr");
- if (vdp_st != VDP_STATUS_OK) {
- talloc_free(out);
- return NULL;
- }
-
- return out;
+ return mp_image_hw_download(mpi, swpool);
}
static struct mp_image *download_image(struct mp_hwdec_ctx *hwctx,
@@ -439,6 +405,26 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx,
return mp_vdpau_get_surface(ctx, chroma, 0, false, w, h);
}
+static bool open_lavu_vdpau_device(struct mp_vdpau_ctx *ctx)
+{
+ ctx->av_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VDPAU);
+ if (!ctx->av_device_ref)
+ return false;
+
+ AVHWDeviceContext *hwctx = (void *)ctx->av_device_ref->data;
+ AVVDPAUDeviceContext *vdctx = hwctx->hwctx;
+
+ vdctx->device = ctx->vdp_device;
+ vdctx->get_proc_address = ctx->get_proc_address;
+
+ if (av_hwdevice_ctx_init(ctx->av_device_ref) < 0)
+ av_buffer_unref(&ctx->av_device_ref);
+
+ ctx->hwctx.av_device_ref = ctx->av_device_ref;
+
+ return !!ctx->av_device_ref;
+}
+
struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11,
bool probing)
{
@@ -463,6 +449,10 @@ struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11
mp_vdpau_destroy(ctx);
return NULL;
}
+ if (!open_lavu_vdpau_device(ctx)) {
+ mp_vdpau_destroy(ctx);
+ return NULL;
+ }
return ctx;
}
diff --git a/video/vdpau.h b/video/vdpau.h
index 389e1c7e9a..bffe901373 100644
--- a/video/vdpau.h
+++ b/video/vdpau.h
@@ -48,6 +48,7 @@ struct mp_vdpau_ctx {
Display *x11;
struct mp_hwdec_ctx hwctx;
+ struct AVBufferRef *av_device_ref;
// These are mostly immutable, except on preemption. We don't really care
// to synchronize the preemption case fully correctly, because it's an
diff --git a/waftools/checks/custom.py b/waftools/checks/custom.py
index 50a16ce26a..8ed06e5b24 100644
--- a/waftools/checks/custom.py
+++ b/waftools/checks/custom.py
@@ -4,7 +4,7 @@ from waflib import Utils
import os
__all__ = ["check_pthreads", "check_iconv", "check_lua", "check_oss_4front",
- "check_cocoa", "check_openal"]
+ "check_cocoa", "check_openal", "check_rpi"]
pthreads_program = load_fragment('pthreads.c')
@@ -50,6 +50,8 @@ def check_iconv(ctx, dependency_identifier):
if ctx.env.DEST_OS == 'openbsd' or ctx.env.DEST_OS == 'freebsd':
args['cflags'] = '-I/usr/local/include'
args['linkflags'] = '-L/usr/local/lib'
+ elif ctx.env.DEST_OS == 'win32':
+ args['linkflags'] = " ".join(['-L' + x for x in ctx.env.LIBRARY_PATH])
checkfn = check_cc(**args)
return check_libs(libs, checkfn)(ctx, dependency_identifier)
@@ -127,3 +129,29 @@ def check_openal(ctx, dependency_identifier):
if fn(ctx, dependency_identifier):
return True
return False
+
+def check_rpi(ctx, dependency_identifier):
+ # We need MMAL/bcm_host/dispmanx APIs.
+ # Upstream keeps pkgconfig files in '/opt/vc/lib/pkgconfig'.
+ # See https://github.com/raspberrypi/userland/issues/245
+ # PKG_CONFIG_SYSROOT_DIR helps with cross compilation.
+ prev_pkg_path = os.getenv('PKG_CONFIG_PATH', '')
+ os.environ['PKG_CONFIG_PATH'] = os.pathsep.join(
+ filter(None, [os.path.join(os.getenv('PKG_CONFIG_SYSROOT_DIR', '/'),
+ 'opt/vc/lib/pkgconfig'),
+ prev_pkg_path]))
+
+ checks = [
+ check_pkg_config('bcm_host', uselib_store='bcm_host'),
+ check_pkg_config('egl'),
+ check_pkg_config('glesv2'),
+ check_cc(lib=['mmal_core', 'mmal_util', 'mmal_vc_client'], use=['bcm_host']),
+ # We still need all OpenGL symbols, because the vo_opengl code is
+ # generic and supports anything from GLES2/OpenGL 2.1 to OpenGL 4 core.
+ check_statement('GL/gl.h', '(void)GL_RGB32F'), # arbitrary OpenGL 3.0 symbol
+ check_statement('GL/gl.h', '(void)GL_LUMINANCE16') # arbitrary OpenGL legacy-only symbol
+ ]
+
+ ret = all((fn(ctx, dependency_identifier) for fn in checks))
+ os.environ['PKG_CONFIG_PATH'] = prev_pkg_path
+ return ret
diff --git a/waftools/checks/generic.py b/waftools/checks/generic.py
index 8da8467798..153cf9463d 100644
--- a/waftools/checks/generic.py
+++ b/waftools/checks/generic.py
@@ -4,7 +4,8 @@ from waflib.ConfigSet import ConfigSet
from waflib import Utils
__all__ = [
- "check_pkg_config", "check_pkg_config_cflags", "check_cc", "check_statement", "check_libs",
+ "check_pkg_config", "check_pkg_config_mixed", "check_pkg_config_mixed_all",
+ "check_pkg_config_cflags", "check_cc", "check_statement", "check_libs",
"check_headers", "compose_checks", "check_true", "any_version",
"load_fragment", "check_stub", "check_ctx_vars", "check_program"]
@@ -69,24 +70,34 @@ def check_cc(**kw_ext):
return fn
def check_pkg_config(*args, **kw_ext):
- return _check_pkg_config(["--libs", "--cflags"], *args, **kw_ext)
+ return _check_pkg_config([], ["--libs", "--cflags"], *args, **kw_ext)
+
+def check_pkg_config_mixed(_dyn_libs, *args, **kw_ext):
+ return _check_pkg_config([_dyn_libs], ["--libs", "--cflags"], *args, **kw_ext)
+
+def check_pkg_config_mixed_all(*all_args, **kw_ext):
+ args = [all_args[i] for i in [n for n in range(0, len(all_args)) if n % 3]]
+ return _check_pkg_config(all_args[::3], ["--libs", "--cflags"], *args, **kw_ext)
def check_pkg_config_cflags(*args, **kw_ext):
- return _check_pkg_config(["--cflags"], *args, **kw_ext)
+ return _check_pkg_config([], ["--cflags"], *args, **kw_ext)
-def _check_pkg_config(_pkgc_args, *args, **kw_ext):
+def _check_pkg_config(_dyn_libs, _pkgc_args, *args, **kw_ext):
def fn(ctx, dependency_identifier, **kw):
argsl = list(args)
packages = args[::2]
verchecks = args[1::2]
sargs = []
pkgc_args = _pkgc_args
+ dyn_libs = {}
for i in range(0, len(packages)):
if i < len(verchecks):
sargs.append(packages[i] + ' ' + verchecks[i])
else:
sargs.append(packages[i])
- if ctx.dependency_satisfied('static-build'):
+ if _dyn_libs and _dyn_libs[i]:
+ dyn_libs[packages[i]] = _dyn_libs[i]
+ if ctx.dependency_satisfied('static-build') and not dyn_libs:
pkgc_args += ["--static"]
defaults = {
@@ -108,6 +119,8 @@ def _check_pkg_config(_pkgc_args, *args, **kw_ext):
defkey = inflector.define_key(dependency_identifier)
if result:
ctx.define(defkey, 1)
+ for x in dyn_libs.keys():
+ ctx.env['LIB_'+x] += dyn_libs[x]
else:
ctx.add_optional_message(dependency_identifier,
"'{0}' not found".format(" ".join(sargs)))
diff --git a/waftools/generators/sources.py b/waftools/generators/sources.py
index b6af693e65..96224cf839 100644
--- a/waftools/generators/sources.py
+++ b/waftools/generators/sources.py
@@ -1,14 +1,10 @@
from waflib.Build import BuildContext
+from waflib import TaskGen
+from io import StringIO
+from TOOLS.matroska import generate_C_header, generate_C_definitions
+from TOOLS.file2string import file2string
import os
-def __file2string_cmd__(ctx):
- return '"${{BIN_PYTHON}}" "{0}/TOOLS/file2string.py" "${{SRC}}" > "${{TGT}}"' \
- .format(ctx.srcnode.abspath())
-
-def __matroska_cmd__(ctx, argument):
- return '"${{BIN_PYTHON}}" "{0}/TOOLS/matroska.py" "{1}" "${{SRC}}" > "${{TGT}}"' \
- .format(ctx.srcnode.abspath(), argument)
-
def __zshcomp_cmd__(ctx, argument):
return '"${{BIN_PERL}}" "{0}/TOOLS/zsh.pl" "{1}" > "${{TGT}}"' \
.format(ctx.srcnode.abspath(), argument)
@@ -21,20 +17,31 @@ def __file2string__(ctx, **kwargs):
**kwargs
)
-def __matroska_header__(ctx, **kwargs):
- ctx(
- rule = __matroska_cmd__(ctx, '--generate-header'),
- before = ("c",),
- name = os.path.basename(kwargs['target']),
- **kwargs
- )
-
-def __matroska_definitions__(ctx, **kwargs):
- ctx(
- rule = __matroska_cmd__(ctx, '--generate-definitions'),
- before = ("c",),
- **kwargs
- )
+def execf(self, fn):
+ setattr(self, 'before', ['c'])
+ setattr(self, 'rule', ' ') # waf doesn't print the task with no rule
+ target = getattr(self, 'target', None)
+ out = self.path.find_or_declare(target)
+ tmp = StringIO()
+ fn(tmp)
+ out.write(tmp.getvalue())
+ tmp.close()
+
+@TaskGen.feature('file2string')
+def f2s(self):
+ def fn(out):
+ source = getattr(self, 'source', None)
+ src = self.path.find_resource(source)
+ file2string(source, iter(src.read().splitlines(True)), out)
+ execf(self, fn)
+
+@TaskGen.feature('ebml_header')
+def ebml_header(self):
+ execf(self, generate_C_header)
+
+@TaskGen.feature('ebml_definitions')
+def ebml_definitions(self):
+ execf(self, generate_C_definitions)
def __zshcomp__(ctx, **kwargs):
ctx(
@@ -44,7 +51,5 @@ def __zshcomp__(ctx, **kwargs):
**kwargs
)
-BuildContext.file2string = __file2string__
-BuildContext.matroska_header = __matroska_header__
-BuildContext.matroska_definitions = __matroska_definitions__
-BuildContext.zshcomp = __zshcomp__
+BuildContext.file2string = __file2string__
+BuildContext.zshcomp = __zshcomp__
diff --git a/wscript b/wscript
index 94db7e296f..e660ff7003 100644
--- a/wscript
+++ b/wscript
@@ -68,6 +68,12 @@ build_options = [
'desc': 'dynamic loader',
'func': check_libs(['dl'], check_statement('dlfcn.h', 'dlopen("", 0)'))
}, {
+ 'name': '--cplugins',
+ 'desc': 'C plugins',
+ 'deps': [ 'libdl' ],
+ 'default': 'disable',
+ 'func': check_cc(linkflags=['-Wl,-export-dynamic']),
+ }, {
'name': 'dlopen',
'desc': 'dlopen',
'deps_any': [ 'libdl', 'os-win32', 'os-cygwin' ],
@@ -173,19 +179,11 @@ main_dependencies = [
'test = __atomic_add_fetch(&test, 1, __ATOMIC_SEQ_CST)')),
'deps_neg': [ 'stdatomic' ],
}, {
- 'name': 'sync-builtins',
- 'desc': 'compiler support for __sync built-ins',
- 'func': check_statement('stdint.h',
- 'int64_t test = 0;'
- '__typeof__(test) x = ({int a = 1; a;});'
- 'test = __sync_add_and_fetch(&test, 1)'),
- 'deps_neg': [ 'stdatomic', 'atomic-builtins' ],
- }, {
'name': 'atomics',
'desc': 'stdatomic.h support or emulation',
'func': check_true,
'req': True,
- 'deps_any': ['stdatomic', 'atomic-builtins', 'sync-builtins', 'gnuc'],
+ 'deps_any': ['stdatomic', 'atomic-builtins', 'gnuc'],
}, {
'name': 'c11-tls',
'desc': 'C11 TLS support',
@@ -388,86 +386,76 @@ iconv support use --disable-iconv.",
}
]
-# Libav 12:
-# libavutil 55.20.0
-# libavcodec 57.25.0
-# libavformat 57.7.2
-# libswscale 4.0.0
-# libavfilter 6.7.0
-# libavresample 3.0.0
-# FFmpeg 3.2.2:
-# libavutil 55.34.100
-# libavcodec 57.64.101
-# libavformat 57.56.100
-# libswscale 4.2.100
-# libavfilter 6.65.100
-# libswresample 2.3.100
-
+ffmpeg_version = "3.2.2"
+ffmpeg_pkg_config_checks = [
+ 'libavutil', '>= 55.34.100',
+ 'libavcodec', '>= 57.64.100',
+ 'libavformat', '>= 57.56.100',
+ 'libswscale', '>= 4.2.100',
+ 'libavfilter', '>= 6.65.100',
+ 'libswresample', '>= 2.3.100',
+]
+libav_version = "12"
libav_pkg_config_checks = [
- 'libavutil', '>= 55.20.0',
- 'libavcodec', '>= 57.25.0',
- 'libavformat', '>= 57.07.0',
- 'libswscale', '>= 4.0.0'
+ 'libavutil', '>= 55.20.0',
+ 'libavcodec', '>= 57.25.0',
+ 'libavformat', '>= 57.7.0',
+ 'libswscale', '>= 4.0.0',
+ 'libavfilter', '>= 6.7.0',
+ 'libavresample', '>= 3.0.0',
]
-libav_versions_string = "FFmpeg 3.2.2 or Libav 12"
+
+libav_versions_string = "FFmpeg %s or Libav %s" % (ffmpeg_version, libav_version)
+
+def check_ffmpeg_or_libav_versions():
+ def fn(ctx, dependency_identifier, **kw):
+ versions = ffmpeg_pkg_config_checks
+ if ctx.dependency_satisfied('is_libav'):
+ versions = libav_pkg_config_checks
+ return check_pkg_config(*versions)(ctx, dependency_identifier, **kw)
+ return fn
libav_dependencies = [
{
+ 'name': 'libavcodec',
+ 'desc': 'FFmpeg/Libav present',
+ 'func': check_pkg_config('libavcodec'),
+ 'req': True,
+ 'fmsg': "FFmpeg/Libav development files not found.",
+ }, {
+ 'name': 'is_ffmpeg',
+ 'desc': 'libav* is FFmpeg',
+ # FFmpeg <=> LIBAVUTIL_VERSION_MICRO>=100
+ 'func': check_statement('libavcodec/version.h',
+ 'int x[LIBAVCODEC_VERSION_MICRO >= 100 ? 1 : -1]',
+ use='libavcodec')
+ }, {
+ # This check should always result in the opposite of is_ffmpeg.
+ # Run it to make sure is_ffmpeg didn't fail for some other reason than
+ # the actual version check.
+ 'name': 'is_libav',
+ 'desc': 'libav* is Libav',
+ # FFmpeg <=> LIBAVUTIL_VERSION_MICRO>=100
+ 'func': check_statement('libavcodec/version.h',
+ 'int x[LIBAVCODEC_VERSION_MICRO >= 100 ? -1 : 1]',
+ use='libavcodec')
+ }, {
'name': 'libav',
- 'desc': 'libav/ffmpeg',
- 'func': check_pkg_config(*libav_pkg_config_checks),
+ 'desc': 'Libav/FFmpeg library versions',
+ 'deps_any': [ 'is_ffmpeg', 'is_libav' ],
+ 'func': check_ffmpeg_or_libav_versions(),
'req': True,
'fmsg': "Unable to find development files for some of the required \
FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_string)
}, {
- 'name': '--libswresample',
- 'desc': 'libswresample',
- 'func': check_pkg_config('libswresample', '>= 2.3.100'),
- }, {
- 'name': '--libavresample',
- 'desc': 'libavresample',
- 'func': check_pkg_config('libavresample', '>= 3.0.0'),
- 'deps_neg': ['libswresample'],
- }, {
- 'name': 'resampler',
- 'desc': 'usable resampler found',
- 'deps_any': [ 'libavresample', 'libswresample' ],
- 'func': check_true,
- 'req': True,
- 'fmsg': 'No resampler found. Install libavresample or libswresample (FFmpeg).'
- }, {
- 'name': 'libavfilter',
- 'desc': 'libavfilter',
- 'func': check_pkg_config('libavfilter', '>= 6.7.0'),
- 'req': True,
- 'fmsg': 'libavfilter is a required dependency.',
- }, {
'name': '--libavdevice',
'desc': 'libavdevice',
'func': check_pkg_config('libavdevice', '>= 57.0.0'),
}, {
- 'name': 'avcodec-chroma-pos-api',
- 'desc': 'libavcodec avcodec_enum_to_chroma_pos API',
- 'func': check_statement('libavcodec/avcodec.h', """int x, y;
- avcodec_enum_to_chroma_pos(&x, &y, AVCHROMA_LOC_UNSPECIFIED)""",
- use='libav')
- }, {
- 'name': 'avframe-metadata',
- 'desc': 'libavutil AVFrame metadata',
- 'func': check_statement('libavutil/frame.h',
- 'av_frame_get_metadata(NULL)',
- use='libav')
- }, {
- 'name': 'avframe-skip-samples',
- 'desc': 'libavutil AVFrame skip samples metadata',
- 'func': check_statement('libavutil/frame.h',
- 'enum AVFrameSideDataType type = AV_FRAME_DATA_SKIP_SAMPLES',
- use='libav')
- }, {
- 'name': 'avutil-mastering-metadata',
- 'desc': 'libavutil mastering display metadata struct',
- 'func': check_statement('libavutil/frame.h',
- 'AV_FRAME_DATA_MASTERING_DISPLAY_METADATA',
+ 'name': 'avutil-imgcpy-uc',
+ 'desc': 'libavutil GPU memcpy for hardware decoding',
+ 'func': check_statement('libavutil/imgutils.h',
+ 'av_image_copy_uc_from(0,0,0,0,0,0,0)',
use='libav'),
},
]
@@ -750,27 +738,9 @@ video_output_features = [
'desc': 'Android support',
'func': check_statement('android/api-level.h', '(void)__ANDROID__'), # arbitrary android-specific header
}, {
- # We need MMAL/bcm_host/dispmanx APIs. Also, most RPI distros require
- # every project to hardcode the paths to the include directories. Also,
- # these headers are so broken that they spam tons of warnings by merely
- # including them (compensate with -isystem and -fgnu89-inline).
'name': '--rpi',
'desc': 'Raspberry Pi support',
- 'func': compose_checks(
- check_cc(cflags="-isystem/opt/vc/include/ "+
- "-isystem/opt/vc/include/interface/vcos/pthreads " +
- "-isystem/opt/vc/include/interface/vmcs_host/linux " +
- "-fgnu89-inline",
- linkflags="-L/opt/vc/lib",
- header_name="bcm_host.h",
- lib=['mmal_core', 'mmal_util', 'mmal_vc_client', 'bcm_host']),
- # We still need all OpenGL symbols, because the vo_opengl code is
- # generic and supports anything from GLES2/OpenGL 2.1 to OpenGL 4 core.
- check_cc(lib="EGL"),
- check_cc(lib="GLESv2"),
- check_statement('GL/gl.h', '(void)GL_RGB32F'), # arbitrary OpenGL 3.0 symbol
- check_statement('GL/gl.h', '(void)GL_LUMINANCE16') # arbitrary OpenGL legacy-only symbol
- ),
+ 'func': check_rpi,
}, {
'name': '--standard-gl',
'desc': 'Desktop standard OpenGL support',
@@ -822,7 +792,8 @@ video_output_features = [
}, {
'name': 'egl-helpers',
'desc': 'EGL helper functions',
- 'deps_any': [ 'egl-x11', 'mali-fbdev', 'rpi', 'gl-wayland', 'egl-drm' ],
+ 'deps_any': [ 'egl-x11', 'mali-fbdev', 'rpi', 'gl-wayland', 'egl-drm',
+ 'egl-angle' ],
'func': check_true
}
]
@@ -834,6 +805,23 @@ hwaccel_features = [
'deps': [ 'vaapi' ],
'func': check_headers('libavcodec/vaapi.h', use='libav'),
}, {
+ 'name': '--vaapi-hwaccel-new',
+ 'desc': 'libavcodec VAAPI hwaccel (new)',
+ 'deps': [ 'vaapi-hwaccel' ],
+ 'func': check_statement('libavcodec/version.h',
+ 'int x[(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 26, 0) && '
+ ' LIBAVCODEC_VERSION_MICRO < 100) ||'
+ ' (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 74, 100) && '
+ ' LIBAVCODEC_VERSION_MICRO >= 100)'
+ ' ? 1 : -1]',
+ use='libav'),
+ }, {
+ 'name': '--vaapi-hwaccel-old',
+ 'desc': 'libavcodec VAAPI hwaccel (old)',
+ 'deps': [ 'vaapi-hwaccel' ],
+ 'deps_neg': [ 'vaapi-hwaccel-new' ],
+ 'func': check_true,
+ }, {
'name': '--videotoolbox-hwaccel',
'desc': 'libavcodec videotoolbox hwaccel',
'func': compose_checks(
@@ -863,12 +851,13 @@ hwaccel_features = [
}, {
'name': '--cuda-hwaccel',
'desc': 'CUDA hwaccel',
+ 'deps': [ 'gl' ],
'func': check_cc(fragment=load_fragment('cuda.c'),
use='libav'),
}, {
'name': 'sse4-intrinsics',
'desc': 'GCC SSE4 intrinsics for GPU memcpy',
- 'deps_any': [ 'd3d-hwaccel', 'vaapi-hwaccel' ],
+ 'deps_any': [ 'd3d-hwaccel', 'vaapi-hwaccel-old' ],
'func': check_cc(fragment=load_fragment('sse.c')),
}
]
@@ -996,13 +985,14 @@ def configure(ctx):
ctx.find_program(cc, var='CC')
ctx.find_program(pkg_config, var='PKG_CONFIG')
ctx.find_program(ar, var='AR')
- ctx.find_program('python', var='BIN_PYTHON')
ctx.find_program('rst2html', var='RST2HTML', mandatory=False)
ctx.find_program('rst2man', var='RST2MAN', mandatory=False)
ctx.find_program('rst2pdf', var='RST2PDF', mandatory=False)
ctx.find_program(windres, var='WINDRES', mandatory=False)
ctx.find_program('perl', var='BIN_PERL', mandatory=False)
+ ctx.add_os_flags('LIBRARY_PATH')
+
ctx.load('compiler_c')
ctx.load('waf_customizations')
ctx.load('dependencies')
@@ -1046,6 +1036,13 @@ def configure(ctx):
if ctx.dependency_satisfied('clang-database'):
ctx.load('clang_compilation_database')
+ if ctx.dependency_satisfied('cplugins'):
+ # We need to export the libmpv symbols, since the mpv binary itself is
+ # not linked against libmpv. The C plugin needs to be able to pick
+ # up the libmpv symbols from the binary. We still restrict the set
+ # of exported symbols via mpv.def.
+ ctx.env.LINKFLAGS += ['-Wl,-export-dynamic']
+
ctx.store_dependencies_lists()
def __write_version__(ctx):
diff --git a/wscript_build.py b/wscript_build.py
index b7c7b39464..4f65f1ba0f 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -51,39 +51,49 @@ def build(ctx):
ctx.load('waf_customizations')
ctx.load('generators.sources')
- ctx.file2string(
+ ctx(
+ features = "file2string",
source = "TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns",
- target = "osdep/macosx_icon.inc")
+ target = "osdep/macosx_icon.inc",
+ )
- ctx.file2string(
+ ctx(
+ features = "file2string",
source = "video/out/x11_icon.bin",
- target = "video/out/x11_icon.inc")
+ target = "video/out/x11_icon.inc",
+ )
- ctx.file2string(
+ ctx(
+ features = "file2string",
source = "etc/input.conf",
- target = "input/input_conf.h")
+ target = "input/input_conf.h",
+ )
- ctx.file2string(
+ ctx(
+ features = "file2string",
source = "etc/builtin.conf",
- target = "player/builtin_conf.inc")
+ target = "player/builtin_conf.inc",
+ )
- ctx.file2string(
+ ctx(
+ features = "file2string",
source = "sub/osd_font.otf",
- target = "sub/osd_font.h")
+ target = "sub/osd_font.h",
+ )
lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua",
"ytdl_hook.lua"]
+
for fn in lua_files:
fn = "player/lua/" + fn
- ctx.file2string(source = fn, target = os.path.splitext(fn)[0] + ".inc")
-
- ctx.matroska_header(
- source = "demux/ebml.c demux/demux_mkv.c",
- target = "ebml_types.h")
+ ctx(
+ features = "file2string",
+ source = fn,
+ target = os.path.splitext(fn)[0] + ".inc",
+ )
- ctx.matroska_definitions(
- source = "demux/ebml.c",
- target = "ebml_defs.c")
+ ctx(features = "ebml_header", target = "ebml_types.h")
+ ctx(features = "ebml_definitions", target = "ebml_defs.c")
if ctx.env.DEST_OS == 'win32':
main_fn_c = 'osdep/main-fn-win.c'
@@ -162,6 +172,7 @@ def build(ctx):
( "common/tags.c" ),
( "common/msg.c" ),
( "common/playlist.c" ),
+ ( "common/recorder.c" ),
( "common/version.c" ),
## Demuxers
@@ -292,15 +303,16 @@ def build(ctx):
( "video/vaapi.c", "vaapi" ),
( "video/vdpau.c", "vdpau" ),
( "video/vdpau_mixer.c", "vdpau" ),
- ( "video/decode/dec_video.c"),
- ( "video/decode/cuda.c", "cuda-hwaccel" ),
- ( "video/decode/dxva2.c", "d3d-hwaccel" ),
- ( "video/decode/d3d11va.c", "d3d-hwaccel" ),
( "video/decode/d3d.c", "win32" ),
- ( "video/decode/vaapi.c", "vaapi-hwaccel" ),
+ ( "video/decode/dec_video.c"),
+ ( "video/decode/hw_cuda.c", "cuda-hwaccel" ),
+ ( "video/decode/hw_dxva2.c", "d3d-hwaccel" ),
+ ( "video/decode/hw_d3d11va.c", "d3d-hwaccel" ),
+ ( "video/decode/hw_vaapi.c", "vaapi-hwaccel-new" ),
+ ( "video/decode/hw_vaapi_old.c", "vaapi-hwaccel-old" ),
+ ( "video/decode/hw_vdpau.c", "vdpau-hwaccel" ),
+ ( "video/decode/hw_videotoolbox.c", "videotoolbox-hwaccel" ),
( "video/decode/vd_lavc.c" ),
- ( "video/decode/videotoolbox.c", "videotoolbox-hwaccel" ),
- ( "video/decode/vdpau.c", "vdpau-hwaccel" ),
( "video/filter/refqueue.c" ),
( "video/filter/vf.c" ),
( "video/filter/vf_buffer.c" ),
@@ -459,13 +471,19 @@ def build(ctx):
features = "c",
)
+ syms = False
+ if ctx.dependency_satisfied('cplugins'):
+ syms = True
+ ctx.load("syms")
+
if ctx.dependency_satisfied('cplayer'):
ctx(
target = "mpv",
source = main_fn_c,
use = ctx.dependencies_use() + ['objects'],
includes = _all_includes(ctx),
- features = "c cprogram",
+ features = "c cprogram" + (" syms" if syms else ""),
+ export_symbols_def = "libmpv/mpv.def", # for syms=True
install_path = ctx.env.BINDIR
)
for f in ['mpv.conf', 'input.conf', 'mplayer-input.conf', \