summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/5_feature_request.md7
-rw-r--r--.github/ISSUE_TEMPLATE/6_question.md7
-rw-r--r--.travis.yml4
-rw-r--r--Copyright1
-rw-r--r--DOCS/client-api-changes.rst16
-rw-r--r--DOCS/edl-mpv.rst34
-rw-r--r--DOCS/interface-changes.rst27
-rw-r--r--DOCS/man/ao.rst31
-rw-r--r--DOCS/man/changes.rst10
-rw-r--r--DOCS/man/input.rst403
-rw-r--r--DOCS/man/ipc.rst99
-rw-r--r--DOCS/man/lua.rst112
-rw-r--r--DOCS/man/mpv.rst19
-rw-r--r--DOCS/man/options.rst222
-rw-r--r--DOCS/man/osc.rst11
-rw-r--r--DOCS/tech-overview.txt2
-rwxr-xr-xTOOLS/appveyor-build.sh1
-rw-r--r--TOOLS/lua/osd-test.lua35
-rw-r--r--TOOLS/lua/skip-logo.lua1
-rw-r--r--TOOLS/lua/test-hooks.lua32
-rwxr-xr-xTOOLS/matroska.py2
-rwxr-xr-xTOOLS/osxbundle.py4
-rwxr-xr-xTOOLS/stats-conv.py4
-rwxr-xr-xTOOLS/umpv39
-rw-r--r--audio/decode/ad_lavc.c10
-rw-r--r--audio/filter/af_format.c12
-rw-r--r--audio/filter/af_lavcac3enc.c12
-rw-r--r--audio/filter/af_rubberband.c58
-rw-r--r--audio/filter/af_scaletempo.c19
-rw-r--r--audio/out/ao.c24
-rw-r--r--audio/out/ao_alsa.c16
-rw-r--r--audio/out/ao_audiotrack.c4
-rw-r--r--audio/out/ao_coreaudio.c2
-rw-r--r--audio/out/ao_coreaudio_exclusive.c2
-rw-r--r--audio/out/ao_jack.c12
-rw-r--r--audio/out/ao_lavc.c12
-rw-r--r--audio/out/ao_null.c18
-rw-r--r--audio/out/ao_openal.c6
-rw-r--r--audio/out/ao_opensles.c6
-rw-r--r--audio/out/ao_oss.c657
-rw-r--r--audio/out/ao_pcm.c20
-rw-r--r--audio/out/ao_pulse.c9
-rw-r--r--audio/out/ao_rsound.c154
-rw-r--r--audio/out/ao_sdl.c2
-rw-r--r--audio/out/ao_sndio.c319
-rw-r--r--audio/out/ao_wasapi_utils.c52
-rwxr-xr-xci/build-macos.sh2
-rwxr-xr-xci/build-mingw64.sh1
-rwxr-xr-xci/build-tumbleweed.sh1
-rw-r--r--common/av_log.c17
-rw-r--r--common/av_log.h2
-rw-r--r--common/encode_lavc.c75
-rw-r--r--common/encode_lavc.h4
-rw-r--r--common/playlist.c24
-rw-r--r--common/playlist.h8
-rw-r--r--demux/cache.c7
-rw-r--r--demux/demux.c178
-rw-r--r--demux/demux.h4
-rw-r--r--demux/demux_cue.c2
-rw-r--r--demux/demux_edl.c51
-rw-r--r--demux/demux_lavf.c65
-rw-r--r--demux/demux_libarchive.c2
-rw-r--r--demux/demux_mkv.c25
-rw-r--r--demux/demux_raw.c56
-rw-r--r--demux/demux_timeline.c55
-rw-r--r--demux/timeline.h8
-rw-r--r--etc/input.conf1
-rw-r--r--filters/f_async_queue.c299
-rw-r--r--filters/f_async_queue.h92
-rw-r--r--filters/f_decoder_wrapper.c597
-rw-r--r--filters/f_decoder_wrapper.h39
-rw-r--r--filters/f_lavfi.c22
-rw-r--r--filters/f_swresample.c15
-rw-r--r--filters/filter.c83
-rw-r--r--filters/filter.h70
-rw-r--r--filters/filter_internal.h2
-rw-r--r--filters/user_filters.c2
-rw-r--r--input/cmd.c13
-rw-r--r--input/cmd.h3
-rw-r--r--input/input.c43
-rw-r--r--input/input.h12
-rw-r--r--input/ipc-dummy.c6
-rw-r--r--input/ipc-unix.c83
-rw-r--r--input/ipc-win.c6
-rw-r--r--input/ipc.c201
-rw-r--r--input/pipe-win32.c110
-rw-r--r--libmpv/client.h109
-rw-r--r--libmpv/mpv.def2
-rw-r--r--libmpv/qthelper.hpp386
-rw-r--r--misc/dispatch.c25
-rw-r--r--misc/dispatch.h3
-rw-r--r--options/m_config.h367
-rw-r--r--options/m_config_core.c910
-rw-r--r--options/m_config_core.h201
-rw-r--r--options/m_config_frontend.c (renamed from options/m_config.c)922
-rw-r--r--options/m_config_frontend.h260
-rw-r--r--options/m_option.c268
-rw-r--r--options/m_option.h270
-rw-r--r--options/m_property.c3
-rw-r--r--options/options.c1115
-rw-r--r--options/options.h17
-rw-r--r--options/parse_commandline.c5
-rw-r--r--options/parse_configfile.h2
-rw-r--r--osdep/atomic.h9
-rw-r--r--osdep/macos/mpv_helper.swift2
-rw-r--r--osdep/macos/remote_command_center.swift23
-rw-r--r--osdep/macosx_application.m82
-rw-r--r--osdep/macosx_application_objc.h3
-rw-r--r--osdep/macosx_events.m23
-rw-r--r--osdep/macosx_events_objc.h3
-rw-r--r--osdep/subprocess.c6
-rw-r--r--osdep/threads.c28
-rw-r--r--osdep/threads.h55
-rw-r--r--osdep/win32/include/pthread.h1
-rw-r--r--player/audio.c15
-rw-r--r--player/client.c244
-rw-r--r--player/client.h2
-rw-r--r--player/command.c646
-rw-r--r--player/command.h6
-rw-r--r--player/core.h19
-rw-r--r--player/javascript.c105
-rw-r--r--player/javascript/defaults.js21
-rw-r--r--player/loadfile.c69
-rw-r--r--player/lua.c298
-rw-r--r--player/lua/defaults.lua11
-rw-r--r--player/lua/osc.lua26
-rw-r--r--player/lua/stats.lua25
-rw-r--r--player/lua/ytdl_hook.lua294
-rw-r--r--player/main.c43
-rw-r--r--player/misc.c13
-rw-r--r--player/osd.c5
-rw-r--r--player/playloop.c118
-rw-r--r--player/scripting.c129
-rw-r--r--player/video.c77
-rw-r--r--stream/stream.c12
-rw-r--r--stream/stream_cdda.c20
-rw-r--r--stream/stream_dvb.c14
-rw-r--r--stream/stream_file.c11
-rw-r--r--stream/stream_lavf.c26
-rw-r--r--stream/stream_smb.c155
-rw-r--r--sub/ass_mp.c27
-rw-r--r--sub/ass_mp.h2
-rw-r--r--sub/dec_sub.c28
-rw-r--r--sub/osd.c44
-rw-r--r--sub/osd.h3
-rw-r--r--sub/osd_dummy.c39
-rw-r--r--sub/osd_libass.c57
-rw-r--r--sub/osd_state.h2
-rw-r--r--ta/ta.c172
-rw-r--r--ta/ta.h7
-rw-r--r--ta/ta_talloc.h3
-rw-r--r--ta/ta_utils.c27
-rw-r--r--test/ref/img_formats.txt33
-rw-r--r--video/csputils.c19
-rw-r--r--video/decode/vd_lavc.c49
-rw-r--r--video/filter/vf_d3d11vpp.c18
-rw-r--r--video/filter/vf_fingerprint.c6
-rw-r--r--video/filter/vf_format.c36
-rw-r--r--video/filter/vf_gpu.c4
-rw-r--r--video/filter/vf_sub.c4
-rw-r--r--video/filter/vf_vapoursynth.c9
-rw-r--r--video/filter/vf_vavpp.c22
-rw-r--r--video/filter/vf_vdpaupp.c26
-rw-r--r--video/fmt-conversion.c1
-rw-r--r--video/image_writer.c20
-rw-r--r--video/img_format.h3
-rw-r--r--video/out/android_common.c24
-rw-r--r--video/out/aspect.c8
-rw-r--r--video/out/cocoa-cb/events_view.swift26
-rw-r--r--video/out/cocoa-cb/window.swift2
-rw-r--r--video/out/cocoa_cb_common.swift17
-rw-r--r--video/out/cocoa_common.m6
-rw-r--r--video/out/d3d11/context.c60
-rw-r--r--video/out/d3d11/hwdec_d3d11va.c2
-rw-r--r--video/out/drm_common.c42
-rw-r--r--video/out/drm_prime.c87
-rw-r--r--video/out/drm_prime.h16
-rw-r--r--video/out/gpu/lcms.c20
-rw-r--r--video/out/gpu/spirv.c2
-rw-r--r--video/out/gpu/utils.c4
-rw-r--r--video/out/gpu/video.c236
-rw-r--r--video/out/gpu/video_shaders.c8
-rw-r--r--video/out/opengl/common.c1
-rw-r--r--video/out/opengl/context.c28
-rw-r--r--video/out/opengl/context_angle.c40
-rw-r--r--video/out/opengl/context_cocoa.c2
-rw-r--r--video/out/opengl/hwdec_drmprime_drm.c12
-rw-r--r--video/out/vo_direct3d.c30
-rw-r--r--video/out/vo_gpu.c8
-rw-r--r--video/out/vo_image.c4
-rw-r--r--video/out/vo_null.c2
-rw-r--r--video/out/vo_rpi.c8
-rw-r--r--video/out/vo_sdl.c6
-rw-r--r--video/out/vo_tct.c12
-rw-r--r--video/out/vo_vaapi.c12
-rw-r--r--video/out/vo_vdpau.c38
-rw-r--r--video/out/vo_xv.c29
-rw-r--r--video/out/vulkan/context.c20
-rw-r--r--video/out/wayland_common.c79
-rw-r--r--video/out/x11_common.c5
-rw-r--r--video/sws_utils.c42
-rw-r--r--video/vaapi.c2
-rw-r--r--video/zimg.c27
-rw-r--r--waftools/checks/custom.py8
-rw-r--r--waftools/checks/generic.py2
-rw-r--r--waftools/clang_compilation_database.py2
-rw-r--r--waftools/dependencies.py2
-rw-r--r--waftools/detections/compiler.py2
-rw-r--r--waftools/detections/compiler_swift.py52
-rw-r--r--waftools/features.py2
-rw-r--r--wscript85
-rw-r--r--wscript_build.py18
212 files changed, 7384 insertions, 6756 deletions
diff --git a/.github/ISSUE_TEMPLATE/5_feature_request.md b/.github/ISSUE_TEMPLATE/5_feature_request.md
index 1ad3da4a0f..2fba2baab6 100644
--- a/.github/ISSUE_TEMPLATE/5_feature_request.md
+++ b/.github/ISSUE_TEMPLATE/5_feature_request.md
@@ -13,3 +13,10 @@ https://github.com/mpv-player/mpv/labels/meta%3Afeature-request
### Expected behavior of the wanted feature
### Alternative behavior of the wanted feature
+
+### Log file
+
+Even if you think it's not necessary at first, it might help us later to find
+possible issues. Make a log file made with -v -v or --log-file=output.txt, paste
+it to https://0x0.st/ or attach it to the github issue, and replace this text
+with a link to it.
diff --git a/.github/ISSUE_TEMPLATE/6_question.md b/.github/ISSUE_TEMPLATE/6_question.md
index 37796f3433..b6131f35d8 100644
--- a/.github/ISSUE_TEMPLATE/6_question.md
+++ b/.github/ISSUE_TEMPLATE/6_question.md
@@ -16,3 +16,10 @@ described and needs clarification.
Before asking a question make sure it hasn't been asked or answered yet.
https://github.com/mpv-player/mpv/labels/meta%3Aquestion
+
+### Log file
+
+Even if you think it's not necessary at first, it might help us later to find
+possible issues. Make a log file made with -v -v or --log-file=output.txt, paste
+it to https://0x0.st/ or attach it to the github issue, and replace this text
+with a link to it.
diff --git a/.travis.yml b/.travis.yml
index 6910ca7763..ecf12085f1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: c
-macbase:
+_macbase:
- &macOld
os: osx
compiler: clang
@@ -43,8 +43,6 @@ dist: bionic
services:
- docker
-sudo: required
-
env:
global:
# Coverity token
diff --git a/Copyright b/Copyright
index 20f14c6f70..b7f5bfd5e6 100644
--- a/Copyright
+++ b/Copyright
@@ -53,7 +53,6 @@ The following files are still GPL only (--enable-lgpl disables them):
stream/stream_cdda.c unknown
stream/stream_dvb.* must stay GPL
stream/stream_dvdnav.c unknown
- stream/stream_smb.c will stay GPLv3
video/out/vo_caca.c unknown
video/out/vo_direct3d.c unknown
video/out/vo_vaapi.c probably impossible (some company's code)
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index e51286d291..ade0234e79 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -32,6 +32,22 @@ API changes
::
+ --- mpv 0.33.0 ---
+ 1.108 - Deprecate MPV_EVENT_IDLE
+ - add mpv_event_start_file
+ - add the following fields to mpv_event_end_file: playlist_entry_id,
+ playlist_insert_id, playlist_insert_num_entries
+ - add mpv_event_to_node()
+ - add mpv_client_id()
+ 1.107 - Remove the deprecated qthelper.hpp. This was obviously not part of the
+ libmpv API, only an "additionally" provided helper, thus this is not
+ considered an API change. If you are maintaining a project that relies
+ on this header, you can simply download this file and adjust the
+ include statement to use it instead:
+
+ https://raw.githubusercontent.com/mpv-player/mpv/v0.32.0/libmpv/qthelper.hpp
+
+ It is a good idea to write better wrappers for your use, though.
--- mpv 0.31.0 ---
1.107 - Deprecate MPV_EVENT_TICK
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index ec60d09ccc..78da44a8bf 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -214,6 +214,16 @@ It provides following parameters change track metadata:
``byterate``
Number of bytes per second this stream uses. (Purely informational.)
+``index``
+ The numeric index of the track this should map to (default: -1). This is
+ the 0-based index of the virtual stream as seen by the player, enumerating
+ all audio/video/subtitle streams. If nothing matches, this is silently
+ discarded. The special index -1 (the default) has two meanings: if there
+ was a previous meta data entry (either ``!track_meta`` or ``!delay_open``
+ element since the last ``!new_stream``), then this element manipulates
+ the previous meta data entry. If there was no previous entry, a new meta
+ data entry that matches all streams is created.
+
Example::
# mpv EDL v0
@@ -244,6 +254,12 @@ Using multiple segments requires you to specify all offsets and durations (also
it was never tested whether it works at all). Interaction with ``mp4_dash`` may
be strange.
+You can describe multiple sub-tracks by using multiple ``delay_open`` headers
+before the same source URL. (If there are multiple sub-tracks of the same media
+type, then the mapping to the real stream is probably rather arbitrary.) If the
+source contains tracks not described, a warning is logged when the delayed
+opening happens, and the track is hidden.
+
This has the following parameters:
``media_type``
@@ -261,9 +277,27 @@ This has the following parameters:
Above also applies for similar fields such as ``w``. These fields are
mostly to help with user track pre-selection.
+``flags``
+ A ``+`` separated list of boolean flags. Currently defined flags:
+
+ ``default``
+ Set the default track flag.
+
+ ``forced``
+ Set the forced track flag.
+
+ Other values are ignored after triggering a warning.
+
``w``, ``h``
For video codecs: expected video size. See ``codec`` for details.
+``fps``
+ For video codecs: expected video framerate, as integer. (The rate is usually
+ only crudely reported, and it makes no sense to expect exact values.)
+
+``samplerate``
+ For audio codecs: expected sample rate, as integer.
+
The ``delay_open`` header is not part of the core EDL format. It may be changed
or removed at any time, depending on mpv's internal requirements.
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 857697bc4c..cd391aa034 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -33,6 +33,33 @@ Interface changes
- js modules: ~~/scripts/modules.js/ is no longer used, global paths can be
set with custom init (see docs), dir-scripts first look at <dir>/modules/
- the OSX bundle now logs to "~/Library/Logs/mpv.log" by default
+ - deprecate the --cache-secs option (once removed, the cache cannot be
+ limited by time anymore)
+ - remove deprecated legacy hook API ("hook-add", "hook-ack"). Use either the
+ libmpv API (mpv_hook_add(), mpv_hook_continue()), or the Lua scripting
+ wrappers (mp.add_hook()).
+ - improve how property change notifications are delivered on events and on
+ hooks. In particular, a hook event is only returned to a client after all
+ changes initiated before the hook point were delivered to the same client.
+ In addition, it should no longer happen that events and property change
+ notifications were interleaved in bad ways (it could happen that a
+ property notification delivered after an event contained a value that was
+ valid only before the event happened).
+ - the playlist-pos and playlist-pos-1 properties now can return and accept
+ -1, and are never unavailable. Out of range indexes are now accepted, but
+ behave like writing -1.
+ - the playlist-pos and playlist-pos-1 properties deprecate the current
+ behavior when writing back the current value to the property: currently,
+ this restarts playback, but in the future, it will do nothing.
+ Using the "playlist-play-index" command is recommended instead.
+ - add "playlist-play-index" command
+ - add playlist-current-pos, playlist-playing-pos properties
+ - Lua end-file events set the "error" field; this is deprecated; use the
+ "file_error" instead for this specific event. Scripts relying on the
+ "error" field for end-file will silently break at some point in the
+ future.
+ - deprecate encoding mode (lack of maintainer)
+ - remove deprecated --input-file option
--- mpv 0.32.0 ---
- change behavior when using legacy option syntax with options that start
with two dashes (``--`` instead of a ``-``). Now, using the recommended
diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst
index a0c5052b46..5308a7175b 100644
--- a/DOCS/man/ao.rst
+++ b/DOCS/man/ao.rst
@@ -14,8 +14,7 @@ in the list.
See ``--ao=help`` for a list of compiled-in audio output drivers. The
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
- where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
- may work (the latter being experimental).
+ where PulseAudio is used.
Available audio output drivers are:
@@ -36,18 +35,6 @@ Available audio output drivers are:
with automatic upmixing with shared access, so playing stereo
and multichannel audio at the same time will work as expected.
-``oss``
- OSS audio output driver
-
- The following global options are supported by this audio output:
-
- ``--oss-mixer-device``
- Sets the audio mixer device (default: ``/dev/mixer``).
- ``--oss-mixer-channel``
- Sets the audio mixer channel (default: ``pcm``). Other valid values
- include **vol, pcm, line**. For a complete list of options look for
- ``SOUND_DEVICE_NAMES`` in ``/usr/include/linux/soundcard.h``.
-
``jack``
JACK (Jack Audio Connection Kit) audio output driver.
@@ -233,21 +220,5 @@ Available audio output drivers are:
``no-waveheader`` option - with ``waveheader`` it's broken, because
it will write a WAVE header every time the file is opened.
-``rsound``
- Audio output to an RSound daemon. Use ``--audio-device=rsound/<hostname>``
- to set the host name (with ``<hostname>`` replaced, without the ``< >``).
-
- .. note:: Completely useless, unless you intend to run RSound. Not to be
- confused with RoarAudio, which is something completely
- different.
-
-``sndio``
- Audio output to the OpenBSD sndio sound system
-
- .. note:: Experimental. There are known bugs and issues.
-
- (Note: only supports mono, stereo, 4.0, 5.1 and 7.1 channel
- layouts.)
-
``wasapi``
Audio output to the Windows Audio Session API.
diff --git a/DOCS/man/changes.rst b/DOCS/man/changes.rst
index d1daa32bd7..63de41c8d2 100644
--- a/DOCS/man/changes.rst
+++ b/DOCS/man/changes.rst
@@ -8,10 +8,12 @@ There is no real changelog, but you can look at the following things:
https://github.com/mpv-player/mpv/releases
* The git log, which is the "real" changelog
-* The files ``client-api-changes.rst`` and ``interface-changes.rst`` in the
- ``DOCS`` sub directoryon the git repository, which document API and user
- interface changes (the latter usually documents breaking changes only, rather
- than additions).
+* The file https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
+ documents changes to the command and user interface, such as options and
+ properties. (It usually documents breaking changes only, additions and
+ enhancements are often not listed.)
+* C API changes are listed in
+ https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
* The file ``mplayer-changes.rst`` in the ``DOCS`` sub directory on the git
repository, which used to be in place of this section. It documents some
changes that happened since mplayer2 forked off MPlayer. (Not updated
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 04cd8883f7..958b7a4241 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -377,8 +377,35 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
force
Terminate playback if the first file is being played.
+``playlist-play-index <integer|current|none>``
+ Start (or restart) playback of the given playlist index. In addition to the
+ 0-based playlist entry index, it supports the following values:
+
+ <current>
+ The current playlist entry (as in ``playlist-current-pos``) will be
+ played again (unload and reload). If none is set, playback is stopped.
+ (In corner cases, ``playlist-current-pos`` can point to a playlist entry
+ even if playback is currently inactive,
+
+ <none>
+ Playback is stopped. If idle mode (``--idle``) is enabled, the player
+ will enter idle mode, otherwise it will exit.
+
+ This comm and is similar to ``loadfile`` in that it only manipulates the
+ state of what to play next, without waiting until the current file is
+ unloaded, and the next one is loaded.
+
+ Setting ``playlist-pos`` or similar properties can have a similar effect to
+ this command. However, it's more explicit, and guarantees that playback is
+ restarted if for example the new playlist entry is the same as the previous
+ one.
+
``loadfile <url> [<flags> [<options>]]``
- Load the given file or URL and play it.
+ Load the given file or URL and play it. Technically, this is just a playlist
+ manipulation command (which either replaces the playlist or appends an entry
+ to it). Actual file loading happens independently. For example, a
+ ``loadfile`` command that replaces the current file with a new one returns
+ before the current file is stopped, and the new file even begins loading.
Second argument:
@@ -393,8 +420,10 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
The third argument is a list of options and values which should be set
while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``.
- Not all options can be changed this way. Some options require a restart
- of the player.
+ When using the client API, this can be a ``MPV_FORMAT_NODE_MAP`` (or a Lua
+ table), however the values themselves must be strings currently. These
+ options are set during playback, and restored to the previous value at end
+ of playback (see `Per-File Options`_).
``loadlist <url> [<flags>]``
Load the given playlist file or URL (like ``--playlist``).
@@ -643,11 +672,17 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
Write the resume config file that the ``quit-watch-later`` command writes,
but continue playback normally.
-``stop``
+``stop [<flags>]``
Stop playback and clear playlist. With default settings, this is
essentially like ``quit``. Useful for the client API: playback can be
stopped without terminating the player.
+ The first argument is optional, and supports the following flags:
+
+ keep-playlist
+ Do not clear the playlist.
+
+
``mouse <x> <y> [<button> [<mode>]]``
Send a mouse event with given coordinate (``<x>``, ``<y>``).
@@ -1019,6 +1054,31 @@ Input Commands that are Possibly Subject to Change
It's possible that future mpv versions will randomly change how Z order
between different OSD formats and builtin OSD is handled.
+ ``hidden``
+ If set to ``yes``/true, do not display this (default: no).
+
+ ``compute_bounds``
+ If set to ``yes``/true, attempt to determine bounds and write them to
+ the command's result value as ``x0``, ``x1``, ``y0``, ``y1`` rectangle
+ (default: no). If the rectangle is empty, not known, or somehow
+ degenerate, it is not set. ``x1``/``y1`` is the coordinate of the bottom
+ exclusive corner of the rectangle.
+
+ The result value may depend on the VO window size, and is based on the
+ last known window size at the time of the call. This means the results
+ may be different from what is actually rendered.
+
+ For ``ass-events``, the result rectangle is recomputed to ``PlayRes``
+ coordinates (``res_x``/``res_y``). If window size is not known, a
+ fallback is chosen.
+
+ You should be aware that this mechanism is very inefficient, as it
+ renders the full result, and then uses the bounding box of the rendered
+ bitmap list (even if ``hidden`` is set). It will flush various caches.
+ Its results also depend on the used libass version.
+
+ This feature is experimental, and may change in some way again.
+
Note: always use named arguments (``mpv_command_node()``). Scripts should
use the ``mp.create_osd_overlay()`` helper instead of invoking this command
directly.
@@ -1129,6 +1189,10 @@ Input Commands that are Possibly Subject to Change
the script to finish initialization or not changed multiple times, and the
future behavior is left undefined.
+ On success, returns a ``mpv_node`` with a ``client_id`` field set to the
+ return value of the ``mpv_client_id()`` API call of the newly created script
+ handle.
+
``change-list <name> <operation> <value>``
This command changes list options as described in `List Options`_. The
``<name>`` parameter is the normal option name, while ``<operation>`` is
@@ -1215,6 +1279,190 @@ Input Commands that are Possibly Subject to Change
Undocumented commands: ``ao-reload`` (experimental/internal).
+List of events
+~~~~~~~~~~~~~~
+
+This is a partial list of events. This section describes what
+``mpv_event_to_node()`` returns, and which is what scripting APIs and the JSON
+IPC sees. Note that the C API has separate C-level declarations with
+``mpv_event``, which may be slightly different.
+
+Note that events are asynchronous: the player core continues running while
+events are delivered to scripts and other clients. In some cases, you can hooks
+to enforce synchronous execution.
+
+All events can have the following fields:
+
+``event``
+ Name as the event (as returned by ``mpv_event_name()``).
+
+``id``
+ The ``reply_userdata`` field (opaque user value). If ``reply_userdata`` is 0,
+ the field is not added.
+
+``error``
+ Set to an error string (as returned by ``mpv_error_string()``). This field
+ is missing if no error happened, or the event type does not report error.
+ Most events leave this unset.
+
+This list uses the event name field value, and the C API symbol in brackets:
+
+``start-file`` (``MPV_EVENT_START_FILE``)
+ Happens right before a new file is loaded. When you receive this, the
+ player is loading the file (or possibly already done with it).
+
+ This has the following fields:
+
+ ``playlist_entry_id``
+ Playlist entry ID of the file being loaded now.
+
+``end-file`` (``MPV_EVENT_END_FILE``)
+ Happens after a file was unloaded. Typically, the player will load the
+ next file right away, or quit if this was the last file.
+
+ The event has the following fields:
+
+ ``reason``
+ Has one of these values:
+
+ ``eof``
+ The file has ended. This can (but doesn't have to) include
+ incomplete files or broken network connections under
+ circumstances.
+
+ ``stop``
+ Playback was ended by a command.
+
+ ``quit``
+ Playback was ended by sending the quit command.
+
+ ``error``
+ An error happened. In this case, an ``error`` field is present with
+ the error string.
+
+ ``redirect``
+ Happens with playlists and similar. Details see
+ ``MPV_END_FILE_REASON_REDIRECT`` in the C API.
+
+ ``unknown``
+ Unknown. Normally doesn't happen, unless the Lua API is out of sync
+ with the C API. (Likewise, it could happen that your script gets
+ reason strings that did not exist yet at the time your script was
+ written.)
+
+ ``playlist_entry_id``
+ Playlist entry ID of the file that was being played or attempted to be
+ played. This has the same value as the ``playlist_entry_id`` field in the
+ corresponding ``start-file`` event.
+
+ ``file_error``
+ Set to mpv error string describing the approximate reason why playback
+ failed. Unset if no error known. (In Lua scripting, this value was set
+ on the ``error`` field directly. This is deprecated since mpv 0.33.0.
+ In the future, this ``error`` field will be unset for this specific
+ event.)
+
+ ``playlist_insert_id``
+ If loading ended, because the playlist entry to be played was for example
+ a playlist, and the current playlist entry is replaced with a number of
+ other entries. This may happen at least with MPV_END_FILE_REASON_REDIRECT
+ (other event types may use this for similar but different purposes in the
+ future). In this case, playlist_insert_id will be set to the playlist
+ entry ID of the first inserted entry, and playlist_insert_num_entries to
+ the total number of inserted playlist entries. Note this in this specific
+ case, the ID of the last inserted entry is playlist_insert_id+num-1.
+ Beware that depending on circumstances, you may observe the new playlist
+ entries before seeing the event (e.g. reading the "playlist" property or
+ getting a property change notification before receiving the event).
+ If this is 0 in the C API, this field isn't added.
+
+ ``playlist_insert_num_entries``
+ See playlist_insert_id. Only present if playlist_insert_id is present.
+
+``file-loaded`` (``MPV_EVENT_FILE_LOADED``)
+ Happens after a file was loaded and begins playback.
+
+``seek`` (``MPV_EVENT_SEEK``)
+ Happens on seeking. (This might include cases when the player seeks
+ internally, even without user interaction. This includes e.g. segment
+ changes when playing ordered chapters Matroska files.)
+
+``playback-restart`` (``MPV_EVENT_PLAYBACK_RESTART``)
+ Start of playback after seek or after file was loaded.
+
+``shutdown`` (``MPV_EVENT_SHUTDOWN``)
+ Sent when the player quits, and the script should terminate. Normally
+ handled automatically. See `Details on the script initialization and lifecycle`_.
+
+``log-message`` (``MPV_EVENT_LOG_MESSAGE``)
+ Receives messages enabled with ``mpv_request_log_messages()`` (Lua:
+ ``mp.enable_messages``).
+
+ This contains, in addition to the default event fields, the following
+ fields:
+
+ ``prefix``
+ The module prefix, identifies the sender of the message. This is what
+ the terminal player puts in front of the message text when using the
+ ``--v`` option, and is also what is used for ``--msg-level``.
+
+ ``level``
+ The log level as string. See ``msg.log`` for possible log level names.
+ Note that later versions of mpv might add new levels or remove
+ (undocumented) existing ones.
+
+ ``text``
+ The log message. The text will end with a newline character. Sometimes
+ it can contain multiple lines.
+
+ Keep in mind that these messages are meant to be hints for humans. You
+ should not parse them, and prefix/level/text of messages might change
+ any time.
+
+``hook``
+ The event has the following fields:
+
+ ``hook_id``
+ ID to pass to ``mpv_hook_continue()``. The Lua scripting wrapper
+ provides a better API around this with ``mp.add_hook()``.
+
+``get-property-reply`` (``MPV_EVENT_GET_PROPERTY_REPLY``)
+ See C API.
+
+``set-property-reply`` (``MPV_EVENT_SET_PROPERTY_REPLY``)
+ See C API.
+
+``command-reply`` (``MPV_EVENT_COMMAND_REPLY``)
+ This is one of the commands for which the ```error`` field is meaningful.
+
+ JSON IPC and Lua and possibly other backends treat this specially and may
+ not pass the actual event to the user. See C API.
+
+ The event has the following fields:
+
+ ``result``
+ The result (on success) of any ``mpv_node`` type, if any.
+
+``client-message`` (``MPV_EVENT_CLIENT_MESSAGE``)
+ Lua and possibly other backends treat this specially and may not pass the
+ actual event to the user.
+
+ The event has the following fields:
+
+ ``args``
+ Array of strings with the message data.
+
+``video-reconfig`` (``MPV_EVENT_VIDEO_RECONFIG``)
+ Happens on video output or filter reconfig.
+
+``audio-reconfig`` (``MPV_EVENT_AUDIO_RECONFIG``)
+ Happens on audio output or filter reconfig.
+
+The following events also happen, but are deprecated: ``tracks-changed``,
+``track-switched``, ``pause``, ``unpause``, ``metadata-update``, ``idle``,
+``tick``, ``chapter-change``. Use ``mpv_observe_property()``
+(Lua: ``mp.observe_property()``) instead.
+
Hooks
~~~~~
@@ -1228,6 +1476,12 @@ the player freeze randomly. Basically, nobody should use this API.
The C API is described in the header files. The Lua API is described in the
Lua section.
+Before a hook is actually invoked on an API clients, it will attempt to return
+new values for all observed properties that were changed before the hook. This
+may make it easier for an application to set defined "barriers" between property
+change notifications by registering hooks. (That means these hooks will have an
+effect, even if you do nothing and make them continue immediately.)
+
The following hooks are currently defined:
``on_load``
@@ -1239,12 +1493,17 @@ The following hooks are currently defined:
``file-local-options/<option name>``. The player will wait until all
hooks are run.
+ Ordered after ``start-file`` and before ``playback-restart``.
+
``on_load_fail``
Called after after a file has been opened, but failed to. This can be
used to provide a fallback in case native demuxers failed to recognize
the file, instead of always running before the native demuxers like
``on_load``. Demux will only be retried if ``stream-open-filename``
- was changed.
+ was changed. If it fails again, this hook is _not_ called again, and
+ loading definitely fails.
+
+ Ordered after ``on_load``, and before ``playback-restart`` and ``end-file``.
``on_preloaded``
Called after a file has been opened, and before tracks are selected and
@@ -1257,50 +1516,24 @@ The following hooks are currently defined:
exactly can be done and not be done, and what information is available and
what is not yet available yet, is all subject to change.
+ Ordered after ``on_load_fail`` etc. and before ``playback-restart``.
+
``on_unload``
Run before closing a file, and before actually uninitializing
everything. It's not possible to resume playback in this state.
-Legacy hook API
-~~~~~~~~~~~~~~~
-
-.. warning::
-
- The legacy API is deprecated and will be removed soon.
-
-There are two special commands involved. Also, the client must listen for
-client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
-
-``hook-add <hook-name> <id> <priority>``
- Subscribe to the hook identified by the first argument (basically, the
- name of event). The ``id`` argument is an arbitrary integer chosen by the
- user. ``priority`` is used to sort all hook handlers globally across all
- clients. Each client can register multiple hook handlers (even for the
- same hook-name). Once the hook is registered, it cannot be unregistered.
-
- When a specific event happens, all registered handlers are run serially.
- This uses a protocol every client has to follow explicitly. When a hook
- handler is run, a client message (``MPV_EVENT_CLIENT_MESSAGE``) is sent to
- the client which registered the hook. This message has the following
- arguments:
-
- 1. the string ``hook_run``
- 2. the ``id`` argument the hook was registered with as string (this can be
- used to correctly handle multiple hooks registered by the same client,
- as long as the ``id`` argument is unique in the client)
- 3. something undefined, used by the hook mechanism to track hook execution
+ Ordered before ``end-file``. Will also happen in the error case (then after
+ ``on_load_fail``).
- Upon receiving this message, the client can handle the event. While doing
- this, the player core will still react to requests, but playback will
- typically be stopped.
+``on_before_start_file``
+ Run before a ``start-file`` event is sent. (If any client changes the
+ current playlist entry, or sends a quit command to the player, the
+ corresponding event will not actually happen after the hook returns.)
+ Useful to drain property changes before a new file is loaded.
- When the client is done, it must continue the core's hook execution by
- running the ``hook-ack`` command.
-
-``hook-ack <string>``
- Run the next hook in the global chain of hooks. The argument is the 3rd
- argument of the client message that starts hook execution for the
- current client.
+``on_after_end_file``
+ Run after an ``end-file`` event. Useful to drain property changes after a
+ file has finished.
Input Command Prefixes
----------------------
@@ -1755,6 +1988,8 @@ Property list
This gives the number bytes per seconds over a 1 second window (using
the type ``MPV_FORMAT_INT64`` for the client API).
+ This is the same as ``demuxer-cache-state/raw-input-rate``.
+
``demuxer-cache-duration``
Approximate duration of video buffered in the demuxer, in seconds. The
guess is very unreliable, and often the property will not be available
@@ -1802,6 +2037,10 @@ Property list
``cache-duration`` is ``demuxer-cache-duration``. Missing if unavailable.
+ ``raw-input-rate`` is the estimated input rate of the network layer (or any
+ other byte-oriented input layer) in bytes per second. May be inaccurate or
+ missing.
+
When querying the property with the client API using ``MPV_FORMAT_NODE``,
or with Lua ``mp.get_property_native``, this will return a mpv_node with
the following contents:
@@ -1818,6 +2057,7 @@ Property list
"fw-bytes" MPV_FORMAT_INT64
"file-cache-bytes" MPV_FORMAT_INT64
"cache-duration" MPV_FORMAT_DOUBLE
+ "raw-input-rate" MPV_FORMAT_INT64
Other fields (might be changed or removed in the future):
@@ -2233,12 +2473,60 @@ Property list
is returned instead.
``playlist-pos`` (RW)
- Current position on playlist. The first entry is on position 0. Writing
- to the property will restart playback at the written entry.
+ Current position on playlist. The first entry is on position 0. Writing to
+ this property may start playback at the new position.
+
+ In some cases, this is not necessarily the currently playing file. See
+ explanation of ``current`` and ``playing`` flags in ``playlist``.
+
+ If there the playlist is empty, or if it's non-empty, but no entry is
+ "current", this property returns -1. Likewise, writing -1 will put the
+ player into idle mode (or exit playback if idle mode is not enabled). If an
+ out of range index is written to the property, this behaves as if writing -1.
+ (Before mpv 0.33.0, instead of returning -1, this property was unavailable
+ if no playlist entry was current.)
+
+ Writing the current value back to the property is subject to change.
+ Currently, it will restart playback of the playlist entry. But in the
+ future, writing the current value will be ignored. Use the
+ ``playlist-play-index`` command to get guaranteed behavior.
``playlist-pos-1`` (RW)
Same as ``playlist-pos``, but 1-based.
+``playlist-current-pos`` (RW)
+ Index of the "current" item on playlist. This usually, but not necessarily,
+ the currently playing item (see ``playlist-playing-pos``). Depending on the
+ exact internal state of the player, it may refer to the playlist item to
+ play next, or the playlist item used to determine what to play next.
+
+ For reading, this is exactly the same as ``playlist-pos``.
+
+ For writing, this *only* sets the position of the "current" item, without
+ stopping playback of the current file (or starting playback, if this is done
+ in idle mode). Use -1 to remove the current flag.
+
+ This property is only vaguely useful. If set during playback, it will
+ typically cause the playlist entry *after* it to be played next. Another
+ possibly odd observable state is that if ``playlist-next`` is run during
+ playback, this property is set to the playlist entry to play next (unlike
+ the previous case). There is an internal flag that decides whether the
+ current playlist entry or the next one should be played, and this flag is
+ currently inaccessible for API users. (Whether this behavior will kept is
+ possibly subject to change.)
+
+``playlist-playing-pos``
+ Index of the "playing" item on playlist. A playlist item is "playing" if
+ it's being loaded, actually playing, or being unloaded. This property is set
+ during the ``MPV_EVENT_START_FILE`` (``start-file``) and the
+ ``MPV_EVENT_START_END`` (``end-file``) events. Outside of that, it returns
+ -1. If the playlist entry was somehow removed during playback, but playback
+ hasn't stopped yet, or is in progress of being stopped, it also returns -1.
+ (This can happen at least during state transitions.)
+
+ In the "playing" state, this is usually the same as ``playlist-pos``, except
+ during state changes, or if ``playlist-current-pos`` was written explicitly.
+
``playlist-count``
Number of total playlist entries.
@@ -2255,18 +2543,25 @@ Property list
``playlist/N/filename``
Filename of the Nth entry.
- ``playlist/N/current``, ``playlist/N/playing``
- ``yes`` if this entry is currently playing (or being loaded).
- Unavailable or ``no`` otherwise. When changing files, ``current`` and
- ``playing`` can be different, because the currently playing file hasn't
- been unloaded yet; in this case, ``current`` refers to the new
- selection. (Since mpv 0.7.0.)
+ ``playlist/N/playing``
+ ``yes`` if the ``playlist-playing-pos`` property points to this entry,
+ unavailable or ``no`` otherwise.
+
+ ``playlist/N/current``
+ ``yes`` if the ``playlist-current-pos`` property points to this entry,
+ unavailable or ``no`` otherwise.
``playlist/N/title``
Name of the Nth entry. Only available if the playlist file contains
such fields, and only if mpv's parser supports it for the given
playlist format.
+ ``playlist/N/id``
+ Unique ID for this entry. This is an automatically assigned integer ID
+ that is unique for the entire life time of the current mpv core
+ instance. Other commands, events, etc. use this as ``playlist_entry_id``
+ fields.
+
When querying the property with the client API using ``MPV_FORMAT_NODE``,
or with Lua ``mp.get_property_native``, this will return a mpv_node with
the following contents:
@@ -2279,6 +2574,7 @@ Property list
"current" MPV_FORMAT_FLAG (might be missing; since mpv 0.7.0)
"playing" MPV_FORMAT_FLAG (same)
"title" MPV_FORMAT_STRING (optional)
+ "id" MPV_FORMAT_INT64
``track-list``
List of audio/video/sub tracks, current entry marked. Currently, the raw
@@ -2743,6 +3039,11 @@ Property list
still named the same.) This property is unavailable if mpv is linked against
older FFmpeg and Libav versions.
+``libass-version``
+ Return the value of ``ass_library_version()``. This is an integer, encoded
+ in a somewhat weird form (apparently "hex BCD"), indicating the release
+ version of the libass library linked to mpv.
+
``options/<name>`` (RW)
Read-only access to value of option ``--<name>``. Most options can be
changed at runtime by writing to this property. Note that many options
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index 2aa406b190..fdf5daf9b6 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -71,6 +71,9 @@ To be able to simultaneously read and write from the IPC pipe, like on Linux,
it's necessary to write an external program that uses overlapped file I/O (or
some wrapper like .NET's NamedPipeClientStream.)
+You can open the pipe in PuTTY as "serial" device. This is not very
+comfortable, but gives a way to test interactively without having to write code.
+
Protocol
--------
@@ -133,9 +136,6 @@ Would generate this response:
If you don't specify a ``request_id``, command replies will set it to 0.
-Commands may run asynchronously in the future, instead of blocking the socket
-until a reply is sent.
-
All commands, replies, and events are separated from each other with a line
break character (``\n``).
@@ -147,6 +147,72 @@ with ``#`` and empty lines are ignored.
Currently, embedded 0 bytes terminate the current line, but you should not
rely on this.
+Data flow
+---------
+
+Currently, the mpv-side IPC implementation does not service the socket while a
+command is executed and the reply is written. It is for example not possible
+that other events, that happened during the execution of the command, are
+written to the socket before the reply is written.
+
+This might change in the future. The only guarantee is that replies to IPC
+messages are sent in sequence.
+
+Also, since socket I/O is inherently asynchronous, it is possible that you read
+unrelated event messages from the socket, before you read the reply to the
+previous command you sent. In this case, these events were queued by the mpv
+side before it read and started processing your command message.
+
+If the mpv-side IPC implementation switches away from blocking writes and
+blocking command execution, it may attempt to send events at any time.
+
+You can also use asynchronous commands, which can return in any order, and
+which do not block IPC protocol interaction at all while the command is
+executed in the background.
+
+Asynchronous commands
+---------------------
+
+Command can be run asynchronously. This behaves exactly as with normal command
+execution, except that execution is not blocking. Other commands can be sent
+while it's executing, and command completion can be arbitrarily reordered.
+
+The ``async`` field controls this. If present, it must be a boolean. If missing,
+``false`` is assumed.
+
+For example, this initiates an asynchronous command:
+
+::
+
+ { "command": ["screenshot"], "request_id": 123, "async": true }
+
+And this is the completion:
+
+::
+
+ {"request_id":123,"error":"success","data":null}
+
+By design, you will not get a confirmation that the command was started. If a
+command is long running, sending the message will lead to any reply until much
+later when the command finishes.
+
+Some commands execute synchronously, but these will behave like asynchronous
+commands that finished execution immediately.
+
+Cancellation of asynchronous commands is available in the libmpv API, but has
+not yet been implemented in the IPC protocol.
+
+Commands with named arguments
+-----------------------------
+
+If the ``command`` field is a JSON object, named arguments are expected. This
+is described in the C API ``mpv_command_node()`` documentation (the
+``MPV_FORMAT_NODE_MAP`` case). In some cases, this may make commands more
+readable, while some obscure commands basically require using named arguments.
+
+Currently, only "proper" commands (as listed by `List of Input Commands`_)
+support named arguments.
+
Commands
--------
@@ -297,3 +363,30 @@ Is equivalent to:
::
{ "objkey": "value\n" }
+
+Alternative ways of starting clients
+------------------------------------
+
+You can create an anonymous IPC connection without having to set
+``--input-ipc-server``. This is achieved through a mpv pseudo scripting backend
+that starts processes.
+
+You can put ``.run`` file extension in the mpv scripts directory in its config
+directory (see the `FILES`_ section for details), or load them through other
+means (see `Script location`_). These scripts are simply executed with the OS
+native mechanism (as if you ran them in the shell). They must have a proper
+shebang and have the executable bit set.
+
+When executed, a socket (the IPC connection) is passed to them through file
+descriptor inheritance. The file descriptor is indicated as the special command
+line argument ``--mpv-ipc-fd=N``, where ``N`` is the numeric file descriptor.
+Currently, this is hardcoded as ``--mpv-ipc-fd=3``, and the intention is that
+it will always be ``3``. (This was a compromise between keeping it as simple as
+possible, and not doing too much implicitly. Also, since there is a chance that
+this will change anyway, you should at least validate that you got the expected
+argument.)
+
+The rest is the same as with a normal ``--input-ipc-server`` IPC connection. mpv
+does not attempt to observe or other interact with the started script process.
+
+This does not work in Windows yet.
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index a9fc02ed44..b256d7bf2d 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -599,6 +599,7 @@ are useful only in special situations.
``update()``
Commit the OSD overlay to the screen, or in other words, run the
``osd-overlay`` command with the current fields of the overlay table.
+ Returns the result of the ``osd-overlay`` command itself.
``remove()``
Remove the overlay from the screen. A ``update()`` call will add it
@@ -870,116 +871,7 @@ Example:
mp.register_event("file-loaded", my_fn)
-
-
-List of events
---------------
-
-``start-file``
- Happens right before a new file is loaded. When you receive this, the
- player is loading the file (or possibly already done with it).
-
-``end-file``
- Happens after a file was unloaded. Typically, the player will load the
- next file right away, or quit if this was the last file.
-
- The event has the ``reason`` field, which takes one of these values:
-
- ``eof``
- The file has ended. This can (but doesn't have to) include
- incomplete files or broken network connections under
- circumstances.
-
- ``stop``
- Playback was ended by a command.
-
- ``quit``
- Playback was ended by sending the quit command.
-
- ``error``
- An error happened. In this case, an ``error`` field is present with
- the error string.
-
- ``redirect``
- Happens with playlists and similar. Details see
- ``MPV_END_FILE_REASON_REDIRECT`` in the C API.
-
- ``unknown``
- Unknown. Normally doesn't happen, unless the Lua API is out of sync
- with the C API. (Likewise, it could happen that your script gets
- reason strings that did not exist yet at the time your script was
- written.)
-
-``file-loaded``
- Happens after a file was loaded and begins playback.
-
-``seek``
- Happens on seeking. (This might include cases when the player seeks
- internally, even without user interaction. This includes e.g. segment
- changes when playing ordered chapters Matroska files.)
-
-``playback-restart``
- Start of playback after seek or after file was loaded.
-
-``idle``
- Idle mode is entered. This happens when playback ended, and the player was
- started with ``--idle`` or ``--force-window``. This mode is implicitly ended
- when the ``start-file`` or ``shutdown`` events happen.
-
-``tick``
- Called after a video frame was displayed. This is a hack, and you should
- avoid using it. Use timers instead and maybe watch pausing/unpausing events
- to avoid wasting CPU when the player is paused.
-
-``shutdown``
- Sent when the player quits, and the script should terminate. Normally
- handled automatically. See `Details on the script initialization and lifecycle`_.
-
-``log-message``
- Receives messages enabled with ``mp.enable_messages``. The message data
- is contained in the table passed as first parameter to the event handler.
- The table contains, in addition to the default event fields, the following
- fields:
-
- ``prefix``
- The module prefix, identifies the sender of the message. This is what
- the terminal player puts in front of the message text when using the
- ``--v`` option, and is also what is used for ``--msg-level``.
-
- ``level``
- The log level as string. See ``msg.log`` for possible log level names.
- Note that later versions of mpv might add new levels or remove
- (undocumented) existing ones.
-
- ``text``
- The log message. The text will end with a newline character. Sometimes
- it can contain multiple lines.
-
- Keep in mind that these messages are meant to be hints for humans. You
- should not parse them, and prefix/level/text of messages might change
- any time.
-
-``get-property-reply``
- Undocumented (not useful for Lua scripts).
-
-``set-property-reply``
- Undocumented (not useful for Lua scripts).
-
-``command-reply``
- Undocumented (not useful for Lua scripts).
-
-``client-message``
- Undocumented (used internally).
-
-``video-reconfig``
- Happens on video output or filter reconfig.
-
-``audio-reconfig``
- Happens on audio output or filter reconfig.
-
-The following events also happen, but are deprecated: ``tracks-changed``,
-``track-switched``, ``pause``, ``unpause``, ``metadata-update``,
-``chapter-change``. Use ``mp.observe_property()`` instead.
+For the existing event types, see `List of events`_.
Extras
------
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index d019f15c6a..6078bf9731 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -427,15 +427,18 @@ additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
Some mpv options interpret paths starting with ``~``. Currently, the prefix
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
``~/`` expands to the user's home directory. (The trailing ``/`` is always
-required.) There are the following paths as well:
+required.) The following paths are currently recognized:
================ ===============================================================
Name Meaning
================ ===============================================================
+``~~/`` mpv config dir (for example ``~/.config/mpv/``)
+``~/`` user home directory root (similar to shell, ``$HOME``)
``~~home/`` same as ``~~/``
``~~global/`` the global config path, if available (not on win32)
``~~osxbundle/`` the OSX bundle resource path (OSX only)
``~~desktop/`` the path to the desktop (win32, OSX)
+``~~old_home`` do not use
================ ===============================================================
@@ -602,7 +605,7 @@ setting them to *no*. Even suboptions can be specified in this way.
# Use GPU-accelerated video output by default.
vo=gpu
# Use quotes for text that can contain spaces:
- status-msg="Time: ${time-pos}"
+ term-status-msg="Time: ${time-pos}"
Escaping spaces and special characters
--------------------------------------
@@ -786,7 +789,7 @@ listed.
- ``AV:`` or ``V:`` (video only) or ``A:`` (audio only)
- The current time position in ``HH:MM:SS`` format (``playback-time`` property)
-- The total file duration (absent if unknown) (``length`` property)
+- The total file duration (absent if unknown) (``duration`` property)
- Playback speed, e.g. `` x2.0``. Only visible if the speed is not normal. This
is the user-requested speed, and not the actual speed (usually they should
be the same, unless playback is too slow). (``speed`` property.)
@@ -812,11 +815,11 @@ listed.
- Dropped frames, e.g. ``Dropped: 4``. Shows up only if the count is not 0. Can
grow if the video framerate is higher than that of the display, or if video
rendering is too slow. May also be incremented on "hiccups" and when the video
- frame couldn't be displayed on time. (``vo-drop-frame-count`` property.)
+ frame couldn't be displayed on time. (``frame-drop-count`` property.)
If the decoder drops frames, the number of decoder-dropped frames is appended
to the display as well, e.g.: ``Dropped: 4/34``. This happens only if
decoder frame dropping is enabled with the ``--framedrop`` options.
- (``drop-frame-count`` property.)
+ (``decoder-frame-drop-count`` property.)
- Cache state, e.g. ``Cache: 2s/134KB``. Visible if the stream cache is enabled.
The first value shows the amount of video buffered in the demuxer in seconds,
the second value shows the estimated size of the buffered amount in kilobytes.
@@ -895,7 +898,7 @@ PROTOCOLS
``smb://PATH``
- Play a path from Samba share.
+ Play a path from Samba share. (Requires FFmpeg support.)
``bd://[title][/device]`` ``--bluray-device=PATH``
@@ -1096,10 +1099,6 @@ behavior of mpv.
``$HOME/.mpv`` is always added to the list of config search paths with a
lower priority.
-``XDG_CONFIG_DIRS``
- If set, XDG-style system configuration directories are used. Otherwise,
- the UNIX convention (``PREFIX/etc/mpv/``) is used.
-
``MPV_HOME``
Directory where mpv looks for user settings. Overrides ``HOME``, and mpv
will try to load the config file as ``$MPV_HOME/mpv.conf``.
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 5ffc16cf89..7fd01f0592 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -222,7 +222,7 @@ Playback Control
chapter instead. A negative value means always go back to the previous
chapter.
-``--hr-seek=<no|absolute|yes>``
+``--hr-seek=<no|absolute|yes|default>``
Select when to use precise seeks that are not limited to keyframes. Such
seeks require decoding video from the previous keyframe up to the target
position and so can take some time depending on decoding performance. For
@@ -234,6 +234,9 @@ Playback Control
:absolute: Use precise seeks if the seek is to an absolute position in the
file, such as a chapter seek, but not for relative seeks like
the default behavior of arrow keys (default).
+ :default: Like ``absolute``, but enable hr-seeks in audio-only cases. The
+ exact behavior is implementation specific and may change with
+ new releases.
:yes: Use precise seeks whenever possible.
:always: Same as ``yes`` (for compatibility).
@@ -529,6 +532,9 @@ Playback Control
(basically it's the ``--video-reversal-buffer`` equivalent for the
demuxer layer).
+ - Setting ``--vd-queue-enable=yes`` can help a lot to make playback smooth
+ (once it works).
+
- ``--demuxer-backward-playback-step`` also factors into how many seeks may
be performed, and whether backward demuxing could break due to queue
overflow. If it's set too high, the backstep operation needs to search
@@ -810,40 +816,109 @@ Program Behavior
If the script can't do anything with an URL, it will do nothing.
- The ``try_ytdl_first`` script option accepts a boolean 'yes' or 'no', and if
- 'yes' will try parsing the URL with youtube-dl first, instead of the default
- where it's only after mpv failed to open it. This mostly depends on whether
- most of your URLs need youtube-dl parsing.
+ This accepts a set of options, which can be passed to it with the
+ ``--script-opts`` option (using ``ytdl_hook-`` as prefix):
- The ``exclude`` script option accepts a ``|``-separated list of URL patterns
- which mpv should not use with youtube-dl. The patterns are matched after
- the ``http(s)://`` part of the URL.
+ ``try_ytdl_first=<yes|no>``
+ If 'yes' will try parsing the URL with youtube-dl first, instead of the
+ default where it's only after mpv failed to open it. This mostly depends
+ on whether most of your URLs need youtube-dl parsing.
- ``^`` matches the beginning of the URL, ``$`` matches its end, and you
- should use ``%`` before any of the characters ``^$()%|,.[]*+-?`` to match
- that character.
+ ``exclude=<URL1|URL2|...``
+ A ``|``-separated list of URL patterns which mpv should not use with
+ youtube-dl. The patterns are matched after the ``http(s)://`` part of
+ the URL.
- .. admonition:: Examples
+ ``^`` matches the beginning of the URL, ``$`` matches its end, and you
+ should use ``%`` before any of the characters ``^$()%|,.[]*+-?`` to
+ match that character.
+
+ .. admonition:: Examples
+
+ - ``--script-opts=ytdl_hook-exclude='^youtube%.com'``
+ will exclude any URL that starts with ``http://youtube.com`` or
+ ``https://youtube.com``.
+ - ``--script-opts=ytdl_hook-exclude='%.mkv$|%.mp4$'``
+ will exclude any URL that ends with ``.mkv`` or ``.mp4``.
+
+ See more lua patterns here: https://www.lua.org/manual/5.1/manual.html#5.4.1
+
+ ``all_formats=<yes|no>``
+ If 'yes' will attempt to add all formats found reported by youtube-dl
+ (default: no). Each format is added as a separate track. In addition,
+ they are delay-loaded, and actually opened only when a track is selected
+ (this should keep load times as low as without this option).
+
+ It adds average bitrate metadata, if available, which means you can use
+ ``--hls-bitrate`` to decide which track to select. (HLS used to be the
+ only format whose alternative quality streams were exposed in a similar
+ way, thus the option name.)
+
+ Tracks which represent formats that were selected by youtube-dl as
+ default will have the default flag set. This means mpv should generally
+ still select formats chosen with ``--ytdl-format`` by default.
+
+ Although this mechanism makes it possible to switch streams at runtime,
+ it's not suitable for this purpose for various technical reasons. (It's
+ slow, which can't be really fixed.) In general, this option is not
+ useful, and was only added to show that it's possible.
+
+ There are two cases that must be considered when doing quality/bandwidth
+ selection:
- - ``--script-opts=ytdl_hook-exclude='^youtube%.com'``
- will exclude any URL that starts with ``http://youtube.com`` or
- ``https://youtube.com``.
- - ``--script-opts=ytdl_hook-exclude='%.mkv$|%.mp4$'``
- will exclude any URL that ends with ``.mkv`` or ``.mp4``.
+ 1. Completely separate audio and video streams (DASH-like). Each of
+ these streams contain either only audio or video, so you can
+ mix and combine audio/video bandwidths without restriction. This
+ intuitively matches best with the concept of selecting quality
+ by track (what ``all_formats`` is supposed to do).
- See more lua patterns here: https://www.lua.org/manual/5.1/manual.html#5.4.1
+ 2. Separate sets of muxed audio and video streams. Each version of
+ the media contains both an audio and video stream, and they are
+ interleaved. In order not to waste bandwidth, you should only
+ select one of these versions (if, for example, you select an
+ audio stream, then video will be downloaded, even if you selected
+ video from a different stream).
- The ``use_manifests`` script option makes mpv use the master manifest URL for
- formats like HLS and DASH, if available, allowing for video/audio selection
- in runtime. It's disabled ("no") by default for performance reasons.
+ mpv will still represent them as separate tracks, but will set
+ the title of each track to ``muxed-N``, where ``N`` is replaced
+ with the youtube-dl format ID of the originating stream.
-``--ytdl-format=<best|worst|mp4|webm|...>``
+ Some sites will mix 1. and 2., but we assume that they do so for
+ compatibility reasons, and there is no reason to use them at all.
+
+ ``force_all_formats=<yes|no>``
+ If set to 'yes', and ``all_formats`` is also set to 'yes', this will
+ try to represent all youtube-dl reported formats as tracks, even if
+ mpv would normally use the direct URL reported by it (default: yes).
+
+ It appears this normally makes a difference if youtube-dl works on a
+ master HLS playlist.
+
+ If this is set to 'no', this specific kind of stream is treated like
+ ``all_formats`` is set to 'no', and the stream selection as done by
+ youtube-dl (via ``--ytdl-format``) is used.
+
+ ``use_manifests=<yes|no>``
+ Make mpv use the master manifest URL for formats like HLS and DASH,
+ if available, allowing for video/audio selection in runtime (default:
+ no). It's disabled ("no") by default for performance reasons.
+
+ .. admonition:: Why do the option names mix ``_`` and ``-``?
+
+ I have no idea.
+
+``--ytdl-format=<ytdl|best|worst|mp4|webm|...>``
Video format/quality that is directly passed to youtube-dl. The possible
values are specific to the website and the video, for a given url the
available formats can be found with the command
``youtube-dl --list-formats URL``. See youtube-dl's documentation for
available aliases.
- (Default: youtube-dl's default, currently ``bestvideo+bestaudio/best``)
+ (Default: ``bestvideo+bestaudio/best``)
+
+ The ``ytdl`` value does not pass a ``--format`` option to youtube-dl at all,
+ and thus does not override its default. Note that sometimes youtube-dl
+ returns a format that mpv cannot use, and in these cases the mpv default
+ may work better.
``--ytdl-raw-options=<key>=<value>[,<key>=<value>[,...]]``
Pass arbitrary options to youtube-dl. Parameter and argument should be
@@ -1757,7 +1832,7 @@ Audio
other players) ignore this for the sake of better audio quality.
``--ad-lavc-downmix=<yes|no>``
- Whether to request audio channel downmixing from the decoder (default: yes).
+ Whether to request audio channel downmixing from the decoder (default: no).
Some decoders, like AC-3, AAC and DTS, can remix audio on decoding. The
requested number of output channels is set with the ``--audio-channels`` option.
Useful for playing surround audio on a stereo system.
@@ -3337,6 +3412,12 @@ Demuxer
``--demuxer-mkv-subtitle-preroll-secs-index=<value>``
See ``--demuxer-mkv-subtitle-preroll``.
+``--demuxer-mkv-probe-start-time=<yes|no>``
+ Check the start time of Matroska files (default: yes). This simply reads the
+ first cluster timestamps and assumes it is the start time. Technically, this
+ also reads the first timestamp, which may increase latency by one frame
+ (which may be relevant for live streams).
+
``--demuxer-mkv-probe-video-duration=<yes|no|full>``
When opening the file, seek to the end of it, and check what timestamp the
last video packet has, and report that as file duration. This is strictly
@@ -3459,6 +3540,16 @@ Demuxer
``--cache-secs`` is used (i.e. when the stream appears to be a network
stream or the stream cache is enabled).
+``--demuxer-force-retry-on-eof=<yes|no>``
+ Whether to keep retrying making the demuxer thread read more packets each
+ time the decoder dequeues a packet, even if the end of the file was reached
+ (default: no). This does not really make sense, but was the default behavior
+ in mpv 0.32.0 and earlier. This option will be silently removed after a
+ while, and exists only to restore the old behavior for testing, in case this
+ was actually needed somewhere. This does _not_ help with files that are
+ being appended to (in these cases use ``appending://``, or disable the
+ cache).
+
``--demuxer-thread=<yes|no>``
Run the demuxer in a separate thread, and let it prefetch a certain amount
of packets (default: yes). Having this enabled leads to smoother playback,
@@ -3487,8 +3578,13 @@ Demuxer
a timestamp difference higher than the readahead amount relative to the
last packet returned to the decoder, the demuxer keeps reading.
- Note that the ``--cache-secs`` option will override this value if a cache
- is enabled, and the value is larger.
+ Note that enabling the cache (such as ``--cache=yes``, or if the input
+ is considered a network stream, and ``--cache=auto`` is used), this option
+ is mostly ignored. (``--cache-secs`` will override this. Technically, the
+ maximum of both options is used.)
+
+ The main purpose of this option is to limit the readhead for local playback,
+ since a large readahead value is not overly useful in this case.
(This value tends to be fuzzy, because many file formats don't store linear
timestamps.)
@@ -3586,22 +3682,6 @@ Input
work (key bindings that normally quit will be shown on OSD only, just
like any other binding). See `INPUT.CONF`_.
-``--input-file=<filename>``
- Deprecated. Use ``--input-ipc-server``.
-
- Read commands from the given file. Mostly useful with a FIFO. Since
- mpv 0.7.0 also understands JSON commands (see `JSON IPC`_), but you can't
- get replies or events. Use ``--input-ipc-server`` for something
- bi-directional. On MS Windows, JSON commands are not available.
-
- This can also specify a direct file descriptor with ``fd://N`` (UNIX only).
- In this case, JSON replies will be written if the FD is writable.
-
- .. note::
-
- When the given file is a FIFO mpv opens both ends, so you can do several
- `echo "seek 10" > mp_pipe` and the pipe will stay valid.
-
``--input-terminal``, ``--no-input-terminal``
``--no-input-terminal`` prevents the player from reading key events from
standard input. Useful when reading data from standard input. This is
@@ -4302,6 +4382,12 @@ Cache
Turn off input stream caching. See ``--cache``.
``--cache-secs=<seconds>``
+ Deprecated. Once this option is removed, there will be no way to limit the
+ cache size by time (only by size with ``--demuxer-max-bytes``). This option
+ is considered useless, since there is no good reason to limit the cache by
+ time, and the default value of this option is already something very high.
+ The interaction with the other cache options is also confusing.
+
How many seconds of audio/video to prefetch if the cache is active. This
overrides the ``--demuxer-readahead-secs`` option if and only if the cache
is enabled and the value is larger. The default value is set to something
@@ -4416,6 +4502,54 @@ Cache
See ``--list-options`` for defaults and value range. ``<bytesize>`` options
accept suffixes such as ``KiB`` and ``MiB``.
+``--vd-queue-enable=<yes|no>, --ad-queue-enable``
+ Enable running the video/audio decoder on a separate thread (default: no).
+ If enabled, the decoder is run on a separate thread, and a frame queue is
+ put between decoder and higher level playback logic. The size of the frame
+ queue is defined by the other options below.
+
+ This is probably quite pointless. libavcodec already has multithreaded
+ decoding (enabled by default), which makes this largely unnecessary. It
+ might help in some corner cases with high bandwidth video that is slow to
+ decode (in these cases libavcodec would block the playback logic, while
+ using a decoding thread would distribute the decoding time evenly without
+ affecting the playback logic). In other situations, it will simply make
+ seeking slower and use significantly more memory.
+
+ The queue size is restricted by the other ``--vd-queue-...`` options. The
+ final queue size is the minimum as indicated by the option with the lowest
+ limit. Each decoder/track has its own queue that may use the full configured
+ queue size.
+
+ Most queue options can be changed at runtime. ``--vd-queue-enable`` itself
+ (and the audio equivalent) update only if decoding is completely
+ reinitialized. However, setting ``--vd-queue-max-samples=1`` should almost
+ lead to the same behavior as ``--vd-queue-enable=no``, so that value can
+ be used for effectively runtime enabling/disabling the queue.
+
+ This should not be used with hardware decoding. It is possible to enable
+ this for audio, but it makes even less sense.
+
+``--vd-queue-max-bytes=<bytesize>``, ``--ad-queue-max-bytes``
+ Maximum approximate allowed size of the queue. If exceeded, decoding will
+ be stopped. The maximum size can be exceeded by about 1 frame.
+
+ See ``--list-options`` for defaults and value range. ``<bytesize>`` options
+ accept suffixes such as ``KiB`` and ``MiB``.
+
+``--vd-queue-max-samples=<int>``, ``--ad-queue-max-samples``
+ Maximum number of frames (video) or samples (audio) of the queue. The audio
+ size may be exceeded by about 1 frame.
+
+ See ``--list-options`` for defaults and value range.
+
+``--vd-queue-max-secs=<seconds>``, ``--ad-queue-max-secs``
+ Maximum number of seconds of media in the queue. The special value 0 means
+ no limit is set. The queue size may be exceeded by about 2 frames. Timestamp
+ resets may lead to random queue size usage.
+
+ See ``--list-options`` for defaults and value range.
+
Network
-------
@@ -4494,7 +4628,7 @@ Network
option is ignored (or should be ignored) on RTSP URLs. You can still
set the timeout option directly with ``--demuxer-lavf-o``.
-``--rtsp-transport=<lavf|udp|tcp|http>``
+``--rtsp-transport=<lavf|udp|udp_multicast|tcp|http>``
Select RTSP transport method (default: tcp). This selects the underlying
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
leaves the decision to libavformat.
@@ -4810,6 +4944,8 @@ The following video options are currently all specific to ``--vo=gpu`` and
better than without it) since it will extend the size to match only the
milder of the scale factors between the axes.
+ Note: this option is ignored when using bilinear downscaling (the default).
+
``--linear-downscaling``
Scale in linear light when downscaling. It should only be used with a
``--fbo-format`` that has at least 16 bit precision. This option
diff --git a/DOCS/man/osc.rst b/DOCS/man/osc.rst
index 4d1222abaa..e1b7e8aaf5 100644
--- a/DOCS/man/osc.rst
+++ b/DOCS/man/osc.rst
@@ -179,11 +179,12 @@ Configurable Options
``seekbarkeyframes``
Default: yes
- Controls the mode used to seek when dragging the seekbar. By default,
- keyframes are used. If set to false, exact seeking on mouse drags
- will be used instead. Keyframes are preferred, but exact seeks may be
- useful in cases where keyframes cannot be found. Note that using exact
- seeks can potentially make mouse dragging much slower.
+ Controls the mode used to seek when dragging the seekbar (default: true). If
+ set to true, default seeking mode is used (usually keyframes, but player
+ defaults and heuristics can change it to exact). If set to false, exact
+ seeking on mouse drags will be used instead. Keyframes are preferred, but
+ exact seeks may be useful in cases where keyframes cannot be found. Note
+ that using exact seeks can potentially make mouse dragging much slower.
``seekrangestyle``
Default: inverted
diff --git a/DOCS/tech-overview.txt b/DOCS/tech-overview.txt
index fba894606a..4bb06ff764 100644
--- a/DOCS/tech-overview.txt
+++ b/DOCS/tech-overview.txt
@@ -113,7 +113,7 @@ options/options.h, options/options.c
link them to the option parser. For example, an entry like this may be
typical:
- OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
+ {"", OPT_SUBSTRUCT(demux_opts, demux_conf)},
This directs the option access code to include all options in demux_conf
into the global option list, with no prefix (""), and as part of the
diff --git a/TOOLS/appveyor-build.sh b/TOOLS/appveyor-build.sh
index 8e16fbeee1..a6a7b724a3 100755
--- a/TOOLS/appveyor-build.sh
+++ b/TOOLS/appveyor-build.sh
@@ -16,7 +16,6 @@ export PYTHON=/usr/bin/python3
--enable-jpeg \
--enable-lcms2 \
--enable-libarchive \
- --enable-libass \
--enable-lua \
--enable-rubberband \
--enable-shaderc \
diff --git a/TOOLS/lua/osd-test.lua b/TOOLS/lua/osd-test.lua
new file mode 100644
index 0000000000..1b1781956d
--- /dev/null
+++ b/TOOLS/lua/osd-test.lua
@@ -0,0 +1,35 @@
+local assdraw = require 'mp.assdraw'
+local utils = require 'mp.utils'
+
+things = {}
+for i = 1, 2 do
+ things[i] = {
+ osd1 = mp.create_osd_overlay("ass-events"),
+ osd2 = mp.create_osd_overlay("ass-events")
+ }
+end
+things[1].text = "{\\an5}hello\\Nworld"
+things[2].text = "{\\pos(400, 200)}something something"
+
+mp.add_periodic_timer(2, function()
+ for i, thing in ipairs(things) do
+ thing.osd1.data = thing.text
+ thing.osd1.compute_bounds = true
+ --thing.osd1.hidden = true
+ local res = thing.osd1:update()
+ print("res " .. i .. ": " .. utils.to_string(res))
+
+ thing.osd2.hidden = true
+ if res ~= nil and res.x0 ~= nil then
+ local draw = assdraw.ass_new()
+ draw:append("{\\alpha&H80}")
+ draw:draw_start()
+ draw:pos(0, 0)
+ draw:rect_cw(res.x0, res.y0, res.x1, res.y1)
+ draw:draw_stop()
+ thing.osd2.hidden = false
+ thing.osd2.data = draw.text
+ end
+ thing.osd2:update()
+ end
+end)
diff --git a/TOOLS/lua/skip-logo.lua b/TOOLS/lua/skip-logo.lua
index 34cbff06f2..8e1f9da489 100644
--- a/TOOLS/lua/skip-logo.lua
+++ b/TOOLS/lua/skip-logo.lua
@@ -129,7 +129,6 @@ local function load_config()
setfenv(conf_fn, config)
end
else
- msg.warn("Lua 5.2 was not tested, this might go wrong.")
conf_fn, err = loadfile(conf_file, "t", config)
end
else
diff --git a/TOOLS/lua/test-hooks.lua b/TOOLS/lua/test-hooks.lua
new file mode 100644
index 0000000000..4e84d9e465
--- /dev/null
+++ b/TOOLS/lua/test-hooks.lua
@@ -0,0 +1,32 @@
+local utils = require("mp.utils")
+
+function hardsleep()
+ os.execute("sleep 1s")
+end
+
+local hooks = {"on_before_start_file", "on_load", "on_load_fail",
+ "on_preloaded", "on_unload", "on_after_end_file"}
+
+for _, name in ipairs(hooks) do
+ mp.add_hook(name, 0, function()
+ print("--- hook: " .. name)
+ hardsleep()
+ print(" ... continue")
+ end)
+end
+
+local events = {"start-file", "end-file", "file-loaded", "seek",
+ "playback-restart", "idle", "shutdown"}
+for _, name in ipairs(events) do
+ mp.register_event(name, function()
+ print("--- event: " .. name)
+ end)
+end
+
+local props = {"path", "metadata"}
+for _, name in ipairs(props) do
+ mp.observe_property(name, "native", function(name, val)
+ print("property '" .. name .. "' changed to '" ..
+ utils.to_string(val) .. "'")
+ end)
+end
diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py
index 0e447bc5ea..2c1b751a54 100755
--- a/TOOLS/matroska.py
+++ b/TOOLS/matroska.py
@@ -276,7 +276,7 @@ class MatroskaElement(object):
def add_subelements(self, subelements):
self.subelements = subelements
- self.subids = set(x[0].elid for x in subelements)
+ self.subids = {x[0].elid for x in subelements}
elementd = {}
elementlist = []
diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py
index 49a0a98e53..f164b2b3b1 100755
--- a/TOOLS/osxbundle.py
+++ b/TOOLS/osxbundle.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-
+from __future__ import print_function
import os
import shutil
import sys
@@ -37,7 +37,7 @@ def copy_binary(binary_name):
def apply_plist_template(plist_file, version):
for line in fileinput.input(plist_file, inplace=1):
- print (line.rstrip().replace('${VERSION}', version))
+ print(line.rstrip().replace('${VERSION}', version))
def create_bundle_symlink(binary_name, symlink_name):
os.symlink(os.path.basename(binary_name),
diff --git a/TOOLS/stats-conv.py b/TOOLS/stats-conv.py
index 5fc092e3c7..16d787ab96 100755
--- a/TOOLS/stats-conv.py
+++ b/TOOLS/stats-conv.py
@@ -147,8 +147,8 @@ if hasval:
ax[1] = win.addPlot()
ax[1].setXLink(ax[0])
for cur in ax:
- if cur is not None:
- cur.addLegend(offset = (-1, 1))
+ if cur is not None:
+ cur.addLegend(offset = (-1, 1))
for e in G.sevents:
cur = ax[1 if e.type == "value" else 0]
if not cur in G.curveno:
diff --git a/TOOLS/umpv b/TOOLS/umpv
index 0080b44ffb..762e73a622 100755
--- a/TOOLS/umpv
+++ b/TOOLS/umpv
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
This script emulates "unique application" functionality on Linux. When starting
@@ -19,10 +19,6 @@ will typically just fill ~/.xsession-errors with garbage.
mpv will terminate if there are no more files to play, and running the umpv
script after that will start a new mpv instance.
-Note that you can control the mpv instance by writing to the command fifo:
-
- echo "cycle fullscreen" > ~/.umpv_fifo
-
Note: you can supply custom mpv path and options with the MPV environment
variable. The environment variable will be split on whitespace, and the
first item is used as path to mpv binary and the rest is passed as options
@@ -32,6 +28,7 @@ Note: you can supply custom mpv path and options with the MPV environment
import sys
import os
+import socket
import errno
import subprocess
import fcntl
@@ -57,40 +54,34 @@ def make_abs(filename):
return filename
files = [make_abs(f) for f in files]
-FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
+SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket")
-fifo_fd = -1
+sock = None
try:
- fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY)
-except OSError as e:
- if e.errno == errno.ENXIO:
- pass # pipe has no writer
+ sock = socket.socket(socket.AF_UNIX)
+ sock.connect(SOCK)
+except socket.error as e:
+ if e.errno == errno.ECONNREFUSED:
+ sock = None
+ pass # abandoned socket
elif e.errno == errno.ENOENT:
+ sock = None
pass # doesn't exist
else:
raise e
-if fifo_fd >= 0:
+if sock:
# Unhandled race condition: what if mpv is terminating right now?
- fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
- fifo = os.fdopen(fifo_fd, "w")
for f in files:
# escape: \ \n "
f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
f = "\"" + f + "\""
- fifo.write("raw loadfile " + f + " append\n")
+ sock.send(("raw loadfile " + f + " append\n").encode("utf-8"))
else:
- # Recreate pipe if it doesn't already exist.
- # Also makes sure it's safe, and no other user can create a bogus pipe
- # that breaks security.
- try:
- os.unlink(FIFO)
- except OSError as e:
- pass
- os.mkfifo(FIFO, 0o600)
+ # Let mpv recreate socket if it doesn't already exist.
opts = (os.getenv("MPV") or "mpv").split()
- opts.extend(["--no-terminal", "--force-window", "--input-file=" + FIFO,
+ opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
"--"])
opts.extend(files)
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index 63f9aa0cbe..eaaf0729e9 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -63,16 +63,16 @@ struct ad_lavc_params {
const struct m_sub_options ad_lavc_conf = {
.opts = (const m_option_t[]) {
- OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 6),
- OPT_FLAG("downmix", downmix, 0),
- OPT_INTRANGE("threads", threads, 0, 0, 16),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"ac3drc", OPT_FLOAT(ac3drc), M_RANGE(0, 6)},
+ {"downmix", OPT_FLAG(downmix)},
+ {"threads", OPT_INT(threads), M_RANGE(0, 16)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
.size = sizeof(struct ad_lavc_params),
.defaults = &(const struct ad_lavc_params){
.ac3drc = 0,
- .downmix = 1,
+ .downmix = 0,
.threads = 1,
},
};
diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c
index 3e1eef664c..79d78d1d96 100644
--- a/audio/filter/af_format.c
+++ b/audio/filter/af_format.c
@@ -128,12 +128,12 @@ const struct mp_user_filter_entry af_format = {
.description = "Force audio format",
.priv_size = sizeof(struct f_opts),
.options = (const struct m_option[]) {
- OPT_AUDIOFORMAT("format", in_format, 0),
- OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
- OPT_CHANNELS("channels", in_channels, 0, .min = 1),
- OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
- OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
- OPT_FLAG("fail", fail, 0),
+ {"format", OPT_AUDIOFORMAT(in_format)},
+ {"srate", OPT_INT(in_srate), M_RANGE(1000, 8*48000)},
+ {"channels", OPT_CHANNELS(in_channels), .min = 1},
+ {"out-srate", OPT_INT(out_srate), M_RANGE(1000, 8*48000)},
+ {"out-channels", OPT_CHANNELS(out_channels), .min = 1},
+ {"fail", OPT_FLAG(fail)},
{0}
},
},
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index c7582cf52b..38f93a1c08 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -375,12 +375,12 @@ const struct mp_user_filter_entry af_lavcac3enc = {
.encoder = "ac3",
},
.options = (const struct m_option[]) {
- OPT_FLAG("tospdif", add_iec61937_header, 0),
- OPT_CHOICE_OR_INT("bitrate", bit_rate, 0, 32, 640,
- ({"auto", 0}, {"default", 0})),
- OPT_INTRANGE("minch", min_channel_num, 0, 2, 6),
- OPT_STRING("encoder", encoder, 0),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"tospdif", OPT_FLAG(add_iec61937_header)},
+ {"bitrate", OPT_CHOICE(bit_rate,
+ {"auto", 0}, {"default", 0}), M_RANGE(32, 640)},
+ {"minch", OPT_INT(min_channel_num), M_RANGE(2, 6)},
+ {"encoder", OPT_STRING(encoder)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
},
diff --git a/audio/filter/af_rubberband.c b/audio/filter/af_rubberband.c
index c7b6317c13..4df2001c49 100644
--- a/audio/filter/af_rubberband.c
+++ b/audio/filter/af_rubberband.c
@@ -333,35 +333,35 @@ const struct mp_user_filter_entry af_rubberband = {
.channels = RubberBandOptionChannelsTogether,
},
.options = (const struct m_option[]) {
- OPT_CHOICE("transients", transients, 0,
- ({"crisp", RubberBandOptionTransientsCrisp},
- {"mixed", RubberBandOptionTransientsMixed},
- {"smooth", RubberBandOptionTransientsSmooth})),
- OPT_CHOICE("detector", detector, 0,
- ({"compound", RubberBandOptionDetectorCompound},
- {"percussive", RubberBandOptionDetectorPercussive},
- {"soft", RubberBandOptionDetectorSoft})),
- OPT_CHOICE("phase", phase, 0,
- ({"laminar", RubberBandOptionPhaseLaminar},
- {"independent", RubberBandOptionPhaseIndependent})),
- OPT_CHOICE("window", window, 0,
- ({"standard", RubberBandOptionWindowStandard},
- {"short", RubberBandOptionWindowShort},
- {"long", RubberBandOptionWindowLong})),
- OPT_CHOICE("smoothing", smoothing, 0,
- ({"off", RubberBandOptionSmoothingOff},
- {"on", RubberBandOptionSmoothingOn})),
- OPT_CHOICE("formant", formant, 0,
- ({"shifted", RubberBandOptionFormantShifted},
- {"preserved", RubberBandOptionFormantPreserved})),
- OPT_CHOICE("pitch", pitch, 0,
- ({"quality", RubberBandOptionPitchHighQuality},
- {"speed", RubberBandOptionPitchHighSpeed},
- {"consistency", RubberBandOptionPitchHighConsistency})),
- OPT_CHOICE("channels", channels, 0,
- ({"apart", RubberBandOptionChannelsApart},
- {"together", RubberBandOptionChannelsTogether})),
- OPT_DOUBLE("pitch-scale", scale, M_OPT_RANGE, .min = 0.01, .max = 100),
+ {"transients", OPT_CHOICE(transients,
+ {"crisp", RubberBandOptionTransientsCrisp},
+ {"mixed", RubberBandOptionTransientsMixed},
+ {"smooth", RubberBandOptionTransientsSmooth})},
+ {"detector", OPT_CHOICE(detector,
+ {"compound", RubberBandOptionDetectorCompound},
+ {"percussive", RubberBandOptionDetectorPercussive},
+ {"soft", RubberBandOptionDetectorSoft})},
+ {"phase", OPT_CHOICE(phase,
+ {"laminar", RubberBandOptionPhaseLaminar},
+ {"independent", RubberBandOptionPhaseIndependent})},
+ {"window", OPT_CHOICE(window,
+ {"standard", RubberBandOptionWindowStandard},
+ {"short", RubberBandOptionWindowShort},
+ {"long", RubberBandOptionWindowLong})},
+ {"smoothing", OPT_CHOICE(smoothing,
+ {"off", RubberBandOptionSmoothingOff},
+ {"on", RubberBandOptionSmoothingOn})},
+ {"formant", OPT_CHOICE(formant,
+ {"shifted", RubberBandOptionFormantShifted},
+ {"preserved", RubberBandOptionFormantPreserved})},
+ {"pitch", OPT_CHOICE(pitch,
+ {"quality", RubberBandOptionPitchHighQuality},
+ {"speed", RubberBandOptionPitchHighSpeed},
+ {"consistency", RubberBandOptionPitchHighConsistency})},
+ {"channels", OPT_CHOICE(channels,
+ {"apart", RubberBandOptionChannelsApart},
+ {"together", RubberBandOptionChannelsTogether})},
+ {"pitch-scale", OPT_DOUBLE(scale), M_RANGE(0.01, 100)},
{0}
},
},
diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c
index ed1df5725e..911fd8914e 100644
--- a/audio/filter/af_scaletempo.c
+++ b/audio/filter/af_scaletempo.c
@@ -30,6 +30,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <float.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
@@ -608,15 +609,15 @@ const struct mp_user_filter_entry af_scaletempo = {
.scale_nominal = 1.0,
},
.options = (const struct m_option[]) {
- OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
- OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
- OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
- OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
- OPT_CHOICE("speed", speed_opt, 0,
- ({"pitch", SCALE_PITCH},
- {"tempo", SCALE_TEMPO},
- {"none", 0},
- {"both", SCALE_TEMPO | SCALE_PITCH})),
+ {"scale", OPT_FLOAT(scale_nominal), M_RANGE(0.01, DBL_MAX)},
+ {"stride", OPT_FLOAT(ms_stride), M_RANGE(0.01, DBL_MAX)},
+ {"overlap", OPT_FLOAT(percent_overlap), M_RANGE(0, 1)},
+ {"search", OPT_FLOAT(ms_search), M_RANGE(0, DBL_MAX)},
+ {"speed", OPT_CHOICE(speed_opt,
+ {"pitch", SCALE_PITCH},
+ {"tempo", SCALE_TEMPO},
+ {"none", 0},
+ {"both", SCALE_TEMPO | SCALE_PITCH})},
{0}
},
},
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 71c17e03b0..480dad69e0 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -29,19 +29,17 @@
#include "audio/format.h"
#include "options/options.h"
-#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "osdep/endian.h"
#include "common/msg.h"
#include "common/common.h"
#include "common/global.h"
-extern const struct ao_driver audio_out_oss;
extern const struct ao_driver audio_out_audiotrack;
extern const struct ao_driver audio_out_audiounit;
extern const struct ao_driver audio_out_coreaudio;
extern const struct ao_driver audio_out_coreaudio_exclusive;
extern const struct ao_driver audio_out_rsound;
-extern const struct ao_driver audio_out_sndio;
extern const struct ao_driver audio_out_pulse;
extern const struct ao_driver audio_out_jack;
extern const struct ao_driver audio_out_openal;
@@ -73,9 +71,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_WASAPI
&audio_out_wasapi,
#endif
-#if HAVE_OSS_AUDIO
- &audio_out_oss,
-#endif
// wrappers:
#if HAVE_JACK
&audio_out_jack,
@@ -89,18 +84,12 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_SDL2_AUDIO
&audio_out_sdl,
#endif
-#if HAVE_SNDIO
- &audio_out_sndio,
-#endif
&audio_out_null,
#if HAVE_COREAUDIO
&audio_out_coreaudio_exclusive,
#endif
&audio_out_pcm,
&audio_out_lavc,
-#if HAVE_RSOUND
- &audio_out_rsound,
-#endif
NULL
};
@@ -136,11 +125,12 @@ static const struct m_obj_list ao_obj_list = {
#define OPT_BASE_STRUCT struct ao_opts
const struct m_sub_options ao_conf = {
.opts = (const struct m_option[]) {
- OPT_SETTINGSLIST("ao", audio_driver_list, UPDATE_AUDIO, &ao_obj_list, ),
- OPT_STRING("audio-device", audio_device, UPDATE_AUDIO),
- OPT_STRING("audio-client-name", audio_client_name, UPDATE_AUDIO),
- OPT_DOUBLE("audio-buffer", audio_buffer,
- UPDATE_AUDIO | M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 10),
+ {"ao", OPT_SETTINGSLIST(audio_driver_list, &ao_obj_list),
+ .flags = UPDATE_AUDIO},
+ {"audio-device", OPT_STRING(audio_device), .flags = UPDATE_AUDIO},
+ {"audio-client-name", OPT_STRING(audio_client_name), .flags = UPDATE_AUDIO},
+ {"audio-buffer", OPT_DOUBLE(audio_buffer),
+ .flags = UPDATE_AUDIO, M_RANGE(0, 10)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index bb67fd84a8..b4fa18891b 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -67,14 +67,14 @@ struct ao_alsa_opts {
#define OPT_BASE_STRUCT struct ao_alsa_opts
static const struct m_sub_options ao_alsa_conf = {
.opts = (const struct m_option[]) {
- OPT_FLAG("alsa-resample", resample, 0),
- OPT_STRING("alsa-mixer-device", mixer_device, 0),
- OPT_STRING("alsa-mixer-name", mixer_name, 0),
- OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99),
- OPT_FLAG("alsa-non-interleaved", ni, 0),
- OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0),
- OPT_INTRANGE("alsa-buffer-time", buffer_time, 0, 0, INT_MAX),
- OPT_INTRANGE("alsa-periods", frags, 0, 0, INT_MAX),
+ {"alsa-resample", OPT_FLAG(resample)},
+ {"alsa-mixer-device", OPT_STRING(mixer_device)},
+ {"alsa-mixer-name", OPT_STRING(mixer_name)},
+ {"alsa-mixer-index", OPT_INT(mixer_index), M_RANGE(0, 99)},
+ {"alsa-non-interleaved", OPT_FLAG(ni)},
+ {"alsa-ignore-chmap", OPT_FLAG(ignore_chmap)},
+ {"alsa-buffer-time", OPT_INT(buffer_time), M_RANGE(0, INT_MAX)},
+ {"alsa-periods", OPT_INT(frags), M_RANGE(0, INT_MAX)},
{0}
},
.defaults = &(const struct ao_alsa_opts) {
diff --git a/audio/out/ao_audiotrack.c b/audio/out/ao_audiotrack.c
index f54f2584c0..b3be357c1b 100644
--- a/audio/out/ao_audiotrack.c
+++ b/audio/out/ao_audiotrack.c
@@ -709,8 +709,8 @@ const struct ao_driver audio_out_audiotrack = {
.resume = start,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
- OPT_FLAG("pcm-float", cfg_pcm_float, 0),
- OPT_INT("session-id", cfg_session_id, 0),
+ {"pcm-float", OPT_FLAG(cfg_pcm_float)},
+ {"session-id", OPT_INT(cfg_session_id)},
{0}
},
.options_prefix = "audiotrack",
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index 2e02c6842b..533d102d32 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -423,7 +423,7 @@ const struct ao_driver audio_out_coreaudio = {
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]){
- OPT_FLAG("change-physical-format", change_physical_format, 0),
+ {"change-physical-format", OPT_FLAG(change_physical_format)},
{0}
},
.options_prefix = "coreaudio",
diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c
index a5d4601384..19721e0951 100644
--- a/audio/out/ao_coreaudio_exclusive.c
+++ b/audio/out/ao_coreaudio_exclusive.c
@@ -465,7 +465,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.changed_mixing = false,
},
.options = (const struct m_option[]){
- OPT_FLAG("spdif-hack", spdif_hack, 0),
+ {"spdif-hack", OPT_FLAG(spdif_hack)},
{0}
},
.options_prefix = "coreaudio",
diff --git a/audio/out/ao_jack.c b/audio/out/ao_jack.c
index 0d5a2da207..249c314f9d 100644
--- a/audio/out/ao_jack.c
+++ b/audio/out/ao_jack.c
@@ -55,12 +55,12 @@ struct jack_opts {
#define OPT_BASE_STRUCT struct jack_opts
static const struct m_sub_options ao_jack_conf = {
.opts = (const struct m_option[]){
- OPT_STRING("jack-port", port, 0),
- OPT_STRING("jack-name", client_name, 0),
- OPT_FLAG("jack-autostart", autostart, 0),
- OPT_FLAG("jack-connect", connect, 0),
- OPT_CHOICE("jack-std-channel-layout", stdlayout, 0,
- ({"waveext", 0}, {"any", 1})),
+ {"jack-port", OPT_STRING(port)},
+ {"jack-name", OPT_STRING(client_name)},
+ {"jack-autostart", OPT_FLAG(autostart)},
+ {"jack-connect", OPT_FLAG(connect)},
+ {"jack-std-channel-layout", OPT_CHOICE(stdlayout,
+ {"waveext", 0}, {"any", 1})},
{0}
},
.defaults = &(const struct jack_opts) {
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index 0973d9f529..a38c01e0b2 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -87,6 +87,9 @@ static void select_format(struct ao *ao, const AVCodec *codec)
static void on_ready(void *ptr)
{
struct ao *ao = ptr;
+ struct priv *ac = ao->priv;
+
+ ac->worst_time_base = encoder_get_mux_timebase_unlocked(ac->enc);
ao_add_events(ao, AO_EVENT_INITIAL_UNBLOCK);
}
@@ -225,14 +228,16 @@ static void encode(struct ao *ao, double apts, void **data)
int64_t frame_pts = av_rescale_q(frame->pts, encoder->time_base,
ac->worst_time_base);
- if (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) {
- // this indicates broken video
- // (video pts failing to increase fast enough to match audio)
+ while (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) {
+ // whatever the fuck this code does?
MP_WARN(ao, "audio frame pts went backwards (%d <- %d), autofixed\n",
(int)frame->pts, (int)ac->lastpts);
frame_pts = ac->lastpts + 1;
+ ac->lastpts = frame_pts;
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base,
encoder->time_base);
+ frame_pts = av_rescale_q(frame->pts, encoder->time_base,
+ ac->worst_time_base);
}
ac->lastpts = frame_pts;
@@ -350,6 +355,7 @@ const struct ao_driver audio_out_lavc = {
.description = "audio encoding using libavcodec",
.name = "lavc",
.initially_blocked = true,
+ .reports_underruns = true, // not a thing
.priv_size = sizeof(struct priv),
.init = init,
.uninit = uninit,
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
index 3880ee8aa4..7e15b58e00 100644
--- a/audio/out/ao_null.c
+++ b/audio/out/ao_null.c
@@ -237,15 +237,15 @@ const struct ao_driver audio_out_null = {
.speed = 1,
},
.options = (const struct m_option[]) {
- OPT_FLAG("untimed", untimed, 0),
- OPT_FLOATRANGE("buffer", bufferlen, 0, 0, 100),
- OPT_INTRANGE("outburst", outburst, 0, 1, 100000),
- OPT_FLOATRANGE("speed", speed, 0, 0, 10000),
- OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
- OPT_FLAG("broken-eof", broken_eof, 0),
- OPT_FLAG("broken-delay", broken_delay, 0),
- OPT_CHANNELS("channel-layouts", channel_layouts, 0),
- OPT_AUDIOFORMAT("format", format, 0),
+ {"untimed", OPT_FLAG(untimed)},
+ {"buffer", OPT_FLOAT(bufferlen), M_RANGE(0, 100)},
+ {"outburst", OPT_INT(outburst), M_RANGE(1, 100000)},
+ {"speed", OPT_FLOAT(speed), M_RANGE(0, 10000)},
+ {"latency", OPT_FLOAT(latency_sec), M_RANGE(0, 100)},
+ {"broken-eof", OPT_FLAG(broken_eof)},
+ {"broken-delay", OPT_FLAG(broken_delay)},
+ {"channel-layouts", OPT_CHANNELS(channel_layouts)},
+ {"format", OPT_AUDIOFORMAT(format)},
{0}
},
.options_prefix = "ao-null",
diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c
index 2af9fadb4f..53fcaca05e 100644
--- a/audio/out/ao_openal.c
+++ b/audio/out/ao_openal.c
@@ -428,9 +428,9 @@ const struct ao_driver audio_out_openal = {
.direct_channels = 0,
},
.options = (const struct m_option[]) {
- OPT_INTRANGE("num-buffers", num_buffers, 0, 2, MAX_BUF),
- OPT_INTRANGE("num-samples", num_samples, 0, 256, MAX_SAMPLES),
- OPT_FLAG("direct-channels", direct_channels, 0),
+ {"num-buffers", OPT_INT(num_buffers), M_RANGE(2, MAX_BUF)},
+ {"num-samples", OPT_INT(num_samples), M_RANGE(256, MAX_SAMPLES)},
+ {"direct-channels", OPT_FLAG(direct_channels)},
{0}
},
.options_prefix = "openal",
diff --git a/audio/out/ao_opensles.c b/audio/out/ao_opensles.c
index 482a85afea..67ebd46aff 100644
--- a/audio/out/ao_opensles.c
+++ b/audio/out/ao_opensles.c
@@ -254,8 +254,10 @@ const struct ao_driver audio_out_opensles = {
.buffer_size_in_ms = 250,
},
.options = (const struct m_option[]) {
- OPT_INTRANGE("frames-per-enqueue", frames_per_enqueue, 0, 1, 96000),
- OPT_INTRANGE("buffer-size-in-ms", buffer_size_in_ms, 0, 0, 500),
+ {"frames-per-enqueue", OPT_INT(frames_per_enqueue),
+ M_RANGE(1, 96000)},
+ {"buffer-size-in-ms", OPT_INT(buffer_size_in_ms),
+ M_RANGE(0, 500)},
{0}
},
.options_prefix = "opensles",
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
deleted file mode 100644
index fbc535e413..0000000000
--- a/audio/out/ao_oss.c
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- * OSS audio output driver
- *
- * Original author: A'rpi
- * Support for >2 output channels added 2001-11-25
- * - Steve Davies <steve@daviesfam.org>
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <sys/ioctl.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <errno.h>
-#include <string.h>
-#include <strings.h>
-
-#include "config.h"
-#include "options/options.h"
-#include "common/common.h"
-#include "common/msg.h"
-#include "osdep/timer.h"
-#include "osdep/endian.h"
-
-#include <sys/soundcard.h>
-
-#include "audio/format.h"
-
-#include "ao.h"
-#include "internal.h"
-
-#if !HAVE_GPL
-#error GPL only
-#endif
-
-// Define to 0 if the device must be reopened to reset it (stop all playback,
-// clear the buffer), and the device should be closed when unused.
-// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
-#if defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__)
-#define KEEP_DEVICE 1
-#else
-#define KEEP_DEVICE 0
-#endif
-
-#define PATH_DEV_DSP "/dev/dsp"
-#define PATH_DEV_MIXER "/dev/mixer"
-
-struct priv {
- int audio_fd;
- int prepause_samples;
- int oss_mixer_channel;
- int audio_delay_method;
- int buffersize;
- int outburst;
- bool device_failed;
- double audio_end;
-
- char *oss_mixer_device;
- char *cfg_oss_mixer_channel;
-};
-
-static const char *const mixer_channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
-
-/* like alsa except for 6.1 and 7.1, from pcm/matrix_map.h */
-static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
- {0}, // empty
- MP_CHMAP_INIT_MONO, // mono
- MP_CHMAP2(FL, FR), // stereo
- MP_CHMAP3(FL, FR, LFE), // 2.1
- MP_CHMAP4(FL, FR, BL, BR), // 4.0
- MP_CHMAP5(FL, FR, BL, BR, FC), // 5.0
- MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1
- MP_CHMAP7(FL, FR, BL, BR, FC, LFE, BC), // 6.1
- MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
-};
-
-#if !defined(AFMT_S16_NE) && defined(AFMT_S16_LE) && defined(AFMT_S16_BE)
-#define AFMT_S16_NE MP_SELECT_LE_BE(AFMT_S16_LE, AFMT_S16_BE)
-#endif
-
-#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
-#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
-#endif
-
-static const int format_table[][2] = {
- {AFMT_U8, AF_FORMAT_U8},
- {AFMT_S16_NE, AF_FORMAT_S16},
-#ifdef AFMT_S32_NE
- {AFMT_S32_NE, AF_FORMAT_S32},
-#endif
-#ifdef AFMT_FLOAT
- {AFMT_FLOAT, AF_FORMAT_FLOAT},
-#endif
-#ifdef AFMT_MPEG
- {AFMT_MPEG, AF_FORMAT_S_MP3},
-#endif
- {-1, -1}
-};
-
-#ifndef AFMT_AC3
-#define AFMT_AC3 -1
-#endif
-
-static int format2oss(int format)
-{
- for (int n = 0; format_table[n][0] != -1; n++) {
- if (format_table[n][1] == format)
- return format_table[n][0];
- }
- return -1;
-}
-
-static int oss2format(int format)
-{
- for (int n = 0; format_table[n][0] != -1; n++) {
- if (format_table[n][0] == format)
- return format_table[n][1];
- }
- return 0;
-}
-
-
-#ifdef SNDCTL_DSP_GETPLAYVOL
-static int volume_oss4(struct ao *ao, ao_control_vol_t *vol, int cmd)
-{
- struct priv *p = ao->priv;
- int v;
-
- if (p->audio_fd < 0)
- return CONTROL_ERROR;
-
- if (cmd == AOCONTROL_GET_VOLUME) {
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1)
- return CONTROL_ERROR;
- vol->right = (v & 0xff00) >> 8;
- vol->left = v & 0x00ff;
- return CONTROL_OK;
- } else if (cmd == AOCONTROL_SET_VOLUME) {
- v = ((int) vol->right << 8) | (int) vol->left;
- if (ioctl(p->audio_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1)
- return CONTROL_ERROR;
- return CONTROL_OK;
- } else
- return CONTROL_UNKNOWN;
-}
-#endif
-
-// to set/get/query special features/parameters
-static int control(struct ao *ao, enum aocontrol cmd, void *arg)
-{
- struct priv *p = ao->priv;
- switch (cmd) {
- case AOCONTROL_GET_VOLUME:
- case AOCONTROL_SET_VOLUME: {
- ao_control_vol_t *vol = (ao_control_vol_t *)arg;
- int fd, v, devs;
-
-#ifdef SNDCTL_DSP_GETPLAYVOL
- // Try OSS4 first
- if (volume_oss4(ao, vol, cmd) == CONTROL_OK)
- return CONTROL_OK;
-#endif
-
- if (!af_fmt_is_pcm(ao->format))
- return CONTROL_TRUE;
-
- if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) {
- ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
- if (devs & (1 << p->oss_mixer_channel)) {
- if (cmd == AOCONTROL_GET_VOLUME) {
- ioctl(fd, MIXER_READ(p->oss_mixer_channel), &v);
- vol->right = (v & 0xFF00) >> 8;
- vol->left = v & 0x00FF;
- } else {
- v = ((int)vol->right << 8) | (int)vol->left;
- ioctl(fd, MIXER_WRITE(p->oss_mixer_channel), &v);
- }
- } else {
- close(fd);
- return CONTROL_ERROR;
- }
- close(fd);
- return CONTROL_OK;
- }
- return CONTROL_ERROR;
- }
- }
- return CONTROL_UNKNOWN;
-}
-
-// 1: ok, 0: not writable, -1: error
-static int device_writable(struct ao *ao)
-{
- struct priv *p = ao->priv;
- struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
- return poll(&fd, 1, 0);
-}
-
-static void close_device(struct ao *ao)
-{
- struct priv *p = ao->priv;
- p->device_failed = false;
- if (p->audio_fd == -1)
- return;
-#if defined(SNDCTL_DSP_RESET)
- ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
-#endif
- close(p->audio_fd);
- p->audio_fd = -1;
-}
-
-// close audio device
-static void uninit(struct ao *ao)
-{
- close_device(ao);
-}
-
-static bool try_format(struct ao *ao, int *format)
-{
- struct priv *p = ao->priv;
-
- int oss_format = format2oss(*format);
- if (oss_format == -1 && af_fmt_is_spdif(*format))
- oss_format = AFMT_AC3;
-
- if (oss_format == -1) {
- MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
- af_fmt_to_str(*format));
- *format = 0;
- return false;
- }
-
- int actual_format = oss_format;
- if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &actual_format) < 0)
- actual_format = -1;
-
- if (actual_format == oss_format)
- return true;
-
- MP_WARN(ao, "Can't set audio device to %s output.\n", af_fmt_to_str(*format));
- *format = oss2format(actual_format);
- if (actual_format != -1 && !*format)
- MP_ERR(ao, "Unknown/Unsupported OSS format: 0x%x.\n", actual_format);
- return false;
-}
-
-static int reopen_device(struct ao *ao, bool allow_format_changes)
-{
- struct priv *p = ao->priv;
-
- int samplerate = ao->samplerate;
- int format = ao->format;
- struct mp_chmap channels = ao->channels;
-
- const char *device = PATH_DEV_DSP;
- if (ao->device)
- device = ao->device;
-
- MP_VERBOSE(ao, "using '%s' dsp device\n", device);
-#ifdef __linux__
- p->audio_fd = open(device, O_WRONLY | O_NONBLOCK);
-#else
- p->audio_fd = open(device, O_WRONLY);
-#endif
- if (p->audio_fd < 0) {
- MP_ERR(ao, "Can't open audio device %s: %s\n",
- device, mp_strerror(errno));
- goto fail;
- }
-
-#ifdef __linux__
- /* Remove the non-blocking flag */
- if (fcntl(p->audio_fd, F_SETFL, 0) < 0) {
- MP_ERR(ao, "Can't make file descriptor blocking: %s\n",
- mp_strerror(errno));
- goto fail;
- }
-#endif
-
-#if defined(FD_CLOEXEC) && defined(F_SETFD)
- fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
-#endif
-
- if (af_fmt_is_spdif(format)) {
- if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
- goto fail;
- // Probably could be fixed by setting number of channels; needs testing.
- if (channels.num != 2) {
- MP_ERR(ao, "Format %s not implemented.\n", af_fmt_to_str(format));
- goto fail;
- }
- }
-
- int try_formats[AF_FORMAT_COUNT + 1];
- af_get_best_sample_formats(format, try_formats);
- for (int n = 0; try_formats[n]; n++) {
- format = try_formats[n];
- if (try_format(ao, &format))
- break;
- }
-
- if (!format) {
- MP_ERR(ao, "Can't set sample format.\n");
- goto fail;
- }
-
- MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format));
-
- if (!af_fmt_is_spdif(format)) {
- struct mp_chmap_sel sel = {0};
- for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
- mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
- if (!ao_chmap_sel_adjust(ao, &sel, &channels))
- goto fail;
- int c, nchannels, reqchannels;
- nchannels = reqchannels = channels.num;
- // We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
- if (reqchannels > 2) {
- if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) {
- MP_ERR(ao, "Failed to set audio device to %d channels.\n",
- reqchannels);
- goto fail;
- }
- if (nchannels != reqchannels) {
- // Fallback to stereo
- nchannels = 2;
- goto stereo;
- }
- } else {
-stereo:
- c = nchannels - 1;
- if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
- MP_ERR(ao, "Failed to set audio device to %d channels.\n",
- reqchannels);
- goto fail;
- }
- if (!ao_chmap_sel_get_def(ao, &sel, &channels, c + 1))
- goto fail;
- }
- MP_VERBOSE(ao, "using %d channels (requested: %d)\n",
- channels.num, reqchannels);
- // set rate
- if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
- goto fail;
- MP_VERBOSE(ao, "using %d Hz samplerate\n", samplerate);
- }
-
- audio_buf_info zz = {0};
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) == -1) {
- int r = 0;
- MP_WARN(ao, "driver doesn't support SNDCTL_DSP_GETOSPACE\n");
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1)
- MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst);
- else {
- p->outburst = r;
- MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst);
- }
- } else {
- MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n",
- zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes);
- p->buffersize = zz.bytes;
- p->outburst = zz.fragsize;
- }
-
- if (allow_format_changes) {
- ao->format = format;
- ao->samplerate = samplerate;
- ao->channels = channels;
- } else {
- if (format != ao->format || samplerate != ao->samplerate ||
- !mp_chmap_equals(&channels, &ao->channels))
- {
- MP_ERR(ao, "Could not reselect previous audio format.\n");
- goto fail;
- }
- }
-
- int sstride = channels.num * af_fmt_to_bytes(format);
- p->outburst -= p->outburst % sstride; // round down
- ao->period_size = p->outburst / sstride;
-
- return 0;
-
-fail:
- close_device(ao);
- return -1;
-}
-
-// open & setup audio device
-// return: 0=success -1=fail
-static int init(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- const char *mchan = NULL;
- if (p->cfg_oss_mixer_channel && p->cfg_oss_mixer_channel[0])
- mchan = p->cfg_oss_mixer_channel;
-
- if (mchan) {
- int fd, devs, i;
-
- if ((fd = open(p->oss_mixer_device, O_RDONLY)) == -1) {
- MP_ERR(ao, "Can't open mixer device %s: %s\n",
- p->oss_mixer_device, mp_strerror(errno));
- } else {
- ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
- close(fd);
-
- for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
- if (!strcasecmp(mixer_channels[i], mchan)) {
- if (!(devs & (1 << i))) {
- MP_ERR(ao, "Audio card mixer does not have "
- "channel '%s', using default.\n", mchan);
- i = SOUND_MIXER_NRDEVICES + 1;
- break;
- }
- p->oss_mixer_channel = i;
- break;
- }
- }
- if (i == SOUND_MIXER_NRDEVICES) {
- MP_ERR(ao, "Audio card mixer does not have "
- "channel '%s', using default.\n", mchan);
- }
- }
- } else {
- p->oss_mixer_channel = SOUND_MIXER_PCM;
- }
-
- MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
- MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]);
-
- ao->format = af_fmt_from_planar(ao->format);
-
- if (reopen_device(ao, true) < 0)
- goto fail;
-
- if (p->buffersize == -1) {
- // Measuring buffer size:
- void *data = malloc(p->outburst);
- if (!data) {
- MP_ERR(ao, "Out of memory, or broken outburst size.\n");
- goto fail;
- }
- p->buffersize = 0;
- memset(data, 0, p->outburst);
- while (p->buffersize < 0x40000 && device_writable(ao) > 0) {
- (void)write(p->audio_fd, data, p->outburst);
- p->buffersize += p->outburst;
- }
- free(data);
- if (p->buffersize == 0) {
- MP_ERR(ao, "Your OSS audio driver DOES NOT support poll().\n");
- goto fail;
- }
- }
-
- return 0;
-
-fail:
- uninit(ao);
- return -1;
-}
-
-static void drain(struct ao *ao)
-{
-#ifdef SNDCTL_DSP_SYNC
- struct priv *p = ao->priv;
- // to get the buffer played
- if (p->audio_fd != -1)
- ioctl(p->audio_fd, SNDCTL_DSP_SYNC, NULL);
-#endif
-}
-
-// stop playing and empty buffers (for seeking/pause)
-static void reset(struct ao *ao)
-{
-#if KEEP_DEVICE
- struct priv *p = ao->priv;
- ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
-#else
- close_device(ao);
-#endif
-}
-
-// plays 'len' samples of 'data'
-// it should round it down to outburst*n
-// return: number of samples played
-static int play(struct ao *ao, void **data, int samples, int flags)
-{
- struct priv *p = ao->priv;
- int len = samples * ao->sstride;
- if (len == 0)
- return len;
-
- if (p->audio_fd < 0 && !p->device_failed && reopen_device(ao, false) < 0)
- MP_ERR(ao, "Fatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE ***\n");
- if (p->audio_fd < 0) {
- // Let playback continue normally, even with a closed device.
- p->device_failed = true;
- double now = mp_time_sec();
- if (p->audio_end < now)
- p->audio_end = now;
- p->audio_end += samples / (double)ao->samplerate;
- return samples;
- }
-
- if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
- len /= p->outburst;
- len *= p->outburst;
- }
- len = write(p->audio_fd, data[0], len);
- return len / ao->sstride;
-}
-
-// return: delay in seconds between first and last sample in buffer
-static double get_delay(struct ao *ao)
-{
- struct priv *p = ao->priv;
- if (p->audio_fd < 0) {
- double rest = p->audio_end - mp_time_sec();
- if (rest > 0)
- return rest;
- return 0;
- }
- /* Calculate how many bytes/second is sent out */
- if (p->audio_delay_method == 2) {
-#ifdef SNDCTL_DSP_GETODELAY
- int r = 0;
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETODELAY, &r) != -1)
- return r / (double)ao->bps;
-#endif
- p->audio_delay_method = 1; // fallback if not supported
- }
- if (p->audio_delay_method == 1) {
- audio_buf_info zz = {0};
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
- return (p->buffersize - zz.bytes) / (double)ao->bps;
- }
- p->audio_delay_method = 0; // fallback if not supported
- }
- return p->buffersize / (double)ao->bps;
-}
-
-
-// return: how many samples can be played without blocking
-static int get_space(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- audio_buf_info zz = {0};
- if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
- // calculate exact buffer space:
- return zz.fragments * zz.fragsize / ao->sstride;
- }
-
- if (p->audio_fd < 0 && p->device_failed && get_delay(ao) > 0.2)
- return 0;
-
- if (p->audio_fd < 0 || device_writable(ao) > 0)
- return p->outburst / ao->sstride;
-
- return 0;
-}
-
-// stop playing, keep buffers (for pause)
-static void audio_pause(struct ao *ao)
-{
- struct priv *p = ao->priv;
- p->prepause_samples = get_delay(ao) * ao->samplerate;
-#if KEEP_DEVICE
- ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
-#else
- close_device(ao);
-#endif
-}
-
-// resume playing, after audio_pause()
-static void audio_resume(struct ao *ao)
-{
- struct priv *p = ao->priv;
- p->audio_end = 0;
- if (p->prepause_samples > 0)
- ao_play_silence(ao, p->prepause_samples);
-}
-
-static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
-{
- struct priv *p = ao->priv;
-
- struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
- int r = ao_wait_poll(ao, &fd, 1, lock);
- if (fd.revents & (POLLERR | POLLNVAL))
- return -1;
- return r;
-}
-
-static void list_devs(struct ao *ao, struct ao_device_list *list)
-{
- if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
- ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
-}
-
-#define OPT_BASE_STRUCT struct priv
-
-const struct ao_driver audio_out_oss = {
- .description = "OSS/ioctl audio output",
- .name = "oss",
- .init = init,
- .uninit = uninit,
- .control = control,
- .get_space = get_space,
- .play = play,
- .get_delay = get_delay,
- .pause = audio_pause,
- .resume = audio_resume,
- .reset = reset,
- .drain = drain,
- .wait = audio_wait,
- .wakeup = ao_wakeup_poll,
- .list_devs = list_devs,
- .priv_size = sizeof(struct priv),
- .priv_defaults = &(const struct priv) {
- .audio_fd = -1,
- .audio_delay_method = 2,
- .buffersize = -1,
- .outburst = 512,
- .oss_mixer_channel = SOUND_MIXER_PCM,
- .oss_mixer_device = PATH_DEV_MIXER,
- },
- .options = (const struct m_option[]) {
- OPT_STRING("mixer-device", oss_mixer_device, M_OPT_FILE),
- OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
- {0}
- },
- .options_prefix = "oss",
-};
diff --git a/audio/out/ao_pcm.c b/audio/out/ao_pcm.c
index 689d5c019a..7d2656be49 100644
--- a/audio/out/ao_pcm.c
+++ b/audio/out/ao_pcm.c
@@ -111,9 +111,11 @@ static int init(struct ao *ao)
{
struct priv *priv = ao->priv;
- if (!priv->outputfilename)
- priv->outputfilename =
- talloc_strdup(priv, priv->waveheader ? "audiodump.wav" : "audiodump.pcm");
+ char *outputfilename = priv->outputfilename;
+ if (!outputfilename) {
+ outputfilename = talloc_strdup(priv, priv->waveheader ? "audiodump.wav"
+ : "audiodump.pcm");
+ }
ao->format = af_fmt_from_planar(ao->format);
@@ -148,13 +150,13 @@ static int init(struct ao *ao)
ao->bps = ao->channels.num * ao->samplerate * af_fmt_to_bytes(ao->format);
MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n",
- priv->outputfilename,
+ outputfilename,
priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
ao->channels.num, af_fmt_to_str(ao->format));
- priv->fp = fopen(priv->outputfilename, priv->append ? "ab" : "wb");
+ priv->fp = fopen(outputfilename, priv->append ? "ab" : "wb");
if (!priv->fp) {
- MP_ERR(ao, "Failed to open %s for writing!\n", priv->outputfilename);
+ MP_ERR(ao, "Failed to open %s for writing!\n", outputfilename);
return -1;
}
if (priv->waveheader) // Reserve space for wave header
@@ -219,9 +221,9 @@ const struct ao_driver audio_out_pcm = {
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) { .waveheader = 1 },
.options = (const struct m_option[]) {
- OPT_STRING("file", outputfilename, M_OPT_FILE),
- OPT_FLAG("waveheader", waveheader, 0),
- OPT_FLAG("append", append, 0),
+ {"file", OPT_STRING(outputfilename), .flags = M_OPT_FILE},
+ {"waveheader", OPT_FLAG(waveheader)},
+ {"append", OPT_FLAG(append)},
{0}
},
.options_prefix = "ao-pcm",
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index 5b4ced6f11..fc5fb0caeb 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -837,10 +837,11 @@ const struct ao_driver audio_out_pulse = {
.cfg_buffer = 100,
},
.options = (const struct m_option[]) {
- OPT_STRING("host", cfg_host, 0),
- OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
- OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
- OPT_FLAG("allow-suspended", cfg_allow_suspended, 0),
+ {"host", OPT_STRING(cfg_host)},
+ {"buffer", OPT_CHOICE(cfg_buffer, {"native", 0}),
+ M_RANGE(1, 2000)},
+ {"latency-hacks", OPT_FLAG(cfg_latency_hacks)},
+ {"allow-suspended", OPT_FLAG(cfg_allow_suspended)},
{0}
},
.options_prefix = "pulse",
diff --git a/audio/out/ao_rsound.c b/audio/out/ao_rsound.c
deleted file mode 100644
index 6cfd60d4c9..0000000000
--- a/audio/out/ao_rsound.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * RSound audio output driver
- *
- * Copyright (C) 2011 Hans-Kristian Arntzen
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * mpv is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <rsound.h>
-
-#include "mpv_talloc.h"
-
-#include "options/m_option.h"
-#include "osdep/timer.h"
-#include "audio/format.h"
-#include "ao.h"
-#include "internal.h"
-
-struct priv {
- rsound_t *rd;
-};
-
-static int set_format(struct ao *ao)
-{
- int rsd_format;
-
- switch (ao->format) {
- case AF_FORMAT_U8:
- rsd_format = RSD_U8;
- break;
- case AF_FORMAT_S32:
- rsd_format = RSD_S32_NE;
- break;
- default:
- rsd_format = RSD_S16_NE;
- ao->format = AF_FORMAT_S16;
- }
-
- return rsd_format;
-}
-
-static int init(struct ao *ao)
-{
- struct priv *priv = ao->priv;
-
- if (rsd_init(&priv->rd) < 0)
- return -1;
-
- if (ao->device)
- rsd_set_param(priv->rd, RSD_HOST, ao->device);
-
- // Actual channel layout unknown.
- struct mp_chmap_sel sel = {0};
- mp_chmap_sel_add_waveext_def(&sel);
- if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) {
- rsd_free(priv->rd);
- return -1;
- }
-
- rsd_set_param(priv->rd, RSD_SAMPLERATE, (int[]) { ao->samplerate });
- rsd_set_param(priv->rd, RSD_CHANNELS, (int[]) { ao->channels.num });
-
- ao->format = af_fmt_from_planar(ao->format);
-
- int rsd_format = set_format(ao);
- rsd_set_param(priv->rd, RSD_FORMAT, &rsd_format);
-
- if (rsd_start(priv->rd) < 0) {
- rsd_free(priv->rd);
- return -1;
- }
-
- return 0;
-}
-
-static void uninit(struct ao *ao)
-{
- struct priv *priv = ao->priv;
-
- rsd_stop(priv->rd);
- rsd_free(priv->rd);
-}
-
-static void reset(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- rsd_stop(priv->rd);
- rsd_start(priv->rd);
-}
-
-static void audio_pause(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- rsd_pause(priv->rd, 1);
-}
-
-static void audio_resume(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- rsd_pause(priv->rd, 0);
-}
-
-static int get_space(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- return rsd_get_avail(priv->rd) / ao->sstride;
-}
-
-static int play(struct ao *ao, void **data, int samples, int flags)
-{
- struct priv *priv = ao->priv;
- return rsd_write(priv->rd, data[0], samples * ao->sstride) / ao->sstride;
-}
-
-static double get_delay(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- return rsd_delay_ms(priv->rd) / 1000.0;
-}
-
-#define OPT_BASE_STRUCT struct priv
-
-const struct ao_driver audio_out_rsound = {
- .description = "RSound output driver",
- .name = "rsound",
- .init = init,
- .uninit = uninit,
- .reset = reset,
- .get_space = get_space,
- .play = play,
- .get_delay = get_delay,
- .pause = audio_pause,
- .resume = audio_resume,
- .priv_size = sizeof(struct priv),
-};
-
diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c
index 6144918dfe..c1c09b8c92 100644
--- a/audio/out/ao_sdl.c
+++ b/audio/out/ao_sdl.c
@@ -210,7 +210,7 @@ const struct ao_driver audio_out_sdl = {
.buflen = 0, // use SDL default
},
.options = (const struct m_option[]) {
- OPT_FLOAT("buflen", buflen, 0),
+ {"buflen", OPT_FLOAT(buflen)},
{0}
},
.options_prefix = "sdl",
diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c
deleted file mode 100644
index e5938b7d26..0000000000
--- a/audio/out/ao_sndio.c
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
- * Copyright (c) 2013 Christian Neukirchen <chneukirchen@gmail.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "config.h"
-
-#include <sys/types.h>
-#include <poll.h>
-#include <errno.h>
-#include <sndio.h>
-
-#include "options/m_option.h"
-#include "common/msg.h"
-
-#include "audio/format.h"
-#include "ao.h"
-#include "internal.h"
-
-struct priv {
- struct sio_hdl *hdl;
- struct sio_par par;
- int delay;
- bool playing;
- int vol;
- int havevol;
-#define SILENCE_NMAX 0x1000
- char silence[SILENCE_NMAX];
- struct pollfd *pfd;
- char *dev;
-};
-
-/*
- * misc parameters (volume, etc...)
- */
-static int control(struct ao *ao, enum aocontrol cmd, void *arg)
-{
- struct priv *p = ao->priv;
- ao_control_vol_t *vol = arg;
-
- switch (cmd) {
- case AOCONTROL_GET_VOLUME:
- if (!p->havevol)
- return CONTROL_FALSE;
- vol->left = vol->right = p->vol * 100 / SIO_MAXVOL;
- break;
- case AOCONTROL_SET_VOLUME:
- if (!p->havevol)
- return CONTROL_FALSE;
- sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
- break;
- default:
- return CONTROL_UNKNOWN;
- }
- return CONTROL_OK;
-}
-
-/*
- * call-back invoked to notify of the hardware position
- */
-static void movecb(void *addr, int delta)
-{
- struct priv *p = addr;
- p->delay -= delta;
-}
-
-/*
- * call-back invoked to notify about volume changes
- */
-static void volcb(void *addr, unsigned newvol)
-{
- struct priv *p = addr;
- p->vol = newvol;
-}
-
-static const struct mp_chmap sndio_layouts[MP_NUM_CHANNELS + 1] = {
- {0}, // empty
- {1, {MP_SPEAKER_ID_FL}}, // mono
- MP_CHMAP2(FL, FR), // stereo
- {0}, // 2.1
- MP_CHMAP4(FL, FR, BL, BR), // 4.0
- {0}, // 5.0
- MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1
- {0}, // 6.1
- MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
- /* above is the fixed channel assignment for sndio, since we need to fill
- all channels and cannot insert silence, not all layouts are supported.
- NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */
-};
-
-/*
- * open device and setup parameters
- * return: 0=success -1=fail
- */
-static int init(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- struct af_to_par {
- int format, bits, sig;
- };
- static const struct af_to_par af_to_par[] = {
- {AF_FORMAT_U8, 8, 0},
- {AF_FORMAT_S16, 16, 1},
- {AF_FORMAT_S32, 32, 1},
- };
- const struct af_to_par *ap;
- int i;
-
- p->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
- if (p->hdl == NULL) {
- MP_ERR(ao, "can't open sndio %s\n", SIO_DEVANY);
- goto error;
- }
-
- ao->format = af_fmt_from_planar(ao->format);
-
- sio_initpar(&p->par);
- for (i = 0, ap = af_to_par;; i++, ap++) {
- if (i == sizeof(af_to_par) / sizeof(struct af_to_par)) {
- MP_VERBOSE(ao, "unsupported format\n");
- p->par.bits = 16;
- p->par.sig = 1;
- p->par.le = SIO_LE_NATIVE;
- break;
- }
- if (ap->format == ao->format) {
- p->par.bits = ap->bits;
- p->par.sig = ap->sig;
- if (ap->bits > 8)
- p->par.le = SIO_LE_NATIVE;
- if (ap->bits != SIO_BPS(ap->bits))
- p->par.bps = ap->bits / 8;
- break;
- }
- }
- p->par.rate = ao->samplerate;
-
- struct mp_chmap_sel sel = {0};
- for (int n = 0; n < MP_NUM_CHANNELS+1; n++)
- mp_chmap_sel_add_map(&sel, &sndio_layouts[n]);
-
- if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
- goto error;
-
- p->par.pchan = ao->channels.num;
- p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */
- p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */
- if (!sio_setpar(p->hdl, &p->par)) {
- MP_ERR(ao, "couldn't set params\n");
- goto error;
- }
- if (!sio_getpar(p->hdl, &p->par)) {
- MP_ERR(ao, "couldn't get params\n");
- goto error;
- }
- if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
- MP_ERR(ao, "swapped endian output not supported\n");
- goto error;
- }
- if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) {
- ao->format = AF_FORMAT_U8;
- } else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) {
- ao->format = AF_FORMAT_S16;
- } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4 && p->par.sig) {
- ao->format = AF_FORMAT_S32;
- } else {
- MP_ERR(ao, "couldn't set format\n");
- goto error;
- }
-
- p->havevol = sio_onvol(p->hdl, volcb, p);
- sio_onmove(p->hdl, movecb, p);
- if (!sio_start(p->hdl))
- MP_ERR(ao, "init: couldn't start\n");
-
- p->pfd = calloc (sio_nfds(p->hdl), sizeof (struct pollfd));
- if (!p->pfd)
- goto error;
-
- ao->period_size = p->par.round;
-
- return 0;
-
-error:
- if (p->hdl)
- sio_close(p->hdl);
-
- return -1;
-}
-
-/*
- * close device
- */
-static void uninit(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- if (p->hdl)
- sio_close(p->hdl);
-
- free(p->pfd);
-}
-
-/*
- * stop playing and empty buffers (for seeking/pause)
- */
-static void reset(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- if (p->playing) {
- MP_WARN(ao, "Blocking until remaining audio is played... (sndio design bug).\n");
-
- p->playing = false;
-
- if (!sio_stop(p->hdl))
- MP_ERR(ao, "reset: couldn't stop\n");
- p->delay = 0;
- if (!sio_start(p->hdl))
- MP_ERR(ao, "reset: couldn't start\n");
- }
-}
-
-/*
- * play given number of samples until sio_write() blocks
- */
-static int play(struct ao *ao, void **data, int samples, int flags)
-{
- struct priv *p = ao->priv;
- int n;
-
- n = sio_write(p->hdl, data[0], samples * ao->sstride) / ao->sstride;
- p->delay += n;
- p->playing = true;
- /* on AOPLAY_FINAL_CHUNK, just let it underrun */
- return n;
-}
-
-/*
- * make libsndio call movecb()
- */
-static void update(struct ao *ao)
-{
- struct priv *p = ao->priv;
- int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
- while (poll(p->pfd, n, 0) < 0 && errno == EINTR) {}
- sio_revents(p->hdl, p->pfd);
-}
-
-/*
- * how many samples can be played without blocking
- */
-static int get_space(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- update(ao);
-
- int samples = p->par.bufsz - p->delay;
- return samples / p->par.round * p->par.round;
-}
-
-/*
- * return: delay in seconds between first and last sample in buffer
- */
-static double get_delay(struct ao *ao)
-{
- struct priv *p = ao->priv;
-
- update(ao);
-
- return p->delay / (double)p->par.rate;
-}
-
-/*
- * stop playing, keep buffers (for pause)
- */
-static void audio_pause(struct ao *ao)
-{
- reset(ao);
-}
-
-/*
- * resume playing, after audio_pause()
- */
-static void audio_resume(struct ao *ao)
-{
- return;
-}
-
-#define OPT_BASE_STRUCT struct priv
-
-const struct ao_driver audio_out_sndio = {
- .description = "sndio audio output",
- .name = "sndio",
- .init = init,
- .uninit = uninit,
- .control = control,
- .get_space = get_space,
- .play = play,
- .get_delay = get_delay,
- .pause = audio_pause,
- .resume = audio_resume,
- .reset = reset,
- .priv_size = sizeof(struct priv),
-};
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index df844aad7f..a696040dba 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -299,8 +299,9 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
}
#define mp_format_res_str(hres) \
- (SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
- ? "unsupported" : mp_HRESULT_to_str(hres))
+ (SUCCEEDED(hres) ? ((hres) == S_OK) ? "ok" : "close" \
+ : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
+ ? "unsupported" : mp_HRESULT_to_str(hres))
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
{
@@ -426,32 +427,57 @@ static bool find_formats_shared(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
{
struct wasapi_state *state = ao->priv;
- WAVEFORMATEX *closestMatch;
- HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
- AUDCLNT_SHAREMODE_SHARED,
- &wformat->Format,
- &closestMatch);
+ struct mp_chmap channels;
+ if (!chmap_from_waveformat(&channels, &wformat->Format)) {
+ MP_ERR(ao, "Error converting channel map\n");
+ return false;
+ }
+
+ HRESULT hr;
+ WAVEFORMATEX *mix_format;
+ hr = IAudioClient_GetMixFormat(state->pAudioClient, &mix_format);
+ EXIT_ON_ERROR(hr);
+
+ // WASAPI doesn't do any sample rate conversion on its own and
+ // will typically only accept the mix format samplerate. Although
+ // it will accept any PCM sample format, everything gets converted
+ // to the mix format anyway (pretty much always float32), so just
+ // use that.
+ WAVEFORMATEXTENSIBLE try_format;
+ waveformat_copy(&try_format, mix_format);
+ CoTaskMemFree(mix_format);
+
+ // WASAPI may accept channel maps other than the mix format
+ // if a surround emulator is enabled.
+ change_waveformat_channels(&try_format, &channels);
+
+ hr = IAudioClient_IsFormatSupported(state->pAudioClient,
+ AUDCLNT_SHAREMODE_SHARED,
+ &try_format.Format,
+ &mix_format);
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
- waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
+ waveformat_to_str(&try_format.Format), mp_format_res_str(hr));
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
EXIT_ON_ERROR(hr);
switch (hr) {
case S_OK:
+ waveformat_copy(wformat, &try_format.Format);
break;
case S_FALSE:
- waveformat_copy(wformat, closestMatch);
- CoTaskMemFree(closestMatch);
+ waveformat_copy(wformat, mix_format);
+ CoTaskMemFree(mix_format);
MP_VERBOSE(ao, "Closest match is %s\n",
waveformat_to_str(&wformat->Format));
break;
default:
- hr = IAudioClient_GetMixFormat(state->pAudioClient, &closestMatch);
+ hr = IAudioClient_GetMixFormat(state->pAudioClient, &mix_format);
EXIT_ON_ERROR(hr);
- waveformat_copy(wformat, closestMatch);
+ waveformat_copy(wformat, mix_format);
+ CoTaskMemFree(mix_format);
MP_VERBOSE(ao, "Fallback to mix format %s\n",
waveformat_to_str(&wformat->Format));
- CoTaskMemFree(closestMatch);
+
}
return true;
diff --git a/ci/build-macos.sh b/ci/build-macos.sh
index 7b6eda7356..347a75160f 100755
--- a/ci/build-macos.sh
+++ b/ci/build-macos.sh
@@ -18,7 +18,7 @@ PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" pytho
./waf configure \
--variant="${MPV_VARIANT}" \
--prefix="${MPV_INSTALL_PREFIX}" \
- --enable-{gl,iconv,lcms2,libass,libass-osd,libmpv-shared,lua,jpeg,plain-gl,zlib} \
+ --enable-{gl,iconv,lcms2,libmpv-shared,lua,jpeg,plain-gl,zlib} \
--enable-{cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}
python3 ./waf build --variant="${MPV_VARIANT}" -j4
diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh
index ed4eb9fb64..e0e8091905 100755
--- a/ci/build-mingw64.sh
+++ b/ci/build-mingw64.sh
@@ -22,7 +22,6 @@ python3 ./waf configure \
--enable-lua \
--enable-javascript \
--enable-libarchive \
- --enable-libass \
--enable-libbluray \
--enable-dvdnav \
--enable-uchardet \
diff --git a/ci/build-tumbleweed.sh b/ci/build-tumbleweed.sh
index 0ef468b9ef..9c0c2851a6 100755
--- a/ci/build-tumbleweed.sh
+++ b/ci/build-tumbleweed.sh
@@ -7,7 +7,6 @@ python3 ./waf configure \
--enable-dvdnav \
--enable-libarchive \
--enable-libmpv-shared \
- --enable-libsmbclient \
--enable-manpage-build \
--enable-shaderc \
--enable-vulkan
diff --git a/common/av_log.c b/common/av_log.c
index d7df24e05c..8122d3a65b 100644
--- a/common/av_log.c
+++ b/common/av_log.c
@@ -176,7 +176,7 @@ struct lib {
unsigned runv;
};
-bool print_libav_versions(struct mp_log *log, int v)
+void check_library_versions(struct mp_log *log, int v)
{
const struct lib libs[] = {
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
@@ -189,21 +189,22 @@ bool print_libav_versions(struct mp_log *log, int v)
mp_msg(log, v, "FFmpeg library versions:\n");
- bool mismatch = false;
for (int n = 0; n < MP_ARRAY_SIZE(libs); n++) {
const struct lib *l = &libs[n];
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
- if (l->buildv != l->runv) {
+ if (l->buildv != l->runv)
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
- mismatch = l->buildv > l->runv ||
- AV_VERSION_MAJOR(l->buildv) != AV_VERSION_MAJOR(l->runv);
- }
mp_msg(log, v, "\n");
+ if (l->buildv > l->runv ||
+ AV_VERSION_MAJOR(l->buildv) != AV_VERSION_MAJOR(l->runv))
+ {
+ fprintf(stderr, "%s: %d.%d.%d -> %d.%d.%d\n",
+ l->name, V(l->buildv), V(l->runv));
+ abort();
+ }
}
mp_msg(log, v, "FFmpeg version: %s\n", av_version_info());
-
- return !mismatch;
}
#undef V
diff --git a/common/av_log.h b/common/av_log.h
index 18f7fc9d82..ae12838c35 100644
--- a/common/av_log.h
+++ b/common/av_log.h
@@ -7,5 +7,5 @@ struct mpv_global;
struct mp_log;
void init_libav(struct mpv_global *global);
void uninit_libav(struct mpv_global *global);
-bool print_libav_versions(struct mp_log *log, int v);
+void check_library_versions(struct mp_log *log, int v);
#endif
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index b64232fb87..94428c6733 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -77,32 +77,33 @@ struct mux_stream {
#define OPT_BASE_STRUCT struct encode_opts
const struct m_sub_options encode_config = {
.opts = (const m_option_t[]) {
- OPT_STRING("o", file, CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
- OPT_STRING("of", format, 0),
- OPT_KEYVALUELIST("ofopts", fopts, M_OPT_HAVE_HELP),
- OPT_STRING("ovc", vcodec, 0),
- OPT_KEYVALUELIST("ovcopts", vopts, M_OPT_HAVE_HELP),
- OPT_STRING("oac", acodec, 0),
- OPT_KEYVALUELIST("oacopts", aopts, M_OPT_HAVE_HELP),
- OPT_FLOATRANGE("ovoffset", voffset, 0, -1000000.0, 1000000.0,
- .deprecation_message = "--audio-delay (once unbroken)"),
- OPT_FLOATRANGE("oaoffset", aoffset, 0, -1000000.0, 1000000.0,
- .deprecation_message = "--audio-delay (once unbroken)"),
- OPT_FLAG("orawts", rawts, 0),
- OPT_FLAG("ovfirst", video_first, 0,
- .deprecation_message = "no replacement"),
- OPT_FLAG("oafirst", audio_first, 0,
- .deprecation_message = "no replacement"),
- OPT_FLAG("ocopy-metadata", copy_metadata, 0),
- OPT_KEYVALUELIST("oset-metadata", set_metadata, 0),
- OPT_STRINGLIST("oremove-metadata", remove_metadata, 0),
-
- OPT_REMOVED("ocopyts", "ocopyts is now the default"),
- OPT_REMOVED("oneverdrop", "no replacement"),
- OPT_REMOVED("oharddup", "use --vf-add=fps=VALUE"),
- OPT_REMOVED("ofps", "no replacement (use --vf-add=fps=VALUE for CFR)"),
- OPT_REMOVED("oautofps", "no replacement"),
- OPT_REMOVED("omaxfps", "no replacement"),
+ {"o", OPT_STRING(file), .flags = CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE,
+ .deprecation_message = "lack of maintainer"},
+ {"of", OPT_STRING(format)},
+ {"ofopts", OPT_KEYVALUELIST(fopts), .flags = M_OPT_HAVE_HELP},
+ {"ovc", OPT_STRING(vcodec)},
+ {"ovcopts", OPT_KEYVALUELIST(vopts), .flags = M_OPT_HAVE_HELP},
+ {"oac", OPT_STRING(acodec)},
+ {"oacopts", OPT_KEYVALUELIST(aopts), .flags = M_OPT_HAVE_HELP},
+ {"ovoffset", OPT_FLOAT(voffset), M_RANGE(-1000000.0, 1000000.0),
+ .deprecation_message = "--audio-delay (once unbroken)"},
+ {"oaoffset", OPT_FLOAT(aoffset), M_RANGE(-1000000.0, 1000000.0),
+ .deprecation_message = "--audio-delay (once unbroken)"},
+ {"orawts", OPT_FLAG(rawts)},
+ {"ovfirst", OPT_FLAG(video_first),
+ .deprecation_message = "no replacement"},
+ {"oafirst", OPT_FLAG(audio_first),
+ .deprecation_message = "no replacement"},
+ {"ocopy-metadata", OPT_FLAG(copy_metadata)},
+ {"oset-metadata", OPT_KEYVALUELIST(set_metadata)},
+ {"oremove-metadata", OPT_STRINGLIST(remove_metadata)},
+
+ {"ocopyts", OPT_REMOVED("ocopyts is now the default")},
+ {"oneverdrop", OPT_REMOVED("no replacement")},
+ {"oharddup", OPT_REMOVED("use --vf-add=fps=VALUE")},
+ {"ofps", OPT_REMOVED("no replacement (use --vf-add=fps=VALUE for CFR)")},
+ {"oautofps", OPT_REMOVED("no replacement")},
+ {"omaxfps", OPT_REMOVED("no replacement")},
{0}
},
.size = sizeof(struct encode_opts),
@@ -394,10 +395,11 @@ void encode_lavc_stream_eof(struct encode_lavc_context *ctx,
// Signal that you are ready to encode (you provide the codec params etc. too).
// This returns a muxing handle which you can use to add encodec packets.
// Can be called only once per stream. info is copied by callee as needed.
-static struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx,
- struct encoder_stream_info *info,
- void (*on_ready)(void *ctx),
- void *on_ready_ctx)
+static void encode_lavc_add_stream(struct encoder_context *enc,
+ struct encode_lavc_context *ctx,
+ struct encoder_stream_info *info,
+ void (*on_ready)(void *ctx),
+ void *on_ready_ctx)
{
struct encode_priv *p = ctx->priv;
@@ -426,19 +428,18 @@ static struct mux_stream *encode_lavc_add_stream(struct encode_lavc_context *ctx
// set on the AVStream.
if (info->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
dst->st->sample_aspect_ratio = info->codecpar->sample_aspect_ratio;
-
+
if (avcodec_parameters_copy(dst->st->codecpar, info->codecpar) < 0)
MP_HANDLE_OOM(0);
dst->on_ready = on_ready;
dst->on_ready_ctx = on_ready_ctx;
+ enc->mux_stream = dst;
maybe_init_muxer(ctx);
done:
pthread_mutex_unlock(&ctx->lock);
-
- return dst;
}
// Write a packet. This will take over ownership of `pkt`
@@ -491,6 +492,11 @@ done:
av_packet_unref(pkt);
}
+AVRational encoder_get_mux_timebase_unlocked(struct encoder_context *p)
+{
+ return p->mux_stream->st->time_base;
+}
+
void encode_lavc_discontinuity(struct encode_lavc_context *ctx)
{
if (!ctx)
@@ -916,8 +922,7 @@ bool encoder_init_codec_and_muxer(struct encoder_context *p,
if (avcodec_parameters_from_context(p->info.codecpar, p->encoder) < 0)
goto fail;
- p->mux_stream = encode_lavc_add_stream(p->encode_lavc_ctx, &p->info,
- on_ready, ctx);
+ encode_lavc_add_stream(p, p->encode_lavc_ctx, &p->info, on_ready, ctx);
if (!p->mux_stream)
goto fail;
diff --git a/common/encode_lavc.h b/common/encode_lavc.h
index 97c2cf01f1..390aabd1f8 100644
--- a/common/encode_lavc.h
+++ b/common/encode_lavc.h
@@ -111,6 +111,10 @@ bool encoder_init_codec_and_muxer(struct encoder_context *p,
// Encode the frame and write the packet. frame is ref'ed as need.
bool encoder_encode(struct encoder_context *p, AVFrame *frame);
+// Return muxer timebase (only available after on_ready() has been called).
+// Caller needs to acquire encode_lavc_context.lock (or call it from on_ready).
+AVRational encoder_get_mux_timebase_unlocked(struct encoder_context *p);
+
double encoder_get_offset(struct encoder_context *p);
#endif
diff --git a/common/playlist.c b/common/playlist.c
index 1fd202b98f..b7398dd936 100644
--- a/common/playlist.c
+++ b/common/playlist.c
@@ -62,9 +62,11 @@ static void playlist_update_indexes(struct playlist *pl, int start, int end)
void playlist_add(struct playlist *pl, struct playlist_entry *add)
{
+ assert(add->filename);
MP_TARRAY_APPEND(pl, pl->entries, pl->num_entries, add);
add->pl = pl;
add->pl_index = pl->num_entries - 1;
+ add->id = ++pl->id_alloc;
talloc_steal(pl, add);
}
@@ -237,10 +239,11 @@ void playlist_set_stream_flags(struct playlist *pl, int flags)
pl->entries[n]->stream_flags = flags;
}
-static void playlist_transfer_entries_to(struct playlist *pl, int dst_index,
- struct playlist *source_pl)
+static int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index,
+ struct playlist *source_pl)
{
assert(pl != source_pl);
+ struct playlist_entry *first = playlist_get_first(source_pl);
int count = source_pl->num_entries;
MP_TARRAY_INSERT_N_AT(pl, pl->entries, pl->num_entries, dst_index, count);
@@ -249,17 +252,23 @@ static void playlist_transfer_entries_to(struct playlist *pl, int dst_index,
struct playlist_entry *e = source_pl->entries[n];
e->pl = pl;
e->pl_index = dst_index + n;
+ e->id = ++pl->id_alloc;
pl->entries[e->pl_index] = e;
talloc_steal(pl, e);
}
playlist_update_indexes(pl, dst_index + count, -1);
source_pl->num_entries = 0;
+
+ return first ? first->id : 0;
}
// Move all entries from source_pl to pl, appending them after the current entry
// of pl. source_pl will be empty, and all entries have changed ownership to pl.
-void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
+// Return the new ID of the first added entry within pl (0 if source_pl was
+// empty). The IDs of all added entries increase by 1 each entry (you can
+// predict the ID of the last entry).
+int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
{
int add_at = pl->num_entries;
@@ -271,21 +280,20 @@ void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
assert(add_at >= 0);
assert(add_at <= pl->num_entries);
- playlist_transfer_entries_to(pl, add_at, source_pl);
+ return playlist_transfer_entries_to(pl, add_at, source_pl);
}
-void playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
+int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
{
- playlist_transfer_entries_to(pl, pl->num_entries, source_pl);
+ return playlist_transfer_entries_to(pl, pl->num_entries, source_pl);
}
// Return number of entries between list start and e.
// Return -1 if e is not on the list, or if e is NULL.
int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
{
- if (!e)
+ if (!e || e->pl != pl)
return -1;
- assert(e->pl == pl);
return e->pl_index;
}
diff --git a/common/playlist.h b/common/playlist.h
index 8b014e864d..5f84e3b413 100644
--- a/common/playlist.h
+++ b/common/playlist.h
@@ -30,6 +30,8 @@ struct playlist_entry {
struct playlist *pl;
int pl_index;
+ uint64_t id;
+
char *filename;
struct playlist_param *params;
@@ -71,6 +73,8 @@ struct playlist {
// current_was_replaced is set to true.
struct playlist_entry *current;
bool current_was_replaced;
+
+ uint64_t id_alloc;
};
void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
@@ -99,8 +103,8 @@ struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e,
void playlist_add_base_path(struct playlist *pl, bstr base_path);
void playlist_add_redirect(struct playlist *pl, const char *redirected_from);
void playlist_set_stream_flags(struct playlist *pl, int flags);
-void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
-void playlist_append_entries(struct playlist *pl, struct playlist *source_pl);
+int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
+int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl);
int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e);
int playlist_entry_count(struct playlist *pl);
diff --git a/demux/cache.c b/demux/cache.c
index 4404c870de..f48598807c 100644
--- a/demux/cache.c
+++ b/demux/cache.c
@@ -40,9 +40,10 @@ struct demux_cache_opts {
const struct m_sub_options demux_cache_conf = {
.opts = (const struct m_option[]){
- OPT_STRING("cache-dir", cache_dir, M_OPT_FILE),
- OPT_CHOICE("cache-unlink-files", unlink_files, 0,
- ({"immediate", 2}, {"whendone", 1}, {"no", 0})),
+ {"cache-dir", OPT_STRING(cache_dir), .flags = M_OPT_FILE},
+ {"cache-unlink-files", OPT_CHOICE(unlink_files,
+ {"immediate", 2}, {"whendone", 1}, {"no", 0}),
+ },
{0}
},
.size = sizeof(struct demux_cache_opts),
diff --git a/demux/demux.c b/demux/demux.c
index 75d74d0beb..1922f68397 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -15,6 +15,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -100,41 +101,45 @@ struct demux_opts {
int back_batch[STREAM_TYPE_COUNT];
double back_seek_size;
char *meta_cp;
+ int force_retry_eof;
};
#define OPT_BASE_STRUCT struct demux_opts
-#define MAX_BYTES MPMIN(INT64_MAX, SIZE_MAX / 2)
-
static bool get_demux_sub_opts(int index, const struct m_sub_options **sub);
const struct m_sub_options demux_conf = {
.opts = (const struct m_option[]){
- OPT_CHOICE("cache", enable_cache, 0,
- ({"no", 0}, {"auto", -1}, {"yes", 1})),
- OPT_FLAG("cache-on-disk", disk_cache, 0),
- OPT_DOUBLE("demuxer-readahead-secs", min_secs, M_OPT_MIN, .min = 0),
- // (The MAX_BYTES sizes may not be accurate because the max field is
- // of double type.)
- OPT_BYTE_SIZE("demuxer-max-bytes", max_bytes, 0, 0, MAX_BYTES),
- OPT_BYTE_SIZE("demuxer-max-back-bytes", max_bytes_bw, 0, 0, MAX_BYTES),
- OPT_FLAG("demuxer-donate-buffer", donate_fw, 0),
- OPT_FLAG("force-seekable", force_seekable, 0),
- OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0),
- OPT_FLAG("access-references", access_references, 0),
- OPT_CHOICE("demuxer-seekable-cache", seekable_cache, 0,
- ({"auto", -1}, {"no", 0}, {"yes", 1})),
- OPT_FLAG("sub-create-cc-track", create_ccs, 0),
- OPT_STRING("stream-record", record_file, 0),
- OPT_CHOICE_OR_INT("video-backward-overlap", video_back_preroll, 0, 0,
- 1024, ({"auto", -1})),
- OPT_CHOICE_OR_INT("audio-backward-overlap", audio_back_preroll, 0, 0,
- 1024, ({"auto", -1})),
- OPT_INTRANGE("video-backward-batch", back_batch[STREAM_VIDEO], 0, 0, 1024),
- OPT_INTRANGE("audio-backward-batch", back_batch[STREAM_AUDIO], 0, 0, 1024),
- OPT_DOUBLE("demuxer-backward-playback-step", back_seek_size, M_OPT_MIN,
- .min = 0),
- OPT_STRING("metadata-codepage", meta_cp, 0),
+ {"cache", OPT_CHOICE(enable_cache,
+ {"no", 0}, {"auto", -1}, {"yes", 1})},
+ {"cache-on-disk", OPT_FLAG(disk_cache)},
+ {"demuxer-readahead-secs", OPT_DOUBLE(min_secs), M_RANGE(0, DBL_MAX)},
+ {"demuxer-max-bytes", OPT_BYTE_SIZE(max_bytes),
+ M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"demuxer-max-back-bytes", OPT_BYTE_SIZE(max_bytes_bw),
+ M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"demuxer-donate-buffer", OPT_FLAG(donate_fw)},
+ {"force-seekable", OPT_FLAG(force_seekable)},
+ {"cache-secs", OPT_DOUBLE(min_secs_cache), M_RANGE(0, DBL_MAX),
+ .deprecation_message = "will use unlimited time"},
+ {"access-references", OPT_FLAG(access_references)},
+ {"demuxer-seekable-cache", OPT_CHOICE(seekable_cache,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"sub-create-cc-track", OPT_FLAG(create_ccs)},
+ {"stream-record", OPT_STRING(record_file)},
+ {"video-backward-overlap", OPT_CHOICE(video_back_preroll, {"auto", -1}),
+ M_RANGE(0, 1024)},
+ {"audio-backward-overlap", OPT_CHOICE(audio_back_preroll, {"auto", -1}),
+ M_RANGE(0, 1024)},
+ {"video-backward-batch", OPT_INT(back_batch[STREAM_VIDEO]),
+ M_RANGE(0, 1024)},
+ {"audio-backward-batch", OPT_INT(back_batch[STREAM_AUDIO]),
+ M_RANGE(0, 1024)},
+ {"demuxer-backward-playback-step", OPT_DOUBLE(back_seek_size),
+ M_RANGE(0, DBL_MAX)},
+ {"metadata-codepage", OPT_STRING(meta_cp)},
+ {"demuxer-force-retry-on-eof", OPT_FLAG(force_retry_eof),
+ .deprecation_message = "temporary debug option, no replacement"},
{0}
},
.size = sizeof(struct demux_opts),
@@ -144,7 +149,7 @@ const struct m_sub_options demux_conf = {
.max_bytes_bw = 50 * 1024 * 1024,
.donate_fw = 1,
.min_secs = 1.0,
- .min_secs_cache = 10.0 * 60 * 60,
+ .min_secs_cache = 1000.0 * 60 * 60,
.seekable_cache = -1,
.access_references = 1,
.video_back_preroll = -1,
@@ -202,9 +207,7 @@ struct demux_internal {
struct demux_cache *cache;
bool warned_queue_overflow;
- bool last_eof; // last actual global EOF status
- bool eof; // whether we're in EOF state (reset for retry)
- bool idle;
+ bool eof; // whether we're in EOF state
double min_secs;
size_t max_bytes;
size_t max_bytes_bw;
@@ -212,8 +215,11 @@ struct demux_internal {
bool using_network_cache_opts;
char *record_filename;
- // At least one decoder actually requested data since init or the last seek.
- // Do this to allow the decoder thread to select streams before starting.
+ // Whether the demuxer thread should prefetch packets. This is set to false
+ // if EOF was reached or the demuxer cache is full. This is also important
+ // in the initial state: the decoder thread needs to select streams before
+ // the first packet is read, so this is set to true by packet reading only.
+ // Reset to false again on EOF or if prefetching is done.
bool reading;
// Set if we just performed a seek, without reading packets yet. Used to
@@ -266,6 +272,7 @@ struct demux_internal {
// Cached state.
int64_t stream_size;
int64_t last_speed_query;
+ double speed_query_prev_sample;
uint64_t bytes_per_second;
int64_t next_cache_update;
@@ -400,6 +407,7 @@ struct demux_stream {
bool skip_to_keyframe;
bool attached_picture_added;
bool need_wakeup; // call wakeup_cb on next reader_head state change
+ double force_read_until;// eager=false streams (subs): force read-ahead
// For demux_internal.dumper. Currently, this is used only temporarily
// during blocking dumping.
@@ -626,7 +634,9 @@ static void update_seek_ranges(struct demux_cached_range *range)
range->is_bof &= queue->is_bof;
bool empty = queue->is_eof && !queue->head;
- if (queue->seek_start >= queue->seek_end && !empty)
+ if (queue->seek_start >= queue->seek_end && !empty &&
+ !(queue->seek_start == queue->seek_end &&
+ queue->seek_start != MP_NOPTS_VALUE))
goto broken;
}
}
@@ -824,6 +834,7 @@ static void ds_clear_reader_state(struct demux_stream *ds,
ds->attached_picture_added = false;
ds->last_ret_pos = -1;
ds->last_ret_dts = MP_NOPTS_VALUE;
+ ds->force_read_until = MP_NOPTS_VALUE;
if (clear_back_state) {
ds->back_restart_pos = -1;
@@ -1557,6 +1568,7 @@ resume_earlier:
ds->reader_head = t;
ds->back_need_recheck = true;
in->back_any_need_recheck = true;
+ pthread_cond_signal(&in->wakeup);
} else {
ds->back_seek_pos -= in->opts->back_seek_size;
in->need_back_seek = true;
@@ -1569,7 +1581,7 @@ static void back_demux_see_packets(struct demux_stream *ds)
{
struct demux_internal *in = ds->in;
- if (!ds->selected || !in->back_demuxing)
+ if (!ds->selected || !in->back_demuxing || !ds->eager)
return;
assert(!(ds->back_resuming && ds->back_restarting));
@@ -1844,7 +1856,8 @@ static struct demux_packet *compute_keyframe_times(struct demux_packet *pkt,
break;
double ts = MP_PTS_OR_DEF(pkt->pts, pkt->dts);
- if (pkt->segmented && (ts < pkt->start || ts > pkt->end))
+ if (pkt->segmented && ((pkt->start != MP_NOPTS_VALUE && ts < pkt->start) ||
+ (pkt->end != MP_NOPTS_VALUE && ts > pkt->end)))
ts = MP_NOPTS_VALUE;
min = MP_PTS_MIN(min, ts);
@@ -1913,7 +1926,8 @@ static void adjust_seek_range_on_packet(struct demux_stream *ds,
queue->keyframe_latest = dp;
}
- if (update_ranges) {
+ // Adding a sparse packet never changes the seek range.
+ if (update_ranges && ds->eager) {
update_seek_ranges(queue->range);
attempt_range_joining(ds->in);
}
@@ -2011,7 +2025,8 @@ static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp)
if (!drop) {
// If libavformat splits packets, some packets will have pos unset, so
// make up one based on the first packet => makes refresh seeks work.
- if (dp->pos < 0 && !dp->keyframe && queue->last_pos_fixup >= 0)
+ if ((dp->pos < 0 || dp->pos == queue->last_pos_fixup) &&
+ !dp->keyframe && queue->last_pos_fixup >= 0)
dp->pos = queue->last_pos_fixup + 1;
queue->last_pos_fixup = dp->pos;
}
@@ -2078,7 +2093,7 @@ static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp)
if (!ds->ignore_eof) {
// obviously not true anymore
ds->eof = false;
- in->last_eof = in->eof = false;
+ in->eof = false;
}
// For video, PTS determination is not trivial, but for other media types
@@ -2138,13 +2153,24 @@ static void mark_stream_eof(struct demux_stream *ds)
}
}
+static bool lazy_stream_needs_wait(struct demux_stream *ds)
+{
+ struct demux_internal *in = ds->in;
+ // Attempt to read until force_read_until was reached, or reading has
+ // stopped for some reason (true EOF, queue overflow).
+ return !ds->eager && !ds->reader_head && !in->back_demuxing &&
+ !in->eof && ds->force_read_until != MP_NOPTS_VALUE &&
+ (in->demux_ts == MP_NOPTS_VALUE ||
+ in->demux_ts <= ds->force_read_until);
+}
+
// Returns true if there was "progress" (lock was released temporarily).
static bool read_packet(struct demux_internal *in)
{
- in->eof = false;
- in->idle = true;
+ bool was_reading = in->reading;
+ in->reading = false;
- if (!in->reading || in->blocked || demux_cancel_test(in->d_thread))
+ if (!was_reading || in->blocked || demux_cancel_test(in->d_thread))
return false;
// Check if we need to read a new packet. We do this if all queues are below
@@ -2158,6 +2184,12 @@ static bool read_packet(struct demux_internal *in)
read_more |= !ds->reader_head;
if (in->back_demuxing)
read_more |= ds->back_restarting || ds->back_resuming;
+ } else {
+ if (lazy_stream_needs_wait(ds)) {
+ read_more = true;
+ } else {
+ mark_stream_eof(ds); // let playback continue
+ }
}
refresh_more |= ds->refreshing;
if (ds->eager && ds->queue->last_ts != MP_NOPTS_VALUE &&
@@ -2216,7 +2248,7 @@ static bool read_packet(struct demux_internal *in)
// Actually read a packet. Drop the lock while doing so, because waiting
// for disk or network I/O can take time.
- in->idle = false;
+ in->reading = true;
in->after_seek = false;
in->after_seek_to_start = false;
pthread_mutex_unlock(&in->lock);
@@ -2241,14 +2273,15 @@ static bool read_packet(struct demux_internal *in)
for (int n = 0; n < in->num_streams; n++)
mark_stream_eof(in->streams[n]->ds);
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
- if (!in->last_eof) {
+ if (!in->eof) {
if (in->wakeup_cb)
in->wakeup_cb(in->wakeup_cb_ctx);
pthread_cond_signal(&in->wakeup);
MP_VERBOSE(in, "EOF reached.\n");
}
}
- in->eof = in->last_eof = eof;
+ in->eof = eof;
+ in->reading = !eof;
}
return true;
}
@@ -2388,7 +2421,7 @@ static void execute_seek(struct demux_internal *in)
{
int flags = in->seek_flags;
double pts = in->seek_pts;
- in->last_eof = in->eof = false;
+ in->eof = false;
in->seeking = false;
in->seeking_in_progress = pts;
in->demux_ts = MP_NOPTS_VALUE;
@@ -2503,10 +2536,8 @@ static bool thread_work(struct demux_internal *in)
execute_seek(in);
return true;
}
- if (!in->eof) {
- if (read_packet(in))
- return true; // read_packet unlocked, so recheck conditions
- }
+ if (read_packet(in))
+ return true; // read_packet unlocked, so recheck conditions
if (mp_time_us() >= in->next_cache_update) {
update_cache(in);
return true;
@@ -2586,7 +2617,8 @@ static struct demux_packet *read_packet_from_cache(struct demux_internal *in,
// < 0: EOF was reached, *res is not set
// == 0: no new packet yet, wait, *res is not set
// > 0: new packet is moved to *res
-static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
+static int dequeue_packet(struct demux_stream *ds, double min_pts,
+ struct demux_packet **res)
{
struct demux_internal *in = ds->in;
@@ -2608,12 +2640,13 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
return 1;
}
- if (ds->eager) {
- in->reading = true; // enable readahead
- in->eof = false; // force retry
- pthread_cond_signal(&in->wakeup); // possibly read more
+ if (!in->reading && (!in->eof || in->opts->force_retry_eof)) {
+ in->reading = true; // enable demuxer thread prefetching
+ pthread_cond_signal(&in->wakeup);
}
+ ds->force_read_until = min_pts;
+
if (ds->back_resuming || ds->back_restarting) {
assert(in->back_demuxing);
return 0;
@@ -2650,7 +2683,10 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
// the reader would have to wait for new packets, which does not
// make sense due to the sparseness and passiveness of non-eager
// streams.
- return -1;
+ // Unless the min_pts feature is used: then EOF is only signaled
+ // if read-ahead went above min_pts.
+ if (!lazy_stream_needs_wait(ds))
+ ds->eof = eof = true;
}
return eof ? -1 : 0;
}
@@ -2729,6 +2765,16 @@ static int dequeue_packet(struct demux_stream *ds, struct demux_packet **res)
// minutes away). In this situation, this function will just return -1.
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
{
+ return demux_read_packet_async_until(sh, MP_NOPTS_VALUE, out_pkt);
+}
+
+// Like demux_read_packet_async(). They are the same for min_pts==MP_NOPTS_VALUE.
+// If min_pts is set, and the stream is lazily read (eager=false, interleaved
+// subtitles), then return 0 until demuxing has reached min_pts, or the queue
+// overflowed, or EOF was reached, or a packet was read for this stream.
+int demux_read_packet_async_until(struct sh_stream *sh, double min_pts,
+ struct demux_packet **out_pkt)
+{
struct demux_stream *ds = sh ? sh->ds : NULL;
*out_pkt = NULL;
if (!ds)
@@ -2738,7 +2784,7 @@ int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt)
pthread_mutex_lock(&in->lock);
int r = -1;
while (1) {
- r = dequeue_packet(ds, out_pkt);
+ r = dequeue_packet(ds, min_pts, out_pkt);
if (in->threading || in->blocked || r != 0)
break;
// Needs to actually read packets until we got a packet or EOF.
@@ -2760,8 +2806,7 @@ struct demux_packet *demux_read_any_packet(struct demuxer *demuxer)
while (read_more && !in->blocked) {
bool all_eof = true;
for (int n = 0; n < in->num_streams; n++) {
- in->reading = true; // force read_packet() to read
- int r = dequeue_packet(in->streams[n]->ds, &out_pkt);
+ int r = dequeue_packet(in->streams[n]->ds, MP_NOPTS_VALUE, &out_pkt);
if (r > 0)
goto done;
if (r == 0)
@@ -3735,6 +3780,9 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
bool force_seek = flags & SEEK_FORCE;
flags &= ~(unsigned)SEEK_FORCE;
+ bool block = flags & SEEK_BLOCK;
+ flags &= ~(unsigned)SEEK_BLOCK;
+
struct demux_cached_range *cache_target =
find_cache_seek_range(in, seek_pts, flags);
@@ -3750,12 +3798,13 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
}
in->eof = false;
- in->idle = true;
in->reading = false;
in->back_demuxing = set_backwards;
clear_reader_state(in, clear_back_state);
+ in->blocked = block;
+
if (cache_target) {
execute_cache_seek(in, cache_target, seek_pts, flags);
} else {
@@ -4060,7 +4109,10 @@ static void update_cache(struct demux_internal *in)
uint64_t bytes = in->cache_unbuffered_read_bytes;
in->cache_unbuffered_read_bytes = 0;
in->last_speed_query = now;
- in->bytes_per_second = bytes / (diff / (double)MP_SECOND_US);
+ double speed = bytes / (diff / (double)MP_SECOND_US);
+ in->bytes_per_second = 0.5 * in->speed_query_prev_sample +
+ 0.5 * speed;
+ in->speed_query_prev_sample = speed;
}
// The idea is to update as long as there is "activity".
if (in->bytes_per_second)
@@ -4374,7 +4426,7 @@ void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *
pthread_mutex_lock(&in->lock);
*r = (struct demux_reader_state){
- .eof = in->last_eof,
+ .eof = in->eof,
.ts_reader = MP_NOPTS_VALUE,
.ts_end = MP_NOPTS_VALUE,
.ts_duration = -1,
@@ -4397,7 +4449,7 @@ void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *
}
r->fw_bytes += get_foward_buffered_bytes(ds);
}
- r->idle = (in->idle && !r->underrun) || r->eof;
+ r->idle = (!in->reading && !r->underrun) || r->eof;
r->underrun &= !r->idle && in->threading;
r->ts_reader = MP_ADD_PTS(r->ts_reader, in->ts_offset);
r->ts_end = MP_ADD_PTS(r->ts_end, in->ts_offset);
diff --git a/demux/demux.h b/demux/demux.h
index c94f690da0..f49e6e2a2f 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -63,6 +63,8 @@ struct demux_reader_state {
#define SEEK_SATAN (1 << 4) // enable backward demuxing
#define SEEK_HR (1 << 5) // hr-seek (this is a weak hint only)
#define SEEK_FORCE (1 << 6) // ignore unseekable flag
+#define SEEK_BLOCK (1 << 7) // upon successfully queued seek, block readers
+ // (simplifies syncing multiple reader threads)
// Strictness of the demuxer open format check.
// demux.c will try by default: NORMAL, UNSAFE (in this order)
@@ -251,6 +253,8 @@ bool demux_free_async_finish(struct demux_free_async_state *state);
void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp);
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
+int demux_read_packet_async_until(struct sh_stream *sh, double min_pts,
+ struct demux_packet **out_pkt);
bool demux_stream_is_selected(struct sh_stream *stream);
void demux_set_stream_wakeup_cb(struct sh_stream *sh,
void (*cb)(void *ctx), void *ctx);
diff --git a/demux/demux_cue.c b/demux/demux_cue.c
index edd1a97d1b..b7b425af03 100644
--- a/demux/demux_cue.c
+++ b/demux/demux_cue.c
@@ -49,7 +49,7 @@ struct demux_cue_opts {
const struct m_sub_options demux_cue_conf = {
.opts = (const m_option_t[]) {
- OPT_STRING("codepage", cue_cp, 0),
+ {"codepage", OPT_STRING(cue_cp)},
{0}
},
.size = sizeof(struct demux_cue_opts),
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index e71c1dfd99..256b304f3a 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -50,7 +50,8 @@ struct tl_parts {
bool disable_chapters;
bool dash, no_clip, delay_open;
char *init_fragment_url;
- struct sh_stream *sh_meta;
+ struct sh_stream **sh_meta;
+ int num_sh_meta;
struct tl_part *parts;
int num_parts;
struct tl_parts *next;
@@ -145,12 +146,22 @@ static bool get_param_time(struct parse_ctx *ctx, const char *name, double *t)
static struct tl_parts *add_part(struct tl_root *root)
{
struct tl_parts *tl = talloc_zero(root, struct tl_parts);
- tl->sh_meta = demux_alloc_sh_stream(STREAM_TYPE_COUNT);
- talloc_steal(tl, tl->sh_meta);
MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
return tl;
}
+static struct sh_stream *get_meta(struct tl_parts *tl, int index)
+{
+ for (int n = 0; n < tl->num_sh_meta; n++) {
+ if (tl->sh_meta[n]->index == index)
+ return tl->sh_meta[n];
+ }
+ struct sh_stream *sh = demux_alloc_sh_stream(STREAM_TYPE_COUNT);
+ talloc_steal(tl, sh);
+ MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta, sh);
+ return sh;
+}
+
/* Returns a list of parts, or NULL on parse error.
* Syntax (without file header or URI prefix):
* url ::= <entry> ( (';' | '\n') <entry> )*
@@ -223,12 +234,26 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
} else if (bstr_equals0(f_type, "no_chapters")) {
tl->disable_chapters = true;
} else if (bstr_equals0(f_type, "track_meta")) {
- struct sh_stream *sh = tl->sh_meta;
+ int index = get_param_int(&ctx, "index", -1);
+ struct sh_stream *sh = index < 0 && tl->num_sh_meta
+ ? tl->sh_meta[tl->num_sh_meta - 1]
+ : get_meta(tl, index);
sh->lang = get_param0(&ctx, sh, "lang");
sh->title = get_param0(&ctx, sh, "title");
sh->hls_bitrate = get_param_int(&ctx, "byterate", 0) * 8;
+ bstr flags = get_param(&ctx, "flags");
+ bstr flag;
+ while (bstr_split_tok(flags, "+", &flag, &flags) || flag.len) {
+ if (bstr_equals0(flag, "default")) {
+ sh->default_track = true;
+ } else if (bstr_equals0(flag, "forced")) {
+ sh->forced_track = true;
+ } else {
+ mp_warn(log, "Unknown flag: '%.*s'\n", BSTR_P(flag));
+ }
+ }
} else if (bstr_equals0(f_type, "delay_open")) {
- struct sh_stream *sh = tl->sh_meta;
+ struct sh_stream *sh = get_meta(tl, tl->num_sh_meta);
bstr mt = get_param(&ctx, "media_type");
if (bstr_equals0(mt, "video")) {
sh->type = sh->codec->type = STREAM_VIDEO;
@@ -245,6 +270,8 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
sh->codec->codec = "null";
sh->codec->disp_w = get_param_int(&ctx, "w", 0);
sh->codec->disp_h = get_param_int(&ctx, "h", 0);
+ sh->codec->fps = get_param_int(&ctx, "fps", 0);
+ sh->codec->samplerate = get_param_int(&ctx, "samplerate", 0);
tl->delay_open = true;
} else {
mp_err(log, "Unknown header: '%.*s'\n", BSTR_P(f_type));
@@ -278,8 +305,10 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
}
if (ctx.error)
goto error;
- for (int n = 0; n < ctx.num_params; n++)
- mp_warn(log, "Unknown parameter: '%.*s'\n", BSTR_P(ctx.param_names[n]));
+ for (int n = 0; n < ctx.num_params; n++) {
+ mp_warn(log, "Unknown or duplicate parameter: '%.*s'\n",
+ BSTR_P(ctx.param_names[n]));
+ }
}
assert(root->num_pars);
for (int n = 0; n < root->num_pars; n++) {
@@ -371,8 +400,12 @@ static struct timeline_par *build_timeline(struct timeline *root,
tl->delay_open = parts->delay_open;
// There is no copy function for sh_stream, so just steal it.
- tl->sh_meta = talloc_steal(tl, parts->sh_meta);
- parts->sh_meta = NULL;
+ for (int n = 0; n < parts->num_sh_meta; n++) {
+ MP_TARRAY_APPEND(tl, tl->sh_meta, tl->num_sh_meta,
+ talloc_steal(tl, parts->sh_meta[n]));
+ parts->sh_meta[n] = NULL;
+ }
+ parts->num_sh_meta = 0;
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
MP_VERBOSE(root, "Opening init fragment...\n");
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index f4bbfc5739..50b9e044e4 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -84,28 +84,29 @@ struct demux_lavf_opts {
const struct m_sub_options demux_lavf_conf = {
.opts = (const m_option_t[]) {
- OPT_INTRANGE("demuxer-lavf-probesize", probesize, 0, 32, INT_MAX),
- OPT_CHOICE("demuxer-lavf-probe-info", probeinfo, 0,
- ({"no", 0}, {"yes", 1}, {"auto", -1}, {"nostreams", -2})),
- OPT_STRING("demuxer-lavf-format", format, 0),
- OPT_FLOATRANGE("demuxer-lavf-analyzeduration", analyzeduration, 0,
- 0, 3600),
- OPT_INTRANGE("demuxer-lavf-buffersize", buffersize, 0, 1,
- 10 * 1024 * 1024, OPTDEF_INT(BIO_BUFFER_SIZE)),
- OPT_FLAG("demuxer-lavf-allow-mimetype", allow_mimetype, 0),
- OPT_INTRANGE("demuxer-lavf-probescore", probescore, 0,
- 1, AVPROBE_SCORE_MAX),
- OPT_FLAG("demuxer-lavf-hacks", hacks, 0),
- OPT_KEYVALUELIST("demuxer-lavf-o", avopts, 0),
- OPT_STRING("sub-codepage", sub_cp, 0),
- OPT_CHOICE("rtsp-transport", rtsp_transport, 0,
- ({"lavf", 0},
- {"udp", 1},
- {"tcp", 2},
- {"http", 3})),
- OPT_CHOICE("demuxer-lavf-linearize-timestamps", linearize_ts, 0,
- ({"no", 0}, {"auto", -1}, {"yes", 1})),
- OPT_FLAG("demuxer-lavf-propagate-opts", propagate_opts, 0),
+ {"demuxer-lavf-probesize", OPT_INT(probesize), M_RANGE(32, INT_MAX)},
+ {"demuxer-lavf-probe-info", OPT_CHOICE(probeinfo,
+ {"no", 0}, {"yes", 1}, {"auto", -1}, {"nostreams", -2})},
+ {"demuxer-lavf-format", OPT_STRING(format)},
+ {"demuxer-lavf-analyzeduration", OPT_FLOAT(analyzeduration),
+ M_RANGE(0, 3600)},
+ {"demuxer-lavf-buffersize", OPT_INT(buffersize),
+ M_RANGE(1, 10 * 1024 * 1024), OPTDEF_INT(BIO_BUFFER_SIZE)},
+ {"demuxer-lavf-allow-mimetype", OPT_FLAG(allow_mimetype)},
+ {"demuxer-lavf-probescore", OPT_INT(probescore),
+ M_RANGE(1, AVPROBE_SCORE_MAX)},
+ {"demuxer-lavf-hacks", OPT_FLAG(hacks)},
+ {"demuxer-lavf-o", OPT_KEYVALUELIST(avopts)},
+ {"sub-codepage", OPT_STRING(sub_cp)},
+ {"rtsp-transport", OPT_CHOICE(rtsp_transport,
+ {"lavf", 0},
+ {"udp", 1},
+ {"tcp", 2},
+ {"http", 3},
+ {"udp_multicast", 4})},
+ {"demuxer-lavf-linearize-timestamps", OPT_CHOICE(linearize_ts,
+ {"no", 0}, {"auto", -1}, {"yes", 1})},
+ {"demuxer-lavf-propagate-opts", OPT_FLAG(propagate_opts)},
{0}
},
.size = sizeof(struct demux_lavf_opts),
@@ -146,6 +147,7 @@ struct format_hack {
bool is_network : 1;
bool no_seek : 1;
bool no_pcm_seek : 1;
+ bool no_seek_on_no_duration : 1;
};
#define BLACKLIST(fmt) {fmt, .ignore = true}
@@ -174,6 +176,7 @@ static const struct format_hack format_hacks[] = {
{"matroska", .skipinfo = true, .no_pcm_seek = true, .use_stream_ids = true},
{"v4l2", .no_seek = true},
+ {"rtsp", .no_seek_on_no_duration = true},
// In theory, such streams might contain timestamps, but virtually none do.
{"h264", .if_flags = AVFMT_NOTIMESTAMPS },
@@ -238,6 +241,8 @@ typedef struct lavf_priv {
int linearize_ts;
bool any_ts_fixed;
+ int retry_counter;
+
AVDictionary *av_opts;
// Proxying nested streams.
@@ -796,8 +801,6 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
if (lang && lang->value && strcmp(lang->value, "und") != 0)
sh->lang = talloc_strdup(sh, lang->value);
sh->hls_bitrate = dict_get_decimal(st->metadata, "variant_bitrate", 0);
- if (!sh->title && sh->hls_bitrate > 0)
- sh->title = talloc_asprintf(sh, "bitrate %d", sh->hls_bitrate);
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
mp_tags_copy_from_av_dictionary(sh->tags, st->metadata);
demux_add_sh_stream(demuxer, sh);
@@ -984,6 +987,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
case 1: transport = "udp"; break;
case 2: transport = "tcp"; break;
case 3: transport = "http"; break;
+ case 4: transport = "udp_multicast"; break;
}
if (transport)
av_dict_set(&dopts, "rtsp_transport", transport, 0);
@@ -1092,6 +1096,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
demuxer->duration = duration;
}
+ if (demuxer->duration < 0 && priv->format_hack.no_seek_on_no_duration)
+ demuxer->seekable = false;
+
// In some cases, libavformat will export bogus bullshit timestamps anyway,
// such as with mjpeg.
if (priv->avif_flags & AVFMT_NOTIMESTAMPS) {
@@ -1126,13 +1133,17 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
update_read_stats(demux);
if (r < 0) {
av_packet_unref(pkt);
- if (r == AVERROR(EAGAIN))
- return true;
if (r == AVERROR_EOF)
return false;
MP_WARN(demux, "error reading packet: %s.\n", av_err2str(r));
- return false;
+ if (priv->retry_counter >= 10) {
+ MP_ERR(demux, "...treating it as fatal error.\n");
+ return false;
+ }
+ priv->retry_counter += 1;
+ return true;
}
+ priv->retry_counter = 0;
add_new_streams(demux);
update_metadata(demux);
diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c
index 4be5193b22..dc8d201b19 100644
--- a/demux/demux_libarchive.c
+++ b/demux/demux_libarchive.c
@@ -112,7 +112,7 @@ const struct demuxer_desc demuxer_desc_libarchive = {
.open = open_file,
.options = &(const struct m_sub_options){
.opts = (const struct m_option[]) {
- OPT_FLAG("rar-list-all-volumes", rar_list_all_volumes, 0),
+ {"rar-list-all-volumes", OPT_FLAG(rar_list_all_volumes)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index 99e481bc7d..bb1125a0f2 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -20,6 +20,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <float.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
@@ -228,15 +229,15 @@ struct demux_mkv_opts {
const struct m_sub_options demux_mkv_conf = {
.opts = (const m_option_t[]) {
- OPT_CHOICE("subtitle-preroll", subtitle_preroll, 0,
- ({"no", 0}, {"yes", 1}, {"index", 2})),
- OPT_DOUBLE("subtitle-preroll-secs", subtitle_preroll_secs,
- M_OPT_MIN, .min = 0),
- OPT_DOUBLE("subtitle-preroll-secs-index", subtitle_preroll_secs_index,
- M_OPT_MIN, .min = 0),
- OPT_CHOICE("probe-video-duration", probe_duration, 0,
- ({"no", 0}, {"yes", 1}, {"full", 2})),
- OPT_FLAG("probe-start-time", probe_start_time, 0),
+ {"subtitle-preroll", OPT_CHOICE(subtitle_preroll,
+ {"no", 0}, {"yes", 1}, {"index", 2})},
+ {"subtitle-preroll-secs", OPT_DOUBLE(subtitle_preroll_secs),
+ M_RANGE(0, DBL_MAX)},
+ {"subtitle-preroll-secs-index", OPT_DOUBLE(subtitle_preroll_secs_index),
+ M_RANGE(0, DBL_MAX)},
+ {"probe-video-duration", OPT_CHOICE(probe_duration,
+ {"no", 0}, {"yes", 1}, {"full", 2})},
+ {"probe-start-time", OPT_FLAG(probe_start_time)},
{0}
},
.size = sizeof(struct demux_mkv_opts),
@@ -244,6 +245,7 @@ const struct m_sub_options demux_mkv_conf = {
.subtitle_preroll = 2,
.subtitle_preroll_secs = 1.0,
.subtitle_preroll_secs_index = 10.0,
+ .probe_start_time = 1,
},
};
@@ -2993,7 +2995,8 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
double secs = mkv_d->opts->subtitle_preroll_secs;
if (mkv_d->index_has_durations)
secs = MPMAX(secs, mkv_d->opts->subtitle_preroll_secs_index);
- int64_t pre = MPMIN(INT64_MAX, secs * 1e9 / mkv_d->tc_scale);
+ double pre_f = secs * 1e9 / mkv_d->tc_scale;
+ int64_t pre = pre_f >= (double)INT64_MAX ? INT64_MAX : (int64_t)pre_f;
int64_t min_tc = pre < index->timecode ? index->timecode - pre : 0;
uint64_t prev_target = 0;
int64_t prev_tc = 0;
@@ -3211,7 +3214,7 @@ static void probe_first_timestamp(struct demuxer *demuxer)
demuxer->start_time = mkv_d->cluster_tc / 1e9;
- if (demuxer->start_time > 0)
+ if (demuxer->start_time)
MP_VERBOSE(demuxer, "Start PTS: %f\n", demuxer->start_time);
}
diff --git a/demux/demux_raw.c b/demux/demux_raw.c
index de657feb8c..e606a24c9f 100644
--- a/demux/demux_raw.c
+++ b/demux/demux_raw.c
@@ -54,27 +54,27 @@ struct demux_rawaudio_opts {
#define OPT_BASE_STRUCT struct demux_rawaudio_opts
const struct m_sub_options demux_rawaudio_conf = {
.opts = (const m_option_t[]) {
- OPT_CHANNELS("channels", channels, 0, .min = 1),
- OPT_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
- OPT_CHOICE("format", aformat, 0,
- ({"u8", PCM(0, 0, 8, 0)},
- {"s8", PCM(1, 0, 8, 0)},
- {"u16le", PCM(0, 0, 16, 0)}, {"u16be", PCM(0, 0, 16, 1)},
- {"s16le", PCM(1, 0, 16, 0)}, {"s16be", PCM(1, 0, 16, 1)},
- {"u24le", PCM(0, 0, 24, 0)}, {"u24be", PCM(0, 0, 24, 1)},
- {"s24le", PCM(1, 0, 24, 0)}, {"s24be", PCM(1, 0, 24, 1)},
- {"u32le", PCM(0, 0, 32, 0)}, {"u32be", PCM(0, 0, 32, 1)},
- {"s32le", PCM(1, 0, 32, 0)}, {"s32be", PCM(1, 0, 32, 1)},
- {"floatle", PCM(0, 1, 32, 0)}, {"floatbe", PCM(0, 1, 32, 1)},
- {"doublele",PCM(0, 1, 64, 0)}, {"doublebe", PCM(0, 1, 64, 1)},
- {"u16", PCM(0, 0, 16, NE)},
- {"s16", PCM(1, 0, 16, NE)},
- {"u24", PCM(0, 0, 24, NE)},
- {"s24", PCM(1, 0, 24, NE)},
- {"u32", PCM(0, 0, 32, NE)},
- {"s32", PCM(1, 0, 32, NE)},
- {"float", PCM(0, 1, 32, NE)},
- {"double", PCM(0, 1, 64, NE)})),
+ {"channels", OPT_CHANNELS(channels), .min = 1},
+ {"rate", OPT_INT(samplerate), M_RANGE(1000, 8 * 48000)},
+ {"format", OPT_CHOICE(aformat,
+ {"u8", PCM(0, 0, 8, 0)},
+ {"s8", PCM(1, 0, 8, 0)},
+ {"u16le", PCM(0, 0, 16, 0)}, {"u16be", PCM(0, 0, 16, 1)},
+ {"s16le", PCM(1, 0, 16, 0)}, {"s16be", PCM(1, 0, 16, 1)},
+ {"u24le", PCM(0, 0, 24, 0)}, {"u24be", PCM(0, 0, 24, 1)},
+ {"s24le", PCM(1, 0, 24, 0)}, {"s24be", PCM(1, 0, 24, 1)},
+ {"u32le", PCM(0, 0, 32, 0)}, {"u32be", PCM(0, 0, 32, 1)},
+ {"s32le", PCM(1, 0, 32, 0)}, {"s32be", PCM(1, 0, 32, 1)},
+ {"floatle", PCM(0, 1, 32, 0)}, {"floatbe", PCM(0, 1, 32, 1)},
+ {"doublele",PCM(0, 1, 64, 0)}, {"doublebe", PCM(0, 1, 64, 1)},
+ {"u16", PCM(0, 0, 16, NE)},
+ {"s16", PCM(1, 0, 16, NE)},
+ {"u24", PCM(0, 0, 24, NE)},
+ {"s24", PCM(1, 0, 24, NE)},
+ {"u32", PCM(0, 0, 32, NE)},
+ {"s32", PCM(1, 0, 32, NE)},
+ {"float", PCM(0, 1, 32, NE)},
+ {"double", PCM(0, 1, 64, NE)})},
{0}
},
.size = sizeof(struct demux_rawaudio_opts),
@@ -107,13 +107,13 @@ struct demux_rawvideo_opts {
#define OPT_BASE_STRUCT struct demux_rawvideo_opts
const struct m_sub_options demux_rawvideo_conf = {
.opts = (const m_option_t[]) {
- OPT_INTRANGE("w", width, 0, 1, 8192),
- OPT_INTRANGE("h", height, 0, 1, 8192),
- OPT_GENERAL(int, "format", vformat, 0, .type = &m_option_type_fourcc),
- OPT_IMAGEFORMAT("mp-format", mp_format, 0),
- OPT_STRING("codec", codec, 0),
- OPT_FLOATRANGE("fps", fps, 0, 0.001, 1000),
- OPT_INTRANGE("size", imgsize, 0, 1, 8192 * 8192 * 4),
+ {"w", OPT_INT(width), M_RANGE(1, 8192)},
+ {"h", OPT_INT(height), M_RANGE(1, 8192)},
+ {"format", OPT_FOURCC(vformat)},
+ {"mp-format", OPT_IMAGEFORMAT(mp_format)},
+ {"codec", OPT_STRING(codec)},
+ {"fps", OPT_FLOAT(fps), M_RANGE(0.001, 1000)},
+ {"size", OPT_INT(imgsize), M_RANGE(1, 8192 * 8192 * 4)},
{0}
},
.size = sizeof(struct demux_rawvideo_opts),
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index a9a762d3e4..9b4a049aee 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -116,6 +116,7 @@ static void associate_streams(struct demuxer *demuxer,
for (int n = 0; n < num_streams; n++) {
struct sh_stream *sh = demux_get_stream(seg->d, n);
struct virtual_stream *other = NULL;
+
for (int i = 0; i < src->num_streams; i++) {
struct virtual_stream *vs = src->streams[i];
@@ -134,6 +135,11 @@ static void associate_streams(struct demuxer *demuxer,
other = vs;
}
+ if (!other) {
+ MP_WARN(demuxer, "Source stream %d (%s) unused and hidden.\n",
+ n, stream_type_name(sh->type));
+ }
+
MP_TARRAY_APPEND(seg, seg->stream_map, seg->num_stream_map, other);
}
}
@@ -255,15 +261,17 @@ static void switch_segment(struct demuxer *demuxer, struct virtual_source *src,
src->eos_packets = 0;
}
-static bool do_read_next_packet(struct demuxer *demuxer,
+static void do_read_next_packet(struct demuxer *demuxer,
struct virtual_source *src)
{
if (src->next)
- return 1;
+ return;
struct segment *seg = src->current;
- if (!seg || !seg->d)
- return 0;
+ if (!seg || !seg->d) {
+ src->eof_reached = true;
+ return;
+ }
struct demux_packet *pkt = demux_read_any_packet(seg->d);
if (!pkt || (!src->no_clip && pkt->pts >= seg->end))
@@ -304,10 +312,10 @@ static bool do_read_next_packet(struct demuxer *demuxer,
}
if (!next) {
src->eof_reached = true;
- return false;
+ return;
}
switch_segment(demuxer, src, next, next->start, 0, true);
- return true; // reader will retry
+ return; // reader will retry
}
if (pkt->stream < 0 || pkt->stream >= seg->num_stream_map)
@@ -352,17 +360,15 @@ static bool do_read_next_packet(struct demuxer *demuxer,
pkt->stream = vs->sh->index;
src->next = pkt;
- return true;
+ return;
drop:
talloc_free(pkt);
- return true;
}
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
{
struct priv *p = demuxer->priv;
-
struct virtual_source *src = NULL;
for (int x = 0; x < p->num_sources; x++) {
@@ -385,8 +391,7 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
if (!src)
return false;
- if (!do_read_next_packet(demuxer, src))
- return false;
+ do_read_next_packet(demuxer, src);
*out_pkt = src->next;
src->next = NULL;
return true;
@@ -460,7 +465,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
for (int x = 0; x < p->num_sources; x++) {
struct virtual_source *src = p->sources[x];
- if (src != master)
+ if (src != master && src->any_selected)
seek_source(demuxer, src, seek_pts, flags);
}
}
@@ -522,6 +527,17 @@ static void apply_meta(struct sh_stream *dst, struct sh_stream *src)
dst->attached_picture = src->attached_picture;
}
+// This is mostly for EDL user-defined metadata.
+static struct sh_stream *find_matching_meta(struct timeline_par *tl, int index)
+{
+ for (int n = 0; n < tl->num_sh_meta; n++) {
+ struct sh_stream *sh = tl->sh_meta[n];
+ if (sh->index == index || sh->index < 0)
+ return sh;
+ }
+ return NULL;
+}
+
static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
{
struct priv *p = demuxer->priv;
@@ -547,7 +563,7 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
// delay_open streams normally have meta==NULL, and 1 virtual stream
int num_streams = 0;
if (tl->delay_open) {
- num_streams = 1;
+ num_streams = tl->num_sh_meta;
} else if (meta) {
num_streams = demux_get_num_stream(meta);
}
@@ -555,9 +571,10 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
struct sh_stream *new = NULL;
if (tl->delay_open) {
- assert(tl->sh_meta);
- new = demux_alloc_sh_stream(tl->sh_meta->type);
- new->codec = tl->sh_meta->codec;
+ struct sh_stream *tsh = tl->sh_meta[n];
+ new = demux_alloc_sh_stream(tsh->type);
+ new->codec = tsh->codec;
+ apply_meta(new, tsh);
demuxer->is_network = true;
demuxer->is_streaming = true;
} else {
@@ -565,11 +582,11 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
new = demux_alloc_sh_stream(sh->type);
apply_meta(new, sh);
new->codec = sh->codec;
+ struct sh_stream *tsh = find_matching_meta(tl, n);
+ if (tsh)
+ apply_meta(new, tsh);
}
- if (tl->sh_meta)
- apply_meta(new, tl->sh_meta);
-
demux_add_sh_stream(demuxer, new);
struct virtual_stream *vs = talloc_ptrtype(p, vs);
*vs = (struct virtual_stream){
diff --git a/demux/timeline.h b/demux/timeline.h
index faeec53b32..93919a5c51 100644
--- a/demux/timeline.h
+++ b/demux/timeline.h
@@ -24,9 +24,11 @@ struct timeline_par {
bstr init_fragment;
bool dash, no_clip, delay_open;
- // If non-NULL, _some_ fields are used. If delay_open==true, this must be
- // set, and the codec info is used.
- struct sh_stream *sh_meta;
+ // Of any of these, _some_ fields are used. If delay_open==true, this
+ // describes each sub-track, and the codec info is used.
+ // In both cases, the metadata is mapped to actual tracks in specific ways.
+ struct sh_stream **sh_meta;
+ int num_sh_meta;
// Segments to play, ordered by time.
struct timeline_part *parts;
diff --git a/etc/input.conf b/etc/input.conf
index 2130ffa847..ba96a77190 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -161,6 +161,7 @@
#MUTE cycle mute
#CLOSE_WIN quit
#CLOSE_WIN {encode} quit 4
+#ctrl+w quit
#E cycle edition # next edition
#l ab-loop # Set/clear A-B loop points
#L cycle-values loop-file "inf" "no" # toggle infinite looping
diff --git a/filters/f_async_queue.c b/filters/f_async_queue.c
new file mode 100644
index 0000000000..696649f3d1
--- /dev/null
+++ b/filters/f_async_queue.c
@@ -0,0 +1,299 @@
+#include <limits.h>
+#include <pthread.h>
+
+#include "audio/aframe.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "osdep/atomic.h"
+
+#include "f_async_queue.h"
+#include "filter_internal.h"
+
+struct mp_async_queue {
+ // This is just a wrapper, so the API user can talloc_free() it, instead of
+ // having to call a special unref function.
+ struct async_queue *q;
+};
+
+struct async_queue {
+ mp_atomic_uint64 refcount;
+
+ pthread_mutex_t lock;
+
+ // -- protected by lock
+ struct mp_async_queue_config cfg;
+ bool active; // queue was resumed; consumer may request frames
+ bool reading; // data flow: reading => consumer has requested frames
+ int64_t samples_size; // queue size in the cfg.sample_unit
+ size_t byte_size; // queue size in bytes (using approx. frame sizes)
+ int num_frames;
+ struct mp_frame *frames;
+ int eof_count; // number of MP_FRAME_EOF in frames[], for draining
+ struct mp_filter *conn[2]; // filters: in (0), out (1)
+};
+
+static void reset_queue(struct async_queue *q)
+{
+ pthread_mutex_lock(&q->lock);
+ q->active = q->reading = false;
+ for (int n = 0; n < q->num_frames; n++)
+ mp_frame_unref(&q->frames[n]);
+ q->num_frames = 0;
+ q->eof_count = 0;
+ q->samples_size = 0;
+ q->byte_size = 0;
+ for (int n = 0; n < 2; n++) {
+ if (q->conn[n])
+ mp_filter_wakeup(q->conn[n]);
+ }
+ pthread_mutex_unlock(&q->lock);
+}
+
+static void unref_queue(struct async_queue *q)
+{
+ if (!q)
+ return;
+ int count = atomic_fetch_add(&q->refcount, -1) - 1;
+ assert(count >= 0);
+ if (count == 0) {
+ reset_queue(q);
+ pthread_mutex_destroy(&q->lock);
+ talloc_free(q);
+ }
+}
+
+static void on_free_queue(void *p)
+{
+ struct mp_async_queue *q = p;
+ unref_queue(q->q);
+}
+
+struct mp_async_queue *mp_async_queue_create(void)
+{
+ struct mp_async_queue *r = talloc_zero(NULL, struct mp_async_queue);
+ r->q = talloc_zero(NULL, struct async_queue);
+ *r->q = (struct async_queue){
+ .refcount = ATOMIC_VAR_INIT(1),
+ };
+ pthread_mutex_init(&r->q->lock, NULL);
+ talloc_set_destructor(r, on_free_queue);
+ mp_async_queue_set_config(r, (struct mp_async_queue_config){0});
+ return r;
+}
+
+static int64_t frame_get_samples(struct async_queue *q, struct mp_frame frame)
+{
+ int64_t res = 1;
+ if (frame.type == MP_FRAME_AUDIO && q->cfg.sample_unit == AQUEUE_UNIT_SAMPLES) {
+ struct mp_aframe *aframe = frame.data;
+ res = mp_aframe_get_size(aframe);
+ }
+ return res;
+}
+
+static bool is_full(struct async_queue *q)
+{
+ if (q->samples_size >= q->cfg.max_samples || q->byte_size >= q->cfg.max_bytes)
+ return true;
+ if (q->num_frames >= 2 && q->cfg.max_duration > 0) {
+ double pts1 = mp_frame_get_pts(q->frames[q->num_frames - 1]);
+ double pts2 = mp_frame_get_pts(q->frames[0]);
+ if (pts1 != MP_NOPTS_VALUE && pts2 != MP_NOPTS_VALUE &&
+ pts2 - pts1 >= q->cfg.max_duration)
+ return true;
+ }
+ return false;
+}
+
+// Add or remove a frame from the accounted queue size.
+// dir==1: add, dir==-1: remove
+static void account_frame(struct async_queue *q, struct mp_frame frame,
+ int dir)
+{
+ assert(dir == 1 || dir == -1);
+
+ q->samples_size += dir * frame_get_samples(q, frame);
+ q->byte_size += dir * mp_frame_approx_size(frame);
+
+ if (frame.type == MP_FRAME_EOF)
+ q->eof_count += dir;
+}
+
+static void recompute_sizes(struct async_queue *q)
+{
+ q->eof_count = 0;
+ q->samples_size = 0;
+ q->byte_size = 0;
+ for (int n = 0; n < q->num_frames; n++)
+ account_frame(q, q->frames[n], 1);
+}
+
+void mp_async_queue_set_config(struct mp_async_queue *queue,
+ struct mp_async_queue_config cfg)
+{
+ struct async_queue *q = queue->q;
+
+ cfg.max_bytes = MPCLAMP(cfg.max_bytes, 1, (size_t)-1 / 2);
+
+ assert(cfg.sample_unit == AQUEUE_UNIT_FRAME ||
+ cfg.sample_unit == AQUEUE_UNIT_SAMPLES);
+
+ cfg.max_samples = MPMAX(cfg.max_samples, 1);
+
+ pthread_mutex_lock(&q->lock);
+ bool recompute = q->cfg.sample_unit != cfg.sample_unit;
+ q->cfg = cfg;
+ if (recompute)
+ recompute_sizes(q);
+ pthread_mutex_unlock(&q->lock);
+}
+
+void mp_async_queue_reset(struct mp_async_queue *queue)
+{
+ reset_queue(queue->q);
+}
+
+void mp_async_queue_resume(struct mp_async_queue *queue)
+{
+ struct async_queue *q = queue->q;
+
+ pthread_mutex_lock(&q->lock);
+ if (!q->active) {
+ q->active = true;
+ // Possibly make the consumer request new frames.
+ if (q->conn[1])
+ mp_filter_wakeup(q->conn[1]);
+ }
+ pthread_mutex_unlock(&q->lock);
+}
+
+struct priv {
+ struct async_queue *q;
+};
+
+static void destroy(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ struct async_queue *q = p->q;
+
+ pthread_mutex_lock(&q->lock);
+ for (int n = 0; n < 2; n++) {
+ if (q->conn[n] == f)
+ q->conn[n] = NULL;
+ }
+ pthread_mutex_unlock(&q->lock);
+
+ unref_queue(q);
+}
+
+static void process_in(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ struct async_queue *q = p->q;
+ assert(q->conn[0] == f);
+
+ pthread_mutex_lock(&q->lock);
+ if (!q->reading) {
+ // mp_async_queue_reset()/reset_queue() is usually called asynchronously,
+ // so we might have requested a frame earlier, and now can't use it.
+ // Discard it; the expectation is that this is a benign logical race
+ // condition, and the filter graph will be reset anyway.
+ if (mp_pin_out_has_data(f->ppins[0])) {
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ mp_frame_unref(&frame);
+ MP_DBG(f, "discarding frame due to async reset\n");
+ }
+ } else if (!is_full(q) && mp_pin_out_request_data(f->ppins[0])) {
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ account_frame(q, frame, 1);
+ MP_TARRAY_INSERT_AT(q, q->frames, q->num_frames, 0, frame);
+ // Notify reader that we have new frames.
+ if (q->conn[1])
+ mp_filter_wakeup(q->conn[1]);
+ if (!is_full(q))
+ mp_pin_out_request_data_next(f->ppins[0]);
+ }
+ pthread_mutex_unlock(&q->lock);
+}
+
+static void process_out(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ struct async_queue *q = p->q;
+ assert(q->conn[1] == f);
+
+ if (!mp_pin_in_needs_data(f->ppins[0]))
+ return;
+
+ pthread_mutex_lock(&q->lock);
+ if (q->active && !q->reading) {
+ q->reading = true;
+ mp_filter_wakeup(q->conn[0]);
+ }
+ if (q->active && q->num_frames) {
+ struct mp_frame frame = q->frames[q->num_frames - 1];
+ q->num_frames -= 1;
+ account_frame(q, frame, -1);
+ assert(q->samples_size >= 0);
+ mp_pin_in_write(f->ppins[0], frame);
+ // Notify writer that we need new frames.
+ if (q->conn[0])
+ mp_filter_wakeup(q->conn[0]);
+ }
+ pthread_mutex_unlock(&q->lock);
+}
+
+static void reset(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ struct async_queue *q = p->q;
+
+ reset_queue(q);
+}
+
+// producer
+static const struct mp_filter_info info_in = {
+ .name = "async_queue_in",
+ .priv_size = sizeof(struct priv),
+ .destroy = destroy,
+ .process = process_in,
+ .reset = reset,
+};
+
+// consumer
+static const struct mp_filter_info info_out = {
+ .name = "async_queue_out",
+ .priv_size = sizeof(struct priv),
+ .destroy = destroy,
+ .process = process_out,
+ .reset = reset,
+};
+
+struct mp_filter *mp_async_queue_create_filter(struct mp_filter *parent,
+ enum mp_pin_dir dir,
+ struct mp_async_queue *queue)
+{
+ bool is_in = dir == MP_PIN_IN;
+ assert(queue);
+
+ struct mp_filter *f = mp_filter_create(parent, is_in ? &info_in : &info_out);
+ if (!f)
+ return NULL;
+
+ struct priv *p = f->priv;
+
+ struct async_queue *q = queue->q;
+
+ mp_filter_add_pin(f, dir, is_in ? "in" : "out");
+
+ atomic_fetch_add(&q->refcount, 1);
+ p->q = q;
+
+ pthread_mutex_lock(&q->lock);
+ int slot = is_in ? 0 : 1;
+ assert(!q->conn[slot]); // fails if already connected on this end
+ q->conn[slot] = f;
+ pthread_mutex_unlock(&q->lock);
+
+ return f;
+}
diff --git a/filters/f_async_queue.h b/filters/f_async_queue.h
new file mode 100644
index 0000000000..6b1ffabe36
--- /dev/null
+++ b/filters/f_async_queue.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "filter.h"
+
+// A thread safe queue, which buffers a configurable number of frames like a
+// FIFO. It's part of the filter framework, and intended to provide such a
+// queue between filters. Since a filter graph can't be used from multiple
+// threads without synchronization, this provides 2 filters, which are
+// implicitly connected. (This seemed much saner than having special thread
+// safe mp_pins or such in the filter framework.)
+struct mp_async_queue;
+
+// Create a blank queue. Can be freed with talloc_free(). To use it, you need
+// to create input and output filters with mp_async_queue_create_filter().
+// Note that freeing it will only unref it. (E.g. you can free it once you've
+// created the input and output filters.)
+struct mp_async_queue *mp_async_queue_create(void);
+
+// Clear all queued data and make the queue "inactive". The latter prevents any
+// further communication until mp_async_queue_resume() is called.
+// For correct operation, you also need to call reset on the access filters
+void mp_async_queue_reset(struct mp_async_queue *queue);
+
+// Put the queue into "active" mode. If it wasn't, then the consumer is woken
+// up (and if there is no data in the queue, this will in turn wake up the
+// producer, i.e. start transfers automatically).
+void mp_async_queue_resume(struct mp_async_queue *queue);
+
+// Create a filter to access the queue, and connect it. It's not allowed to
+// connect an already connected end of the queue. The filter can be freed at
+// any time.
+//
+// The queue starts out in "inactive" mode, where the queue does not allow
+// the producer to write any data. You need to call mp_async_queue_resume() to
+// start communication. Actual transfers happen only once the consumer filter
+// has read requests on its mp_pin.
+// Resetting any of the consumer/producer filters calls mp_async_queue_reset().
+// If the producer filter requested a new frame from its filter graph, and the
+// queue is asynchronously set to "inactive", then the requested frame will be
+// silently discarded once it reaches the producer filter.
+//
+// For proper global reset, this order should be preferred:
+// - mp_async_queue_reset()
+// - reset producer and consumer filters on their respective threads (in any
+// order)
+// - do whatever other reset work is required
+// - mp_async_queue_resume()
+//
+// parent: filter graph the filter should be part of (or for standalone use,
+// create one with mp_filter_create_root())
+// dir: MP_PIN_IN for a filter that writes to the queue, MP_PIN_OUT to read
+// queue: queue to attach to (which end of it depends on dir)
+// The returned filter will have exactly 1 pin with the requested dir.
+struct mp_filter *mp_async_queue_create_filter(struct mp_filter *parent,
+ enum mp_pin_dir dir,
+ struct mp_async_queue *queue);
+
+enum mp_async_queue_sample_unit {
+ AQUEUE_UNIT_FRAME = 0, // a frame counts as 1 sample
+ AQUEUE_UNIT_SAMPLES, // number of audio samples (1 for other media types)
+};
+
+// Setting this struct to all-0 is equivalent to defaults.
+struct mp_async_queue_config {
+ // Maximum size of frames buffered. mp_frame_approx_size() is used. May be
+ // overshot by up to 1 full frame. Clamped to [1, SIZE_MAX/2].
+ int64_t max_bytes;
+
+ // Defines what a "sample" is; affects the fields below.
+ enum mp_async_queue_sample_unit sample_unit;
+
+ // Maximum number of frames allowed to be buffered at a time (if
+ // unit!=AQUEUE_UNIT_FRAME, can be overshot by the contents of 1 mp_frame).
+ // 0 is treated as 1.
+ int64_t max_samples;
+
+ // Maximum allowed timestamp difference between 2 frames. This still allows
+ // at least 2 samples. Behavior is unclear on timestamp resets (even if EOF
+ // frames are between them). A value of 0 disables this completely.
+ double max_duration;
+};
+
+// Configure the queue size. By default, the queue size is 1 frame.
+// The wakeup_threshold_* fields can be used to avoid too frequent wakeups by
+// delaying wakeups, and then making the producer to filter multiple frames at
+// once.
+// In all cases, the filters can still read/write if the producer/consumer got
+// woken up by something else.
+// If the current queue contains more frames than the new config allows, the
+// queue will remain over-allocated until these frames have been read.
+void mp_async_queue_set_config(struct mp_async_queue *queue,
+ struct mp_async_queue_config cfg);
diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c
index 6f90df04ad..3f7fe50aad 100644
--- a/filters/f_decoder_wrapper.c
+++ b/filters/f_decoder_wrapper.c
@@ -15,11 +15,13 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <assert.h>
+#include <pthread.h>
#include <libavutil/buffer.h>
#include <libavutil/common.h>
@@ -30,6 +32,7 @@
#include "common/msg.h"
#include "options/m_config.h"
#include "osdep/timer.h"
+#include "osdep/threads.h"
#include "demux/demux.h"
#include "demux/packet.h"
@@ -37,6 +40,7 @@
#include "common/codecs.h"
#include "common/global.h"
#include "common/recorder.h"
+#include "misc/dispatch.h"
#include "audio/aframe.h"
#include "video/out/vo.h"
@@ -44,18 +48,120 @@
#include "demux/stheader.h"
+#include "f_async_queue.h"
#include "f_decoder_wrapper.h"
#include "f_demux_in.h"
#include "filter_internal.h"
+struct dec_queue_opts {
+ int use_queue;
+ int64_t max_bytes;
+ int64_t max_samples;
+ double max_duration;
+};
+
+#define OPT_BASE_STRUCT struct dec_queue_opts
+
+static const struct m_option dec_queue_opts_list[] = {
+ {"enable", OPT_FLAG(use_queue)},
+ {"max-secs", OPT_DOUBLE(max_duration), M_RANGE(0, DBL_MAX)},
+ {"max-bytes", OPT_BYTE_SIZE(max_bytes), M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"max-samples", OPT_INT64(max_samples), M_RANGE(0, DBL_MAX)},
+ {0}
+};
+
+static const struct m_sub_options vdec_queue_conf = {
+ .opts = dec_queue_opts_list,
+ .size = sizeof(struct dec_queue_opts),
+ .defaults = &(const struct dec_queue_opts){
+ .use_queue = 0,
+ .max_bytes = 512 * 1024 * 1024,
+ .max_samples = 50,
+ .max_duration = 2,
+ },
+};
+
+static const struct m_sub_options adec_queue_conf = {
+ .opts = dec_queue_opts_list,
+ .size = sizeof(struct dec_queue_opts),
+ .defaults = &(const struct dec_queue_opts){
+ .use_queue = 0,
+ .max_bytes = 1 * 1024 * 1024,
+ .max_samples = 48000,
+ .max_duration = 1,
+ },
+};
+
+#undef OPT_BASE_STRUCT
+#define OPT_BASE_STRUCT struct dec_wrapper_opts
+
+struct dec_wrapper_opts {
+ float movie_aspect;
+ int aspect_method;
+ double force_fps;
+ int correct_pts;
+ int video_rotate;
+ char *audio_decoders;
+ char *video_decoders;
+ char *audio_spdif;
+ struct dec_queue_opts *vdec_queue_opts;
+ struct dec_queue_opts *adec_queue_opts;
+ int64_t video_reverse_size;
+ int64_t audio_reverse_size;
+};
+
+static int decoder_list_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param);
+
+const struct m_sub_options dec_wrapper_conf = {
+ .opts = (const struct m_option[]){
+ {"correct-pts", OPT_FLAG(correct_pts)},
+ {"fps", OPT_DOUBLE(force_fps), M_RANGE(0, DBL_MAX)},
+ {"ad", OPT_STRING_VALIDATE(audio_decoders, decoder_list_opt)},
+ {"vd", OPT_STRING_VALIDATE(video_decoders, decoder_list_opt)},
+ {"audio-spdif", OPT_STRING_VALIDATE(audio_spdif, decoder_list_opt)},
+ {"video-rotate", OPT_CHOICE(video_rotate, {"no", -1}),
+ .flags = UPDATE_IMGPAR, M_RANGE(0, 359)},
+ {"video-aspect-override", OPT_ASPECT(movie_aspect),
+ .flags = UPDATE_IMGPAR, M_RANGE(-1, 10)},
+ {"video-aspect-method", OPT_CHOICE(aspect_method,
+ {"bitstream", 1}, {"container", 2}),
+ .flags = UPDATE_IMGPAR},
+ {"vd-queue", OPT_SUBSTRUCT(vdec_queue_opts, vdec_queue_conf)},
+ {"ad-queue", OPT_SUBSTRUCT(adec_queue_opts, adec_queue_conf)},
+ {"video-reversal-buffer", OPT_BYTE_SIZE(video_reverse_size),
+ M_RANGE(0, M_MAX_MEM_BYTES)},
+ {"audio-reversal-buffer", OPT_BYTE_SIZE(audio_reverse_size),
+ M_RANGE(0, M_MAX_MEM_BYTES)} ,
+ {0}
+ },
+ .size = sizeof(struct dec_wrapper_opts),
+ .defaults = &(const struct dec_wrapper_opts){
+ .correct_pts = 1,
+ .movie_aspect = -1.,
+ .aspect_method = 2,
+ .video_reverse_size = 1 * 1024 * 1024 * 1024,
+ .audio_reverse_size = 64 * 1024 * 1024,
+ },
+};
+
struct priv {
- struct mp_filter *f;
struct mp_log *log;
+ struct sh_stream *header;
+
+ // --- The following fields are to be accessed by dec_dispatch (or if that
+ // field is NULL, by the mp_decoder_wrapper user thread).
+ // Use thread_lock() for access outside of the decoder thread.
+
+ bool request_terminate_dec_thread;
+ struct mp_filter *dec_root_filter; // thread root filter; no thread => NULL
+ struct mp_filter *decf; // wrapper filter which drives the decoder
struct m_config_cache *opt_cache;
+ struct dec_wrapper_opts *opts;
+ struct dec_queue_opts *queue_opts;
+ struct mp_stream_info stream_info;
- struct sh_stream *header;
struct mp_codec_params *codec;
-
struct mp_decoder *decoder;
// Demuxer output.
@@ -86,6 +192,8 @@ struct priv {
struct mp_image_params dec_format, last_format, fixed_format;
+ double fps;
+
double start_pts;
double start, end;
struct demux_packet *new_segment;
@@ -100,9 +208,89 @@ struct priv {
struct mp_frame decoded_coverart;
int coverart_returned; // 0: no, 1: coverart frame itself, 2: EOF returned
+ int play_dir;
+
+ // --- The following fields can be accessed only from the mp_decoder_wrapper
+ // user thread.
struct mp_decoder_wrapper public;
+
+ // --- Specific access depending on threading stuff.
+ struct mp_async_queue *queue; // decoded frame output queue
+ struct mp_dispatch_queue *dec_dispatch; // non-NULL if decoding thread used
+ bool dec_thread_lock; // debugging (esp. for no-thread case)
+ pthread_t dec_thread;
+ bool dec_thread_valid;
+ pthread_mutex_t cache_lock;
+
+ // --- Protected by cache_lock.
+ char *cur_hwdec;
+ char *decoder_desc;
+ bool try_spdif;
+ bool pts_reset;
+ int attempt_framedrops; // try dropping this many frames
+ int dropped_frames; // total frames _probably_ dropped
};
+static int decoder_list_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param)
+{
+ if (!bstr_equals0(param, "help"))
+ return 1;
+ if (strcmp(opt->name, "ad") == 0) {
+ struct mp_decoder_list *list = audio_decoder_list();
+ mp_print_decoders(log, MSGL_INFO, "Audio decoders:", list);
+ talloc_free(list);
+ return M_OPT_EXIT;
+ }
+ if (strcmp(opt->name, "vd") == 0) {
+ struct mp_decoder_list *list = video_decoder_list();
+ mp_print_decoders(log, MSGL_INFO, "Video decoders:", list);
+ talloc_free(list);
+ return M_OPT_EXIT;
+ }
+ if (strcmp(opt->name, "audio-spdif") == 0) {
+ mp_info(log, "Choices: ac3,dts-hd,dts (and possibly more)\n");
+ return M_OPT_EXIT;
+ }
+ return 1;
+}
+
+// Update cached values for main thread which require access to the decoder
+// thread state. Must run on/locked with decoder thread.
+static void update_cached_values(struct priv *p)
+{
+ pthread_mutex_lock(&p->cache_lock);
+
+ p->cur_hwdec = NULL;
+ if (p->decoder && p->decoder->control)
+ p->decoder->control(p->decoder->f, VDCTRL_GET_HWDEC, &p->cur_hwdec);
+
+ pthread_mutex_unlock(&p->cache_lock);
+}
+
+// Lock the decoder thread. This may synchronously wait until the decoder thread
+// is done with its current work item (such as waiting for a frame), and thus
+// may block for a while. (I.e. avoid during normal playback.)
+// If no decoder thread is running, this is a no-op, except for some debug stuff.
+static void thread_lock(struct priv *p)
+{
+ if (p->dec_dispatch)
+ mp_dispatch_lock(p->dec_dispatch);
+
+ assert(!p->dec_thread_lock);
+ p->dec_thread_lock = true;
+}
+
+// Undo thread_lock().
+static void thread_unlock(struct priv *p)
+{
+ assert(p->dec_thread_lock);
+ p->dec_thread_lock = false;
+
+ if (p->dec_dispatch)
+ mp_dispatch_unlock(p->dec_dispatch);
+}
+
// This resets only the decoder. Unlike a full reset(), this doesn't imply a
// seek reset. This distinction exists only when using timeline stuff (EDL and
// ordered chapters). timeline stuff needs to reset the decoder state, but keep
@@ -128,15 +316,19 @@ static void reset_decoder(struct priv *p)
mp_filter_reset(p->decoder->f);
}
-static void reset(struct mp_filter *f)
+static void decf_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
+ assert(p->decf == f);
p->pts = MP_NOPTS_VALUE;
p->last_format = p->fixed_format = (struct mp_image_params){0};
- p->public.dropped_frames = 0;
- p->public.attempt_framedrops = 0;
- p->public.pts_reset = false;
+
+ pthread_mutex_lock(&p->cache_lock);
+ p->pts_reset = false;
+ p->attempt_framedrops = 0;
+ p->dropped_frames = 0;
+ pthread_mutex_unlock(&p->cache_lock);
p->coverart_returned = 0;
@@ -153,20 +345,33 @@ int mp_decoder_wrapper_control(struct mp_decoder_wrapper *d,
enum dec_ctrl cmd, void *arg)
{
struct priv *p = d->f->priv;
- if (p->decoder && p->decoder->control)
- return p->decoder->control(p->decoder->f, cmd, arg);
- return CONTROL_UNKNOWN;
+ int res = CONTROL_UNKNOWN;
+ if (cmd == VDCTRL_GET_HWDEC) {
+ pthread_mutex_lock(&p->cache_lock);
+ *(char **)arg = p->cur_hwdec;
+ pthread_mutex_unlock(&p->cache_lock);
+ } else {
+ thread_lock(p);
+ if (p->decoder && p->decoder->control)
+ res = p->decoder->control(p->decoder->f, cmd, arg);
+ update_cached_values(p);
+ thread_unlock(p);
+ }
+ return res;
}
-static void destroy(struct mp_filter *f)
+static void decf_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
+ assert(p->decf == f);
+
if (p->decoder) {
MP_DBG(f, "Uninit decoder.\n");
talloc_free(p->decoder->f);
p->decoder = NULL;
}
- reset(f);
+
+ decf_reset(f);
mp_frame_unref(&p->decoded_coverart);
}
@@ -184,11 +389,8 @@ struct mp_decoder_list *audio_decoder_list(void)
return list;
}
-bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
+static bool reinit_decoder(struct priv *p)
{
- struct priv *p = d->f->priv;
- struct MPOpts *opts = p->opt_cache->opts;
-
if (p->decoder)
talloc_free(p->decoder->f);
p->decoder = NULL;
@@ -196,6 +398,9 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
reset_decoder(p);
p->has_broken_packet_pts = -10; // needs 10 packets to reach decision
+ talloc_free(p->decoder_desc);
+ p->decoder_desc = NULL;
+
const struct mp_decoder_fns *driver = NULL;
struct mp_decoder_list *list = NULL;
char *user_list = NULL;
@@ -203,16 +408,20 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
if (p->codec->type == STREAM_VIDEO) {
driver = &vd_lavc;
- user_list = opts->video_decoders;
+ user_list = p->opts->video_decoders;
fallback = "h264";
} else if (p->codec->type == STREAM_AUDIO) {
driver = &ad_lavc;
- user_list = opts->audio_decoders;
+ user_list = p->opts->audio_decoders;
fallback = "aac";
- if (p->public.try_spdif && p->codec->codec) {
+ pthread_mutex_lock(&p->cache_lock);
+ bool try_spdif = p->try_spdif;
+ pthread_mutex_unlock(&p->cache_lock);
+
+ if (try_spdif && p->codec->codec) {
struct mp_decoder_list *spdif =
- select_spdif_codec(p->codec->codec, opts->audio_spdif);
+ select_spdif_codec(p->codec->codec, p->opts->audio_spdif);
if (spdif->num_entries) {
driver = &ad_spdif;
list = spdif;
@@ -239,11 +448,13 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
struct mp_decoder_entry *sel = &list->entries[n];
MP_VERBOSE(p, "Opening decoder %s\n", sel->decoder);
- p->decoder = driver->create(p->f, p->codec, sel->decoder);
+ p->decoder = driver->create(p->decf, p->codec, sel->decoder);
if (p->decoder) {
- p->public.decoder_desc =
+ pthread_mutex_lock(&p->cache_lock);
+ p->decoder_desc =
talloc_asprintf(p, "%s (%s)", sel->decoder, sel->desc);
- MP_VERBOSE(p, "Selected codec: %s\n", p->public.decoder_desc);
+ MP_VERBOSE(p, "Selected codec: %s\n", p->decoder_desc);
+ pthread_mutex_unlock(&p->cache_lock);
break;
}
@@ -255,10 +466,81 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
p->codec->codec ? p->codec->codec : "<?>");
}
+ update_cached_values(p);
+
talloc_free(list);
return !!p->decoder;
}
+bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
+{
+ struct priv *p = d->f->priv;
+ thread_lock(p);
+ bool res = reinit_decoder(p);
+ thread_unlock(p);
+ return res;
+}
+
+void mp_decoder_wrapper_get_desc(struct mp_decoder_wrapper *d,
+ char *buf, size_t buf_size)
+{
+ struct priv *p = d->f->priv;
+ pthread_mutex_lock(&p->cache_lock);
+ snprintf(buf, buf_size, "%s", p->decoder_desc ? p->decoder_desc : "");
+ pthread_mutex_unlock(&p->cache_lock);
+}
+
+void mp_decoder_wrapper_set_frame_drops(struct mp_decoder_wrapper *d, int num)
+{
+ struct priv *p = d->f->priv;
+ pthread_mutex_lock(&p->cache_lock);
+ p->attempt_framedrops = num;
+ pthread_mutex_unlock(&p->cache_lock);
+}
+
+int mp_decoder_wrapper_get_frames_dropped(struct mp_decoder_wrapper *d)
+{
+ struct priv *p = d->f->priv;
+ pthread_mutex_lock(&p->cache_lock);
+ int res = p->dropped_frames;
+ pthread_mutex_unlock(&p->cache_lock);
+ return res;
+}
+
+double mp_decoder_wrapper_get_container_fps(struct mp_decoder_wrapper *d)
+{
+ struct priv *p = d->f->priv;
+ thread_lock(p);
+ double res = p->fps;
+ thread_unlock(p);
+ return res;
+}
+
+void mp_decoder_wrapper_set_spdif_flag(struct mp_decoder_wrapper *d, bool spdif)
+{
+ struct priv *p = d->f->priv;
+ pthread_mutex_lock(&p->cache_lock);
+ p->try_spdif = spdif;
+ pthread_mutex_unlock(&p->cache_lock);
+}
+
+bool mp_decoder_wrapper_get_pts_reset(struct mp_decoder_wrapper *d)
+{
+ struct priv *p = d->f->priv;
+ pthread_mutex_lock(&p->cache_lock);
+ bool res = p->pts_reset;
+ pthread_mutex_unlock(&p->cache_lock);
+ return res;
+}
+
+void mp_decoder_wrapper_set_play_dir(struct mp_decoder_wrapper *d, int dir)
+{
+ struct priv *p = d->f->priv;
+ thread_lock(p);
+ p->play_dir = dir;
+ thread_unlock(p);
+}
+
static bool is_valid_peak(float sig_peak)
{
return !sig_peak || (sig_peak >= 1 && sig_peak <= 100);
@@ -269,7 +551,7 @@ static void fix_image_params(struct priv *p,
{
struct mp_image_params m = *params;
struct mp_codec_params *c = p->codec;
- struct MPOpts *opts = p->opt_cache->opts;
+ struct dec_wrapper_opts *opts = p->opts;
MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params));
p->dec_format = *params;
@@ -371,11 +653,11 @@ static void crazy_video_pts_stuff(struct priv *p, struct mp_image *mpi)
// Compensate for incorrectly using mpeg-style DTS for avi timestamps.
if (p->decoder && p->decoder->control && p->codec->avi_dts &&
- mpi->pts != MP_NOPTS_VALUE && p->public.fps > 0)
+ mpi->pts != MP_NOPTS_VALUE && p->fps > 0)
{
int delay = -1;
p->decoder->control(p->decoder->f, VDCTRL_GET_BFRAMES, &delay);
- mpi->pts -= MPMAX(delay, 0) / p->public.fps;
+ mpi->pts -= MPMAX(delay, 0) / p->fps;
}
}
@@ -439,14 +721,12 @@ done:
static void correct_video_pts(struct priv *p, struct mp_image *mpi)
{
- struct MPOpts *opts = p->opt_cache->opts;
-
- mpi->pts *= p->public.play_dir;
+ mpi->pts *= p->play_dir;
- if (!opts->correct_pts || mpi->pts == MP_NOPTS_VALUE) {
- double fps = p->public.fps > 0 ? p->public.fps : 25;
+ if (!p->opts->correct_pts || mpi->pts == MP_NOPTS_VALUE) {
+ double fps = p->fps > 0 ? p->fps : 25;
- if (opts->correct_pts) {
+ if (p->opts->correct_pts) {
if (p->has_broken_decoded_pts <= 1) {
MP_WARN(p, "No video PTS! Making something up. Using "
"%f FPS.\n", fps);
@@ -471,7 +751,7 @@ static void correct_video_pts(struct priv *p, struct mp_image *mpi)
static void correct_audio_pts(struct priv *p, struct mp_aframe *aframe)
{
- double dir = p->public.play_dir;
+ double dir = p->play_dir;
double frame_pts = mp_aframe_get_pts(aframe);
double frame_len = mp_aframe_duration(aframe);
@@ -490,8 +770,11 @@ static void correct_audio_pts(struct priv *p, struct mp_aframe *aframe)
// than enough.
if (p->pts != MP_NOPTS_VALUE && diff > 0.1) {
MP_WARN(p, "Invalid audio PTS: %f -> %f\n", p->pts, frame_pts);
- if (diff >= 5)
- p->public.pts_reset = true;
+ if (diff >= 5) {
+ pthread_mutex_lock(&p->cache_lock);
+ p->pts_reset = true;
+ pthread_mutex_unlock(&p->cache_lock);
+ }
}
// Keep the interpolated timestamp if it doesn't deviate more
@@ -520,11 +803,11 @@ static void process_output_frame(struct priv *p, struct mp_frame frame)
fix_image_params(p, &mpi->params);
mpi->params = p->fixed_format;
- mpi->nominal_fps = p->public.fps;
+ mpi->nominal_fps = p->fps;
} else if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
- if (p->public.play_dir < 0 && !mp_aframe_reverse(aframe))
+ if (p->play_dir < 0 && !mp_aframe_reverse(aframe))
MP_ERR(p, "Couldn't reverse audio frame.\n");
correct_audio_pts(p, aframe);
@@ -544,7 +827,7 @@ static bool is_new_segment(struct priv *p, struct mp_frame frame)
struct demux_packet *pkt = frame.data;
return (pkt->segmented && (pkt->start != p->start || pkt->end != p->end ||
pkt->codec != p->codec)) ||
- (p->public.play_dir < 0 && pkt->back_restart && p->packet_fed);
+ (p->play_dir < 0 && pkt->back_restart && p->packet_fed);
}
static void feed_packet(struct priv *p)
@@ -562,7 +845,7 @@ static void feed_packet(struct priv *p)
if (p->packet.type != MP_FRAME_EOF && p->packet.type != MP_FRAME_PACKET) {
MP_ERR(p, "invalid frame type from demuxer\n");
mp_frame_unref(&p->packet);
- mp_filter_internal_mark_failed(p->f);
+ mp_filter_internal_mark_failed(p->decf);
return;
}
}
@@ -590,17 +873,19 @@ static void feed_packet(struct priv *p)
int framedrop_type = 0;
- if (p->public.attempt_framedrops)
+ pthread_mutex_lock(&p->cache_lock);
+ if (p->attempt_framedrops)
framedrop_type = 1;
+ pthread_mutex_unlock(&p->cache_lock);
- if (start_pts != MP_NOPTS_VALUE && packet && p->public.play_dir > 0 &&
+ if (start_pts != MP_NOPTS_VALUE && packet && p->play_dir > 0 &&
packet->pts < start_pts - .005 && !p->has_broken_packet_pts)
framedrop_type = 2;
p->decoder->control(p->decoder->f, VDCTRL_SET_FRAMEDROP, &framedrop_type);
}
- if (p->public.recorder_sink)
+ if (!p->dec_dispatch && p->public.recorder_sink)
mp_recorder_feed_packet(p->public.recorder_sink, packet);
double pkt_pts = packet ? packet->pts : MP_NOPTS_VALUE;
@@ -633,7 +918,7 @@ static void enqueue_backward_frame(struct priv *p, struct mp_frame frame)
bool eof = frame.type == MP_FRAME_EOF;
if (!eof) {
- struct MPOpts *opts = p->opt_cache->opts;
+ struct dec_wrapper_opts *opts = p->opts;
uint64_t queue_size = 0;
switch (p->header->type) {
@@ -659,7 +944,7 @@ static void enqueue_backward_frame(struct priv *p, struct mp_frame frame)
static void read_frame(struct priv *p)
{
- struct mp_pin *pin = p->f->ppins[0];
+ struct mp_pin *pin = p->decf->ppins[0];
struct mp_frame frame = {0};
if (!p->decoder || !mp_pin_in_needs_data(pin))
@@ -691,23 +976,25 @@ static void read_frame(struct priv *p)
if (p->header->attached_picture && frame.type == MP_FRAME_VIDEO) {
p->decoded_coverart = frame;
- mp_filter_internal_mark_progress(p->f);
+ mp_filter_internal_mark_progress(p->decf);
return;
}
- if (p->public.attempt_framedrops) {
+ pthread_mutex_lock(&p->cache_lock);
+ if (p->attempt_framedrops) {
int dropped = MPMAX(0, p->packets_without_output - 1);
- p->public.attempt_framedrops =
- MPMAX(0, p->public.attempt_framedrops - dropped);
- p->public.dropped_frames += dropped;
+ p->attempt_framedrops = MPMAX(0, p->attempt_framedrops - dropped);
+ p->dropped_frames += dropped;
}
+ pthread_mutex_unlock(&p->cache_lock);
+
p->packets_without_output = 0;
if (p->preroll_discard && frame.type != MP_FRAME_EOF) {
double ts = mp_frame_get_pts(frame);
if (ts == MP_NOPTS_VALUE) {
mp_frame_unref(&frame);
- mp_filter_internal_mark_progress(p->f);
+ mp_filter_internal_mark_progress(p->decf);
return;
}
p->preroll_discard = false;
@@ -715,7 +1002,7 @@ static void read_frame(struct priv *p)
bool segment_ended = process_decoded_frame(p, &frame);
- if (p->public.play_dir < 0 && frame.type) {
+ if (p->play_dir < 0 && frame.type) {
enqueue_backward_frame(p, frame);
frame = MP_NO_FRAME;
}
@@ -731,7 +1018,7 @@ static void read_frame(struct priv *p)
if (p->codec != new_segment->codec) {
p->codec = new_segment->codec;
if (!mp_decoder_wrapper_reinit(&p->public))
- mp_filter_internal_mark_failed(p->f);
+ mp_filter_internal_mark_failed(p->decf);
}
p->start = new_segment->start;
@@ -742,11 +1029,11 @@ static void read_frame(struct priv *p)
p->reverse_queue_complete = p->num_reverse_queue > 0;
p->packet = MAKE_FRAME(MP_FRAME_PACKET, new_segment);
- mp_filter_internal_mark_progress(p->f);
+ mp_filter_internal_mark_progress(p->decf);
}
if (!frame.type) {
- mp_filter_internal_mark_progress(p->f); // make it retry
+ mp_filter_internal_mark_progress(p->decf); // make it retry
return;
}
@@ -755,71 +1042,211 @@ output_frame:
mp_pin_in_write(pin, frame);
}
-static void process(struct mp_filter *f)
+static void update_queue_config(struct priv *p)
+{
+ if (!p->queue)
+ return;
+
+ struct mp_async_queue_config cfg = {
+ .max_bytes = p->queue_opts->max_bytes,
+ .sample_unit = AQUEUE_UNIT_SAMPLES,
+ .max_samples = p->queue_opts->max_samples,
+ .max_duration = p->queue_opts->max_duration,
+ };
+ mp_async_queue_set_config(p->queue, cfg);
+}
+
+static void decf_process(struct mp_filter *f)
{
struct priv *p = f->priv;
- m_config_cache_update(p->opt_cache);
+ assert(p->decf == f);
+
+ if (m_config_cache_update(p->opt_cache))
+ update_queue_config(p);
feed_packet(p);
read_frame(p);
}
-static const struct mp_filter_info decode_wrapper_filter = {
+static void *dec_thread(void *ptr)
+{
+ struct priv *p = ptr;
+
+ char *t_name = "?";
+ switch (p->header->type) {
+ case STREAM_VIDEO: t_name = "vdec"; break;
+ case STREAM_AUDIO: t_name = "adec"; break;
+ }
+ mpthread_set_name(t_name);
+
+ while (!p->request_terminate_dec_thread) {
+ mp_filter_graph_run(p->dec_root_filter);
+ update_cached_values(p);
+ mp_dispatch_queue_process(p->dec_dispatch, INFINITY);
+ }
+
+ return NULL;
+}
+
+static void public_f_reset(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ assert(p->public.f == f);
+
+ if (p->queue) {
+ mp_async_queue_reset(p->queue);
+ thread_lock(p);
+ if (p->dec_root_filter)
+ mp_filter_reset(p->dec_root_filter);
+ mp_dispatch_interrupt(p->dec_dispatch);
+ thread_unlock(p);
+ mp_async_queue_resume(p->queue);
+ }
+}
+
+static void public_f_destroy(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+ assert(p->public.f == f);
+
+ if (p->dec_thread_valid) {
+ assert(p->dec_dispatch);
+ thread_lock(p);
+ p->request_terminate_dec_thread = 1;
+ mp_dispatch_interrupt(p->dec_dispatch);
+ thread_unlock(p);
+ pthread_join(p->dec_thread, NULL);
+ p->dec_thread_valid = false;
+ }
+
+ mp_filter_free_children(f);
+
+ talloc_free(p->dec_root_filter);
+ talloc_free(p->queue);
+ pthread_mutex_destroy(&p->cache_lock);
+}
+
+static const struct mp_filter_info decf_filter = {
.name = "decode",
+ .process = decf_process,
+ .reset = decf_reset,
+ .destroy = decf_destroy,
+};
+
+static const struct mp_filter_info decode_wrapper_filter = {
+ .name = "decode_wrapper",
.priv_size = sizeof(struct priv),
- .process = process,
- .reset = reset,
- .destroy = destroy,
+ .reset = public_f_reset,
+ .destroy = public_f_destroy,
};
+static void wakeup_dec_thread(void *ptr)
+{
+ struct priv *p = ptr;
+
+ mp_dispatch_interrupt(p->dec_dispatch);
+}
+
+static void onlock_dec_thread(void *ptr)
+{
+ struct priv *p = ptr;
+
+ mp_filter_graph_interrupt(p->dec_root_filter);
+}
+
struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
struct sh_stream *src)
{
- struct mp_filter *f = mp_filter_create(parent, &decode_wrapper_filter);
- if (!f)
+ struct mp_filter *public_f = mp_filter_create(parent, &decode_wrapper_filter);
+ if (!public_f)
return NULL;
- struct priv *p = f->priv;
- struct mp_decoder_wrapper *w = &p->public;
- p->opt_cache = m_config_cache_alloc(p, f->global, &mp_opt_root);
- p->log = f->log;
- p->f = f;
+ struct priv *p = public_f->priv;
+ p->public.f = public_f;
+
+ pthread_mutex_init(&p->cache_lock, NULL);
+ p->opt_cache = m_config_cache_alloc(p, public_f->global, &dec_wrapper_conf);
+ p->opts = p->opt_cache->opts;
p->header = src;
p->codec = p->header->codec;
- w->f = f;
-
- w->play_dir = 1;
-
- struct MPOpts *opts = p->opt_cache->opts;
-
- mp_filter_add_pin(f, MP_PIN_OUT, "out");
+ p->play_dir = 1;
+ mp_filter_add_pin(public_f, MP_PIN_OUT, "out");
if (p->header->type == STREAM_VIDEO) {
- p->log = f->log = mp_log_new(f, parent->log, "!vd");
+ p->log = mp_log_new(p, public_f->log, "!vd");
- p->public.fps = src->codec->fps;
+ p->fps = src->codec->fps;
- MP_VERBOSE(p, "Container reported FPS: %f\n", p->public.fps);
+ MP_VERBOSE(p, "Container reported FPS: %f\n", p->fps);
- if (opts->force_fps) {
- p->public.fps = opts->force_fps;
- MP_INFO(p, "FPS forced to %5.3f.\n", p->public.fps);
+ if (p->opts->force_fps) {
+ p->fps = p->opts->force_fps;
+ MP_INFO(p, "FPS forced to %5.3f.\n", p->fps);
MP_INFO(p, "Use --no-correct-pts to force FPS based timing.\n");
}
+
+ p->queue_opts = p->opts->vdec_queue_opts;
} else if (p->header->type == STREAM_AUDIO) {
- p->log = f->log = mp_log_new(f, parent->log, "!ad");
+ p->log = mp_log_new(p, public_f->log, "!ad");
+ p->queue_opts = p->opts->adec_queue_opts;
+ } else {
+ goto error;
+ }
+
+ if (p->queue_opts && p->queue_opts->use_queue) {
+ p->queue = mp_async_queue_create();
+ p->dec_dispatch = mp_dispatch_create(p);
+ p->dec_root_filter = mp_filter_create_root(public_f->global);
+ mp_filter_graph_set_wakeup_cb(p->dec_root_filter, wakeup_dec_thread, p);
+ mp_dispatch_set_onlock_fn(p->dec_dispatch, onlock_dec_thread, p);
+
+ struct mp_stream_info *sinfo = mp_filter_find_stream_info(parent);
+ if (sinfo) {
+ p->dec_root_filter->stream_info = &p->stream_info;
+ p->stream_info = (struct mp_stream_info){
+ .dr_vo = sinfo->dr_vo,
+ .hwdec_devs = sinfo->hwdec_devs,
+ };
+ }
+
+ update_queue_config(p);
}
- struct mp_filter *demux = mp_demux_in_create(f, p->header);
+ p->decf = mp_filter_create(p->dec_root_filter ? p->dec_root_filter : public_f,
+ &decf_filter);
+ p->decf->priv = p;
+ p->decf->log = public_f->log = p->log;
+ mp_filter_add_pin(p->decf, MP_PIN_OUT, "out");
+
+ struct mp_filter *demux = mp_demux_in_create(p->decf, p->header);
if (!demux)
goto error;
p->demux = demux->pins[0];
- reset(f);
+ decf_reset(p->decf);
+
+ if (p->queue) {
+ struct mp_filter *f_in =
+ mp_async_queue_create_filter(public_f, MP_PIN_OUT, p->queue);
+ struct mp_filter *f_out =
+ mp_async_queue_create_filter(p->decf, MP_PIN_IN, p->queue);
+ mp_pin_connect(public_f->ppins[0], f_in->pins[0]);
+ mp_pin_connect(f_out->pins[0], p->decf->pins[0]);
+
+ p->dec_thread_valid = true;
+ if (pthread_create(&p->dec_thread, NULL, dec_thread, p)) {
+ p->dec_thread_valid = false;
+ goto error;
+ }
+ } else {
+ mp_pin_connect(public_f->ppins[0], p->decf->pins[0]);
+ }
+
+ public_f_reset(public_f);
- return w;
+ return &p->public;
error:
- talloc_free(f);
+ talloc_free(public_f);
return NULL;
}
diff --git a/filters/f_decoder_wrapper.h b/filters/f_decoder_wrapper.h
index 28d9b5cb7b..51badaaabd 100644
--- a/filters/f_decoder_wrapper.h
+++ b/filters/f_decoder_wrapper.h
@@ -32,29 +32,8 @@ struct mp_decoder_wrapper {
// Filter with no input and 1 output, which returns the decoded data.
struct mp_filter *f;
- // For informational purposes.
- char *decoder_desc;
-
// Can be set by user.
struct mp_recorder_sink *recorder_sink;
- int play_dir;
-
- // --- for STREAM_VIDEO
-
- // FPS from demuxer or from user override
- float fps;
-
- // Framedrop control for playback (not used for hr seek etc.)
- int attempt_framedrops; // try dropping this many frames
- int dropped_frames; // total frames _probably_ dropped
-
- // --- for STREAM_AUDIO
-
- // Prefer spdif wrapper over real decoders.
- bool try_spdif;
-
- // A pts reset was observed (audio only, heuristic).
- bool pts_reset;
};
// Create the decoder wrapper for the given stream, plus underlying decoder.
@@ -63,6 +42,24 @@ struct mp_decoder_wrapper {
struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
struct sh_stream *src);
+// For informational purposes.
+void mp_decoder_wrapper_get_desc(struct mp_decoder_wrapper *d,
+ char *buf, size_t buf_size);
+
+// Legacy decoder framedrop control.
+void mp_decoder_wrapper_set_frame_drops(struct mp_decoder_wrapper *d, int num);
+int mp_decoder_wrapper_get_frames_dropped(struct mp_decoder_wrapper *d);
+
+double mp_decoder_wrapper_get_container_fps(struct mp_decoder_wrapper *d);
+
+// Whether to prefer spdif wrapper over real decoders on next reinit.
+void mp_decoder_wrapper_set_spdif_flag(struct mp_decoder_wrapper *d, bool spdif);
+
+// True if a pts reset was observed (audio only, heuristic).
+bool mp_decoder_wrapper_get_pts_reset(struct mp_decoder_wrapper *d);
+
+void mp_decoder_wrapper_set_play_dir(struct mp_decoder_wrapper *d, int dir);
+
struct mp_decoder_list *video_decoder_list(void);
struct mp_decoder_list *audio_decoder_list(void);
diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c
index 71624a449b..9e64215f39 100644
--- a/filters/f_lavfi.c
+++ b/filters/f_lavfi.c
@@ -1091,9 +1091,9 @@ const struct mp_user_filter_entry af_lavfi = {
.name = "lavfi",
.priv_size = sizeof(OPT_BASE_STRUCT),
.options = (const m_option_t[]){
- OPT_STRING("graph", graph, M_OPT_MIN, .min = 1),
- OPT_FLAG("fix-pts", fix_pts, 0),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"graph", OPT_STRING(graph)},
+ {"fix-pts", OPT_FLAG(fix_pts)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
.priv_defaults = &(const OPT_BASE_STRUCT){
@@ -1110,9 +1110,9 @@ const struct mp_user_filter_entry af_lavfi_bridge = {
.name = "lavfi-bridge",
.priv_size = sizeof(OPT_BASE_STRUCT),
.options = (const m_option_t[]){
- OPT_STRING("name", filter_name, M_OPT_MIN, .min = 1),
- OPT_KEYVALUELIST("opts", filter_opts, 0),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"name", OPT_STRING(filter_name)},
+ {"opts", OPT_KEYVALUELIST(filter_opts)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
.priv_defaults = &(const OPT_BASE_STRUCT){
@@ -1130,8 +1130,8 @@ const struct mp_user_filter_entry vf_lavfi = {
.name = "lavfi",
.priv_size = sizeof(OPT_BASE_STRUCT),
.options = (const m_option_t[]){
- OPT_STRING("graph", graph, M_OPT_MIN, .min = 1),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"graph", OPT_STRING(graph)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
.priv_defaults = &(const OPT_BASE_STRUCT){
@@ -1148,9 +1148,9 @@ const struct mp_user_filter_entry vf_lavfi_bridge = {
.name = "lavfi-bridge",
.priv_size = sizeof(OPT_BASE_STRUCT),
.options = (const m_option_t[]){
- OPT_STRING("name", filter_name, M_OPT_MIN, .min = 1),
- OPT_KEYVALUELIST("opts", filter_opts, 0),
- OPT_KEYVALUELIST("o", avopts, 0),
+ {"name", OPT_STRING(filter_name)},
+ {"opts", OPT_KEYVALUELIST(filter_opts)},
+ {"o", OPT_KEYVALUELIST(avopts)},
{0}
},
.priv_defaults = &(const OPT_BASE_STRUCT){
diff --git a/filters/f_swresample.c b/filters/f_swresample.c
index 0f4033b26a..4f26d82a59 100644
--- a/filters/f_swresample.c
+++ b/filters/f_swresample.c
@@ -71,14 +71,13 @@ struct priv {
#define OPT_BASE_STRUCT struct mp_resample_opts
const struct m_sub_options resample_conf = {
.opts = (const m_option_t[]) {
- OPT_INTRANGE("audio-resample-filter-size", filter_size, 0, 0, 32),
- OPT_INTRANGE("audio-resample-phase-shift", phase_shift, 0, 0, 30),
- OPT_FLAG("audio-resample-linear", linear, 0),
- OPT_DOUBLE("audio-resample-cutoff", cutoff, M_OPT_RANGE,
- .min = 0, .max = 1),
- OPT_FLAG("audio-normalize-downmix", normalize, 0),
- OPT_DOUBLE("audio-resample-max-output-size", max_output_frame_size, 0),
- OPT_KEYVALUELIST("audio-swresample-o", avopts, 0),
+ {"audio-resample-filter-size", OPT_INT(filter_size), M_RANGE(0, 32)},
+ {"audio-resample-phase-shift", OPT_INT(phase_shift), M_RANGE(0, 30)},
+ {"audio-resample-linear", OPT_FLAG(linear)},
+ {"audio-resample-cutoff", OPT_DOUBLE(cutoff), M_RANGE(0, 1)},
+ {"audio-normalize-downmix", OPT_FLAG(normalize)},
+ {"audio-resample-max-output-size", OPT_DOUBLE(max_output_frame_size)},
+ {"audio-swresample-o", OPT_KEYVALUELIST(avopts)},
{0}
},
.size = sizeof(struct mp_resample_opts),
diff --git a/filters/filter.c b/filters/filter.c
index beec13c210..a60df10616 100644
--- a/filters/filter.c
+++ b/filters/filter.c
@@ -1,8 +1,11 @@
+#include <math.h>
#include <pthread.h>
#include "common/common.h"
#include "common/global.h"
#include "common/msg.h"
+#include "osdep/atomic.h"
+#include "osdep/timer.h"
#include "video/hwdec.h"
#include "filter.h"
@@ -65,6 +68,9 @@ struct filter_runner {
struct mp_filter *root_filter;
+ double max_run_time;
+ atomic_bool interrupt_flag;
+
// If we're currently running the filter graph (for avoiding recursion).
bool filtering;
@@ -133,10 +139,10 @@ static void add_pending(struct mp_filter *f)
// Possibly enter recursive filtering. This is done as convenience for
// "external" filter users only. (Normal filtering does this iteratively via
-// mp_filter_run() to avoid filter reentrancy issues and deep call stacks.) If
-// the API users uses an external manually connected pin, do recursive filtering
-// as a not strictly necessary feature which makes outside I/O with filters
-// easier.
+// mp_filter_graph_run() to avoid filter reentrancy issues and deep call
+// stacks.) If the API users uses an external manually connected pin, do
+// recursive filtering as a not strictly necessary feature which makes outside
+// I/O with filters easier.
static void filter_recursive(struct mp_filter *f)
{
assert(f);
@@ -148,7 +154,7 @@ static void filter_recursive(struct mp_filter *f)
// Also don't lose the pending state, which the user may or may not
// care about.
- r->external_pending |= mp_filter_run(r->root_filter);
+ r->external_pending |= mp_filter_graph_run(r->root_filter);
}
void mp_filter_internal_mark_progress(struct mp_filter *f)
@@ -173,9 +179,14 @@ static void flush_async_notifications(struct filter_runner *r)
pthread_mutex_unlock(&r->async_lock);
}
-bool mp_filter_run(struct mp_filter *filter)
+bool mp_filter_graph_run(struct mp_filter *filter)
{
struct filter_runner *r = filter->in->runner;
+ assert(filter == r->root_filter); // user is supposed to call this on root only
+
+ int64_t end_time = 0;
+ if (isfinite(r->max_run_time))
+ end_time = mp_add_timeout(mp_time_us(), MPMAX(r->max_run_time, 0));
// (could happen with separate filter graphs calling each other, for now
// ignore this issue as we don't use such a setup anywhere)
@@ -183,15 +194,34 @@ bool mp_filter_run(struct mp_filter *filter)
r->filtering = true;
+ // Note: some filters may call mp_filter_wakeup() from process on themselves
+ // to queue a wakeup again later. So do not call this in the loop.
flush_async_notifications(r);
- while (r->num_pending) {
+ while (1) {
+ if (atomic_exchange_explicit(&r->interrupt_flag, false,
+ memory_order_acq_rel))
+ {
+ pthread_mutex_lock(&r->async_lock);
+ if (!r->async_wakeup_sent && r->wakeup_cb)
+ r->wakeup_cb(r->wakeup_ctx);
+ r->async_wakeup_sent = true;
+ pthread_mutex_unlock(&r->async_lock);
+ break;
+ }
+
+ if (!r->num_pending)
+ break;
+
struct mp_filter *next = r->pending[r->num_pending - 1];
r->num_pending -= 1;
next->in->pending = false;
if (next->in->info->process)
next->in->info->process(next);
+
+ if (end_time && mp_time_us() >= end_time)
+ mp_filter_graph_interrupt(r->root_filter);
}
r->filtering = false;
@@ -311,6 +341,8 @@ static struct mp_pin *find_connected_end(struct mp_pin *p)
// state flags.
static void init_connection(struct mp_pin *p)
{
+ struct filter_runner *runner = p->owner->in->runner;
+
if (p->dir == MP_PIN_IN)
p = p->other;
@@ -321,6 +353,12 @@ static void init_connection(struct mp_pin *p)
assert(!in->user_conn);
assert(!out->user_conn);
+ // This and similar checks enforce the same root filter requirement.
+ if (in->manual_connection)
+ assert(in->manual_connection->in->runner == runner);
+ if (out->manual_connection)
+ assert(out->manual_connection->in->runner == runner);
+
// Logicaly, the ends are always manual connections. A pin chain without
// manual connections at the ends is still disconnected (or if this
// attempted to extend an existing connection, becomes dangling and gets
@@ -339,6 +377,7 @@ static void init_connection(struct mp_pin *p)
assert(!cur->data.type); // unused for in pins
assert(!cur->other->data_requested); // unset for unconnected out pins
assert(!cur->other->data.type); // unset for unconnected out pins
+ assert(cur->owner->in->runner == runner);
cur->within_conn = cur->other->within_conn = true;
cur = cur->other->user_conn;
}
@@ -614,11 +653,11 @@ static void filter_wakeup(struct mp_filter *f, bool mark_only)
f->in->async_pending = true;
// (not using a talloc parent for thread safety reasons)
MP_TARRAY_APPEND(NULL, r->async_pending, r->num_async_pending, f);
- if (!mark_only && !r->async_wakeup_sent) {
- if (r->wakeup_cb)
- r->wakeup_cb(r->wakeup_ctx);
- r->async_wakeup_sent = true;
- }
+ }
+ if (!mark_only && !r->async_wakeup_sent) {
+ if (r->wakeup_cb)
+ r->wakeup_cb(r->wakeup_ctx);
+ r->async_wakeup_sent = true;
}
pthread_mutex_unlock(&r->async_lock);
}
@@ -633,6 +672,20 @@ void mp_filter_mark_async_progress(struct mp_filter *f)
filter_wakeup(f, true);
}
+void mp_filter_graph_set_max_run_time(struct mp_filter *f, double seconds)
+{
+ struct filter_runner *r = f->in->runner;
+ assert(f == r->root_filter); // user is supposed to call this on root only
+ r->max_run_time = seconds;
+}
+
+void mp_filter_graph_interrupt(struct mp_filter *f)
+{
+ struct filter_runner *r = f->in->runner;
+ assert(f == r->root_filter); // user is supposed to call this on root only
+ atomic_store(&r->interrupt_flag, true);
+}
+
void mp_filter_free_children(struct mp_filter *f)
{
while(f->in->num_children)
@@ -706,6 +759,7 @@ struct mp_filter *mp_filter_create_with_params(struct mp_filter_params *params)
*f->in->runner = (struct filter_runner){
.global = params->global,
.root_filter = f,
+ .max_run_time = INFINITY,
};
pthread_mutex_init(&f->in->runner->async_lock, NULL);
}
@@ -758,10 +812,11 @@ struct mp_filter *mp_filter_create_root(struct mpv_global *global)
return mp_filter_create_with_params(&params);
}
-void mp_filter_root_set_wakeup_cb(struct mp_filter *root,
- void (*wakeup_cb)(void *ctx), void *ctx)
+void mp_filter_graph_set_wakeup_cb(struct mp_filter *root,
+ void (*wakeup_cb)(void *ctx), void *ctx)
{
struct filter_runner *r = root->in->runner;
+ assert(root == r->root_filter); // user is supposed to call this on root only
pthread_mutex_lock(&r->async_lock);
r->wakeup_cb = wakeup_cb;
r->wakeup_ctx = ctx;
diff --git a/filters/filter.h b/filters/filter.h
index 5e09a17ee7..ddd3f27c3c 100644
--- a/filters/filter.h
+++ b/filters/filter.h
@@ -204,10 +204,10 @@ const char *mp_pin_get_name(struct mp_pin *p);
* --- Driving filters:
*
* The filter root (created by mp_filter_create_root()) will internally create
- * a graph runner, that can be entered with mp_filter_run(). This will check if
- * any filter/pin has unhandled requests, and call filter process() functions
- * accordingly. Outside of the filter, this can be triggered implicitly via the
- * mp_pin_* functions.
+ * a graph runner, that can be entered with mp_filter_graph_run(). This will
+ * check if any filter/pin has unhandled requests, and call filter process()
+ * functions accordingly. Outside of the filter, this can be triggered
+ * implicitly via the mp_pin_* functions.
*
* Multiple filters are driven by letting mp_pin flag filters which need
* process() to be called. The process starts by requesting output from the
@@ -217,12 +217,6 @@ const char *mp_pin_get_name(struct mp_pin *p);
* call the first filter's process() function, which will filter and output
* the frame, and the frame is iteratively filtered until it reaches the output.
*
- * (The mp_pin_* calls can recursively start filtering, but this is only the
- * case if you access a separate graph with a different filter root. Most
- * importantly, calling them from outside the filter's process() function (e.g.
- * an outside filter user) will enter filtering. Within the filter, mp_pin_*
- * will usually only set or query flags.)
- *
* --- General rules for thread safety:
*
* Filters are by default not thread safe. However, some filters can be
@@ -231,6 +225,14 @@ const char *mp_pin_get_name(struct mp_pin *p);
* for some utility functions explicitly marked as such, and which are meant
* to make implementing threaded filters easier.
*
+ * (Semi-)automatic filter communication such as pins must always be within the
+ * same root filter. This is meant to help with ensuring thread-safety. Every
+ * thread that wants to run filters "on its own" should use a different filter
+ * graph, and disallowing different root filters ensures these graphs are not
+ * accidentally connected using non-thread safe mechanisms. Actual threaded
+ * filter graphs would use several independent graphs connected by asynchronous
+ * helpers (such as mp_async_queue instead of mp_pin connections).
+ *
* --- Rules for manual connections:
*
* A pin can be marked for manual connection via mp_pin_set_manual_connection().
@@ -272,6 +274,11 @@ const char *mp_pin_get_name(struct mp_pin *p);
* For running parts of a filter graph on a different thread, f_async_queue.h
* can be used.
*
+ * With different filter graphs working asynchronously, reset handling and start
+ * of filtering becomes more difficult. Since filtering is always triggered by
+ * requesting output from a filter, a simple way to solve this is to trigger
+ * resets from the consumer, and to synchronously reset the producer.
+ *
* --- Format conversions and mid-stream format changes:
*
* Generally, all filters must support all formats, as well as mid-stream
@@ -402,29 +409,56 @@ struct AVBufferRef *mp_filter_load_hwdec_device(struct mp_filter *f, int avtype)
// Perform filtering. This runs until the filter graph is blocked (due to
// missing external input or unread output). It returns whether any outside
// pins have changed state.
-// Does not perform recursive filtering to connected filters with different
-// root filter, though it notifies them.
-bool mp_filter_run(struct mp_filter *f);
+// Can be called on the root filter only.
+bool mp_filter_graph_run(struct mp_filter *root);
+
+// Set the maximum time mp_filter_graph_run() should block. If the maximum time
+// expires, the effect is the same as calling mp_filter_graph_interrupt() while
+// the function is running. See that function for further details.
+// The default is seconds==INFINITY. Values <=0 make it return after 1 iteration.
+// Can be called on the root filter only.
+void mp_filter_graph_set_max_run_time(struct mp_filter *root, double seconds);
+
+// Interrupt mp_filter_graph_run() asynchronously. This does not stop filtering
+// in a destructive way, but merely suspends it. In practice, this will make
+// mp_filter_graph_run() return after the current filter's process() function has
+// finished. Filtering can be resumed with subsequent mp_filter_graph_run() calls.
+// When mp_filter_graph_run() is interrupted, it will trigger the filter graph
+// wakeup callback, which in turn ensures that the user will call
+// mp_filter_graph_run() again.
+// If it is called if not in mp_filter_graph_run(), the next mp_filter_graph_run()
+// call is interrupted and no filtering is done for that call.
+// Calling this too often will starve filtering.
+// This does not call the graph wakeup callback directly, which will avoid
+// potential reentrancy issues. (But mp_filter_graph_run() will call it in
+// reaction to it, as described above.)
+// Explicitly thread-safe.
+// Can be called on the root filter only.
+void mp_filter_graph_interrupt(struct mp_filter *root);
// Create a root dummy filter with no inputs or outputs. This fulfills the
// following functions:
+// - creating a new filter graph (attached to the root filter)
// - passing it as parent filter to top-level filters
// - driving the filter loop between the shared filters
// - setting the wakeup callback for async filtering
// - implicitly passing down global data like mpv_global and keeping filter
// constructor functions simple
// Note that you can still connect pins of filters with different parents or
-// root filters, but then you may have to manually invoke mp_filter_run() on
-// the root filters of the connected filters to drive data flow.
+// root filters, but then you may have to manually invoke mp_filter_graph_run()
+// on the root filters of the connected filters to drive data flow.
struct mp_filter *mp_filter_create_root(struct mpv_global *global);
// Asynchronous filters may need to wakeup the user thread if the status of any
// mp_pin has changed. If this is called, the callback provider should get the
-// user's thread to call mp_filter_run() again.
+// user's thread to call mp_filter_graph_run() again.
// The wakeup callback must not recursively call into any filter APIs, or do
// blocking waits on the filter API (deadlocks will happen).
-void mp_filter_root_set_wakeup_cb(struct mp_filter *root,
- void (*wakeup_cb)(void *ctx), void *ctx);
+// A wakeup callback should always set a "wakeup" flag, that is reset only when
+// mp_filter_graph_run() is going to be called again with no wait time.
+// Can be called on the root filter only.
+void mp_filter_graph_set_wakeup_cb(struct mp_filter *root,
+ void (*wakeup_cb)(void *ctx), void *ctx);
// Debugging internal stuff.
void mp_filter_dump_states(struct mp_filter *f);
diff --git a/filters/filter_internal.h b/filters/filter_internal.h
index bd901db7d8..fe108e7470 100644
--- a/filters/filter_internal.h
+++ b/filters/filter_internal.h
@@ -119,8 +119,6 @@ struct mp_filter_info {
// automatically free'd.
// All filters in the same parent tree must be driven in the same thread (or be
// explicitly synchronized otherwise).
-// Driving the parent (or root) filter with mp_filter_run() will make sure this
-// filter is driven too, without having to resort to recursion.
struct mp_filter *mp_filter_create(struct mp_filter *parent,
const struct mp_filter_info *info);
diff --git a/filters/user_filters.c b/filters/user_filters.c
index e9ccec507d..72a2ab892c 100644
--- a/filters/user_filters.c
+++ b/filters/user_filters.c
@@ -4,7 +4,7 @@
#include "common/common.h"
#include "common/msg.h"
-#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "f_lavfi.h"
#include "user_filters.h"
diff --git a/input/cmd.c b/input/cmd.c
index 5a073d9fec..692b9f5ad5 100644
--- a/input/cmd.c
+++ b/input/cmd.c
@@ -544,6 +544,15 @@ mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
return ret;
}
+static int get_arg_count(const struct mp_cmd_def *cmd)
+{
+ for (int i = MP_CMD_DEF_MAX_ARGS - 1; i >= 0; i--) {
+ if (cmd->args[i].type)
+ return i + 1;
+ }
+ return 0;
+}
+
void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
{
if (!mp_msg_test(log, msgl))
@@ -555,7 +564,9 @@ void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
return;
}
mp_msg(log, msgl, "%s, flags=%d, args=[", cmd->name, cmd->flags);
+ int argc = get_arg_count(cmd->def);
for (int n = 0; n < cmd->nargs; n++) {
+ const char *argname = cmd->def->args[MPMIN(n, argc - 1)].name;
char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v);
if (n)
mp_msg(log, msgl, ", ");
@@ -565,7 +576,7 @@ void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
};
char *esc = NULL;
json_write(&esc, &node);
- mp_msg(log, msgl, "%s", esc ? esc : "<error>");
+ mp_msg(log, msgl, "%s=%s", argname, esc ? esc : "<error>");
talloc_free(esc);
talloc_free(s);
}
diff --git a/input/cmd.h b/input/cmd.h
index c22672b9d3..3ed07e8028 100644
--- a/input/cmd.h
+++ b/input/cmd.h
@@ -152,7 +152,4 @@ struct mp_cmd *mp_cmd_clone(struct mp_cmd *cmd);
extern const struct m_option_type m_option_type_cycle_dir;
-#define OPT_CYCLEDIR(...) \
- OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_cycle_dir)
-
#endif
diff --git a/input/input.c b/input/input.c
index f9475648b0..a4bb8e4543 100644
--- a/input/input.c
+++ b/input/input.c
@@ -183,26 +183,27 @@ struct input_opts {
const struct m_sub_options input_config = {
.opts = (const m_option_t[]) {
- OPT_STRING("input-conf", config_file, M_OPT_FILE),
- OPT_INT("input-ar-delay", ar_delay, 0),
- OPT_INT("input-ar-rate", ar_rate, 0),
- OPT_PRINT("input-keylist", mp_print_key_list),
- OPT_PRINT("input-cmdlist", mp_print_cmd_list),
- OPT_FLAG("input-default-bindings", default_bindings, 0),
- OPT_FLAG("input-test", test, 0),
- OPT_INTRANGE("input-doubleclick-time", doubleclick_time, 0, 0, 1000),
- OPT_FLAG("input-right-alt-gr", use_alt_gr, 0),
- OPT_INTRANGE("input-key-fifo-size", key_fifo_size, 0, 2, 65000),
- OPT_FLAG("input-cursor", enable_mouse_movements, 0),
- OPT_FLAG("input-vo-keyboard", vo_key_input, 0),
- OPT_FLAG("input-media-keys", use_media_keys, 0),
+ {"input-conf", OPT_STRING(config_file), .flags = M_OPT_FILE},
+ {"input-ar-delay", OPT_INT(ar_delay)},
+ {"input-ar-rate", OPT_INT(ar_rate)},
+ {"input-keylist", OPT_PRINT(mp_print_key_list)},
+ {"input-cmdlist", OPT_PRINT(mp_print_cmd_list)},
+ {"input-default-bindings", OPT_FLAG(default_bindings)},
+ {"input-test", OPT_FLAG(test)},
+ {"input-doubleclick-time", OPT_INT(doubleclick_time),
+ M_RANGE(0, 1000)},
+ {"input-right-alt-gr", OPT_FLAG(use_alt_gr)},
+ {"input-key-fifo-size", OPT_INT(key_fifo_size), M_RANGE(2, 65000)},
+ {"input-cursor", OPT_FLAG(enable_mouse_movements)},
+ {"input-vo-keyboard", OPT_FLAG(vo_key_input)},
+ {"input-media-keys", OPT_FLAG(use_media_keys)},
#if HAVE_SDL2_GAMEPAD
- OPT_FLAG("input-gamepad", use_gamepad, 0),
+ {"input-gamepad", OPT_FLAG(use_gamepad)},
#endif
- OPT_FLAG("window-dragging", allow_win_drag, 0),
- OPT_REPLACED("input-x11-keyboard", "input-vo-keyboard"),
+ {"window-dragging", OPT_FLAG(allow_win_drag)},
+ {"input-x11-keyboard", OPT_REPLACED("input-vo-keyboard")},
#if HAVE_COCOA
- OPT_REMOVED("input-appleremote", "replaced by MediaPlayer support"),
+ {"input-appleremote", OPT_REMOVED("replaced by MediaPlayer support")},
#endif
{0}
},
@@ -1378,14 +1379,6 @@ void mp_input_load_config(struct input_ctx *ictx)
talloc_free(tmp);
}
-#if HAVE_WIN32_PIPES
- char *ifile;
- mp_read_option_raw(ictx->global, "input-file", &m_option_type_string, &ifile);
- if (ifile && ifile[0])
- mp_input_pipe_add(ictx, ifile);
- talloc_free(ifile);
-#endif
-
#if HAVE_SDL2_GAMEPAD
if (ictx->opts->use_gamepad) {
mp_input_sdl_gamepad_add(ictx);
diff --git a/input/input.h b/input/input.h
index df51cb7ed4..82e7adc503 100644
--- a/input/input.h
+++ b/input/input.h
@@ -207,16 +207,24 @@ void mp_input_set_repeat_info(struct input_ctx *ictx, int rate, int delay);
struct mpv_node mp_input_get_bindings(struct input_ctx *ictx);
-void mp_input_pipe_add(struct input_ctx *ictx, const char *filename);
-
void mp_input_sdl_gamepad_add(struct input_ctx *ictx);
struct mp_ipc_ctx;
struct mp_client_api;
+struct mpv_handle;
// Platform specific implementation, provided by ipc-*.c.
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
struct mpv_global *global);
+// Start a thread for the given handle and return a socket in out_fd[0] that
+// is served by this thread. If the FD is not full-duplex, then out_fd[0] is
+// the user's read-end, and out_fd[1] the write-end, otherwise out_fd[1] is set
+// to -1.
+// returns:
+// true: out_fd[0] and out_fd[1] are set, ownership of h is transferred
+// false: out_fd are not touched, caller retains ownership of h
+bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
+ int out_fd[2]);
void mp_uninit_ipc(struct mp_ipc_ctx *ctx);
// Serialize the given mpv_event structure to JSON. Returns an allocated string.
diff --git a/input/ipc-dummy.c b/input/ipc-dummy.c
index d9c31c046c..f0232b2f6e 100644
--- a/input/ipc-dummy.c
+++ b/input/ipc-dummy.c
@@ -8,6 +8,12 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
return NULL;
}
+bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
+ int out_fd[2])
+{
+ return false;
+}
+
void mp_uninit_ipc(struct mp_ipc_ctx *ctx)
{
}
diff --git a/input/ipc-unix.c b/input/ipc-unix.c
index bfd035298c..e047c30145 100644
--- a/input/ipc-unix.c
+++ b/input/ipc-unix.c
@@ -58,7 +58,7 @@ struct client_arg {
struct mp_log *log;
struct mpv_handle *client;
- char *client_name;
+ const char *client_name;
int client_fd;
bool close_client_fd;
@@ -215,9 +215,11 @@ done:
return NULL;
}
-static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
+static bool ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client,
+ bool free_on_init_fail)
{
- client->client = mp_new_client(ctx->client_api, client->client_name);
+ if (!client->client)
+ client->client = mp_new_client(ctx->client_api, client->client_name);
if (!client->client)
goto err;
@@ -227,16 +229,19 @@ static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
if (pthread_create(&client_thr, NULL, client_thread, client))
goto err;
- return;
+ return true;
err:
- if (client->client)
- mpv_destroy(client->client);
+ if (free_on_init_fail) {
+ if (client->client)
+ mpv_destroy(client->client);
- if (client->close_client_fd)
- close(client->client_fd);
+ if (client->close_client_fd)
+ close(client->client_fd);
+ }
talloc_free(client);
+ return false;
}
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
@@ -246,53 +251,37 @@ static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
.client_name = talloc_asprintf(client, "ipc-%d", id),
.client_fd = fd,
.close_client_fd = true,
-
.writable = true,
};
- ipc_start_client(ctx, client);
+ ipc_start_client(ctx, client, true);
}
-static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
+bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
+ int out_fd[2])
{
- int mode = O_RDONLY;
- int client_fd = -1;
- bool close_client_fd = true;
- bool writable = false;
-
- if (strcmp(path, "/dev/stdin") == 0) { // for symmetry with Linux
- client_fd = STDIN_FILENO;
- close_client_fd = false;
- } else if (strncmp(path, "fd://", 5) == 0) {
- char *end = NULL;
- client_fd = strtol(path + 5, &end, 0);
- if (!end || end == path + 5 || end[0]) {
- MP_ERR(ctx, "Invalid FD: %s\n", path);
- return;
- }
- close_client_fd = false;
- writable = true; // maybe
- } else {
- // Use RDWR for FIFOs to ensure they stay open over multiple accesses.
- struct stat st;
- if (stat(path, &st) == 0 && S_ISFIFO(st.st_mode))
- mode = O_RDWR;
- client_fd = open(path, mode);
- }
- if (client_fd < 0) {
- MP_ERR(ctx, "Could not open '%s'\n", path);
- return;
- }
+ int pair[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair))
+ return false;
struct client_arg *client = talloc_ptrtype(NULL, client);
*client = (struct client_arg){
- .client_name = "input-file",
- .client_fd = client_fd,
- .close_client_fd = close_client_fd,
- .writable = writable,
+ .client = h,
+ .client_name = mpv_client_name(h),
+ .client_fd = pair[1],
+ .close_client_fd = true,
+ .writable = true,
};
- ipc_start_client(ctx, client);
+ if (!ipc_start_client(ctx, client, false)) {
+ close(pair[0]);
+ close(pair[1]);
+ return false;
+ }
+
+ out_fd[0] = pair[0];
+ out_fd[1] = -1;
+ return true;
}
static void *ipc_thread(void *p)
@@ -314,9 +303,7 @@ static void *ipc_thread(void *p)
goto done;
}
-#if HAVE_FCHMOD
fchmod(ipc_fd, 0600);
-#endif
size_t path_len = strlen(arg->path);
if (path_len >= sizeof(ipc_un.sun_path) - 1) {
@@ -396,13 +383,9 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
.path = mp_get_user_path(arg, global, opts->ipc_path),
.death_pipe = {-1, -1},
};
- char *input_file = mp_get_user_path(arg, global, opts->input_file);
talloc_free(opts);
- if (input_file && *input_file)
- ipc_start_client_text(arg, input_file);
-
if (!arg->path || !arg->path[0])
goto out;
diff --git a/input/ipc-win.c b/input/ipc-win.c
index 727a8cca73..9672ec85fe 100644
--- a/input/ipc-win.c
+++ b/input/ipc-win.c
@@ -335,6 +335,12 @@ static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h)
ipc_start_client(ctx, client);
}
+bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
+ int out_fd[2])
+{
+ return false;
+}
+
static void *ipc_thread(void *p)
{
// Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is
diff --git a/input/ipc.c b/input/ipc.c
index 6568feacfa..34a10bbb00 100644
--- a/input/ipc.c
+++ b/input/ipc.c
@@ -37,28 +37,6 @@ static mpv_node *mpv_node_array_get(mpv_node *src, int index)
return &src->u.list->values[index];
}
-static void mpv_node_array_add(void *ta_parent, mpv_node *src, mpv_node *val)
-{
- if (src->format != MPV_FORMAT_NODE_ARRAY)
- return;
-
- if (!src->u.list)
- src->u.list = talloc_zero(ta_parent, mpv_node_list);
-
- MP_TARRAY_GROW(src->u.list, src->u.list->values, src->u.list->num);
-
- static const struct m_option type = { .type = CONF_TYPE_NODE };
- m_option_get_node(&type, ta_parent, &src->u.list->values[src->u.list->num], val);
-
- src->u.list->num++;
-}
-
-static void mpv_node_array_add_string(void *ta_parent, mpv_node *src, const char *val)
-{
- mpv_node val_node = {.format = MPV_FORMAT_STRING, .u.string = (char *)val};
- mpv_node_array_add(ta_parent, src, &val_node);
-}
-
static void mpv_node_map_add(void *ta_parent, mpv_node *src, const char *key, mpv_node *val)
{
if (src->format != MPV_FORMAT_NODE_MAP)
@@ -84,94 +62,46 @@ static void mpv_node_map_add_null(void *ta_parent, mpv_node *src, const char *ke
mpv_node_map_add(ta_parent, src, key, &val_node);
}
-static void mpv_node_map_add_flag(void *ta_parent, mpv_node *src, const char *key, bool val)
-{
- mpv_node val_node = {.format = MPV_FORMAT_FLAG, .u.flag = val};
-
- mpv_node_map_add(ta_parent, src, key, &val_node);
-}
-
static void mpv_node_map_add_int64(void *ta_parent, mpv_node *src, const char *key, int64_t val)
{
mpv_node val_node = {.format = MPV_FORMAT_INT64, .u.int64 = val};
mpv_node_map_add(ta_parent, src, key, &val_node);
}
-static void mpv_node_map_add_double(void *ta_parent, mpv_node *src, const char *key, double val)
-{
- mpv_node val_node = {.format = MPV_FORMAT_DOUBLE, .u.double_ = val};
- mpv_node_map_add(ta_parent, src, key, &val_node);
-}
-
static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *key, const char *val)
{
mpv_node val_node = {.format = MPV_FORMAT_STRING, .u.string = (char*)val};
mpv_node_map_add(ta_parent, src, key, &val_node);
}
-static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
+// This is supposed to write a reply that looks like "normal" command execution.
+static void mpv_format_command_reply(void *ta_parent, mpv_event *event,
+ mpv_node *dst)
{
- mpv_node_map_add_string(ta_parent, dst, "event", mpv_event_name(event->event_id));
-
- if (event->reply_userdata)
- mpv_node_map_add_int64(ta_parent, dst, "id", event->reply_userdata);
-
- if (event->error < 0)
- mpv_node_map_add_string(ta_parent, dst, "error", mpv_error_string(event->error));
+ assert(event->event_id == MPV_EVENT_COMMAND_REPLY);
+ mpv_event_command *cmd = event->data;
- switch (event->event_id) {
- case MPV_EVENT_LOG_MESSAGE: {
- mpv_event_log_message *msg = event->data;
+ mpv_node_map_add_int64(ta_parent, dst, "request_id", event->reply_userdata);
- mpv_node_map_add_string(ta_parent, dst, "prefix", msg->prefix);
- mpv_node_map_add_string(ta_parent, dst, "level", msg->level);
- mpv_node_map_add_string(ta_parent, dst, "text", msg->text);
+ mpv_node_map_add_string(ta_parent, dst, "error",
+ mpv_error_string(event->error));
- break;
- }
-
- case MPV_EVENT_CLIENT_MESSAGE: {
- mpv_event_client_message *msg = event->data;
-
- mpv_node args_node = {.format = MPV_FORMAT_NODE_ARRAY, .u.list = NULL};
- for (int n = 0; n < msg->num_args; n++)
- mpv_node_array_add_string(ta_parent, &args_node, msg->args[n]);
- mpv_node_map_add(ta_parent, dst, "args", &args_node);
- break;
- }
-
- case MPV_EVENT_PROPERTY_CHANGE: {
- mpv_event_property *prop = event->data;
-
- mpv_node_map_add_string(ta_parent, dst, "name", prop->name);
-
- switch (prop->format) {
- case MPV_FORMAT_NODE:
- mpv_node_map_add(ta_parent, dst, "data", prop->data);
- break;
- case MPV_FORMAT_DOUBLE:
- mpv_node_map_add_double(ta_parent, dst, "data", *(double *)prop->data);
- break;
- case MPV_FORMAT_FLAG:
- mpv_node_map_add_flag(ta_parent, dst, "data", *(int *)prop->data);
- break;
- case MPV_FORMAT_STRING:
- mpv_node_map_add_string(ta_parent, dst, "data", *(char **)prop->data);
- break;
- default:
- mpv_node_map_add_null(ta_parent, dst, "data");
- }
- break;
- }
- }
+ mpv_node_map_add(ta_parent, dst, "data", &cmd->result);
}
char *mp_json_encode_event(mpv_event *event)
{
void *ta_parent = talloc_new(NULL);
- mpv_node event_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
- mpv_event_to_node(ta_parent, event, &event_node);
+ struct mpv_node event_node;
+ if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
+ event_node = (mpv_node){.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
+ mpv_format_command_reply(ta_parent, event, &event_node);
+ } else {
+ mpv_event_to_node(&event_node, event);
+ // Abuse mpv_event_to_node() internals.
+ talloc_steal(ta_parent, node_get_alloc(&event_node));
+ }
char *output = talloc_strdup(NULL, "");
json_write(&output, &event_node);
@@ -193,6 +123,10 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
mpv_node msg_node;
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
mpv_node *reqid_node = NULL;
+ int64_t reqid = 0;
+ mpv_node *async_node = NULL;
+ bool async = false;
+ bool send_reply = true;
rc = json_parse(ta_parent, &msg_node, &src, 50);
if (rc < 0) {
@@ -206,42 +140,58 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
goto error;
}
+ async_node = node_map_get(&msg_node, "async");
+ if (async_node) {
+ if (async_node->format != MPV_FORMAT_FLAG) {
+ rc = MPV_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ async = async_node->u.flag;
+ }
+
reqid_node = node_map_get(&msg_node, "request_id");
- if (reqid_node && reqid_node->format != MPV_FORMAT_INT64) {
- mp_warn(log, "'request_id' must be an integer. Using other types is "
- "deprecated and will trigger an error in the future!\n");
+ if (reqid_node) {
+ if (reqid_node->format == MPV_FORMAT_INT64) {
+ reqid = reqid_node->u.int64;
+ } else if (async) {
+ mp_err(log, "'request_id' must be an integer for async commands.\n");
+ rc = MPV_ERROR_INVALID_PARAMETER;
+ goto error;
+ } else {
+ mp_warn(log, "'request_id' must be an integer. Using other types is "
+ "deprecated and will trigger an error in the future!\n");
+ }
}
mpv_node *cmd_node = node_map_get(&msg_node, "command");
- if (!cmd_node ||
- (cmd_node->format != MPV_FORMAT_NODE_ARRAY) ||
- !cmd_node->u.list->num)
- {
+ if (!cmd_node) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
}
- mpv_node *cmd_str_node = mpv_node_array_get(cmd_node, 0);
- if (!cmd_str_node || (cmd_str_node->format != MPV_FORMAT_STRING)) {
- rc = MPV_ERROR_INVALID_PARAMETER;
- goto error;
- }
+ if (cmd_node->format == MPV_FORMAT_NODE_ARRAY) {
+ mpv_node *cmd_str_node = mpv_node_array_get(cmd_node, 0);
+ if (!cmd_str_node || (cmd_str_node->format != MPV_FORMAT_STRING)) {
+ rc = MPV_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
- cmd = cmd_str_node->u.string;
+ cmd = cmd_str_node->u.string;
+ }
- if (!strcmp("client_name", cmd)) {
+ if (cmd && !strcmp("client_name", cmd)) {
const char *client_name = mpv_client_name(client);
mpv_node_map_add_string(ta_parent, &reply_node, "data", client_name);
rc = MPV_ERROR_SUCCESS;
- } else if (!strcmp("get_time_us", cmd)) {
+ } else if (cmd && !strcmp("get_time_us", cmd)) {
int64_t time_us = mpv_get_time_us(client);
mpv_node_map_add_int64(ta_parent, &reply_node, "data", time_us);
rc = MPV_ERROR_SUCCESS;
- } else if (!strcmp("get_version", cmd)) {
+ } else if (cmd && !strcmp("get_version", cmd)) {
int64_t ver = mpv_client_api_version();
mpv_node_map_add_int64(ta_parent, &reply_node, "data", ver);
rc = MPV_ERROR_SUCCESS;
- } else if (!strcmp("get_property", cmd)) {
+ } else if (cmd && !strcmp("get_property", cmd)) {
mpv_node result_node;
if (cmd_node->u.list->num != 2) {
@@ -260,7 +210,7 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
mpv_free_node_contents(&result_node);
}
- } else if (!strcmp("get_property_string", cmd)) {
+ } else if (cmd && !strcmp("get_property_string", cmd)) {
if (cmd_node->u.list->num != 2) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
@@ -279,8 +229,8 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
} else {
mpv_node_map_add_null(ta_parent, &reply_node, "data");
}
- } else if (!strcmp("set_property", cmd) ||
- !strcmp("set_property_string", cmd))
+ } else if (cmd && (!strcmp("set_property", cmd) ||
+ !strcmp("set_property_string", cmd)))
{
if (cmd_node->u.list->num != 3) {
rc = MPV_ERROR_INVALID_PARAMETER;
@@ -294,7 +244,7 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
rc = mpv_set_property(client, cmd_node->u.list->values[1].u.string,
MPV_FORMAT_NODE, &cmd_node->u.list->values[2]);
- } else if (!strcmp("observe_property", cmd)) {
+ } else if (cmd && !strcmp("observe_property", cmd)) {
if (cmd_node->u.list->num != 3) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
@@ -314,7 +264,7 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
cmd_node->u.list->values[1].u.int64,
cmd_node->u.list->values[2].u.string,
MPV_FORMAT_NODE);
- } else if (!strcmp("observe_property_string", cmd)) {
+ } else if (cmd && !strcmp("observe_property_string", cmd)) {
if (cmd_node->u.list->num != 3) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
@@ -334,7 +284,7 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
cmd_node->u.list->values[1].u.int64,
cmd_node->u.list->values[2].u.string,
MPV_FORMAT_STRING);
- } else if (!strcmp("unobserve_property", cmd)) {
+ } else if (cmd && !strcmp("unobserve_property", cmd)) {
if (cmd_node->u.list->num != 2) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
@@ -347,7 +297,7 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
rc = mpv_unobserve_property(client,
cmd_node->u.list->values[1].u.int64);
- } else if (!strcmp("request_log_messages", cmd)) {
+ } else if (cmd && !strcmp("request_log_messages", cmd)) {
if (cmd_node->u.list->num != 2) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
@@ -360,8 +310,8 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
rc = mpv_request_log_messages(client,
cmd_node->u.list->values[1].u.string);
- } else if (!strcmp("enable_event", cmd) ||
- !strcmp("disable_event", cmd))
+ } else if (cmd && (!strcmp("enable_event", cmd) ||
+ !strcmp("disable_event", cmd)))
{
bool enable = !strcmp("enable_event", cmd);
@@ -394,11 +344,19 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
rc = mpv_request_event(client, event, enable);
}
} else {
- mpv_node result_node;
+ mpv_node result_node = {0};
- rc = mpv_command_node(client, cmd_node, &result_node);
- if (rc >= 0)
- mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
+ if (async) {
+ rc = mpv_command_node_async(client, reqid, cmd_node);
+ if (rc >= 0)
+ send_reply = false;
+ } else {
+ rc = mpv_command_node(client, cmd_node, &result_node);
+ if (rc >= 0)
+ mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
+ }
+
+ mpv_free_node_contents(&result_node);
}
error:
@@ -415,8 +373,11 @@ error:
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
char *output = talloc_strdup(ta_parent, "");
- json_write(&output, &reply_node);
- output = ta_talloc_strdup_append(output, "\n");
+
+ if (send_reply) {
+ json_write(&output, &reply_node);
+ output = ta_talloc_strdup_append(output, "\n");
+ }
return output;
}
diff --git a/input/pipe-win32.c b/input/pipe-win32.c
deleted file mode 100644
index a0a0bfef24..0000000000
--- a/input/pipe-win32.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 <windows.h>
-#include <io.h>
-
-#include "common/msg.h"
-#include "osdep/atomic.h"
-#include "osdep/io.h"
-#include "input.h"
-
-struct priv {
- atomic_bool cancel_requested;
- int fd;
- bool close_fd;
- HANDLE file;
- HANDLE thread;
-};
-
-static void request_cancel(struct mp_input_src *src)
-{
- struct priv *p = src->priv;
-
- MP_VERBOSE(src, "Exiting...\n");
- atomic_store(&p->cancel_requested, true);
-
- // The thread might not be peforming I/O at the exact moment when
- // CancelIoEx is called, so call it in a loop until it succeeds or the
- // thread exits
- do {
- if (CancelIoEx(p->file, NULL))
- break;
- } while (WaitForSingleObject(p->thread, 1) != WAIT_OBJECT_0);
-}
-
-static void uninit(struct mp_input_src *src)
-{
- struct priv *p = src->priv;
-
- CloseHandle(p->thread);
- if (p->close_fd)
- close(p->fd);
-
- MP_VERBOSE(src, "Exited.\n");
-}
-
-static void read_pipe_thread(struct mp_input_src *src, void *param)
-{
- char *filename = talloc_strdup(src, param);
- struct priv *p = talloc_zero(src, struct priv);
-
- p->fd = -1;
- p->close_fd = true;
- if (strcmp(filename, "/dev/stdin") == 0) { // for symmetry with unix
- p->fd = STDIN_FILENO;
- p->close_fd = false;
- }
- if (p->fd < 0)
- p->fd = open(filename, O_RDONLY);
- if (p->fd < 0) {
- MP_ERR(src, "Can't open %s.\n", filename);
- return;
- }
-
- p->file = (HANDLE)_get_osfhandle(p->fd);
- if (!p->file || p->file == INVALID_HANDLE_VALUE) {
- MP_ERR(src, "Can't open %s.\n", filename);
- return;
- }
-
- atomic_store(&p->cancel_requested, false);
- if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
- GetCurrentProcess(), &p->thread, SYNCHRONIZE, FALSE, 0))
- return;
-
- src->priv = p;
- src->cancel = request_cancel;
- src->uninit = uninit;
- mp_input_src_init_done(src);
-
- char buffer[4096];
- while (!atomic_load(&p->cancel_requested)) {
- DWORD r;
- if (!ReadFile(p->file, buffer, 4096, &r, NULL)) {
- if (GetLastError() != ERROR_OPERATION_ABORTED)
- MP_ERR(src, "Read operation failed.\n");
- break;
- }
- mp_input_src_feed_cmd_text(src, buffer, r);
- }
-}
-
-void mp_input_pipe_add(struct input_ctx *ictx, const char *filename)
-{
- mp_input_add_thread_src(ictx, (void *)filename, read_pipe_thread);
-}
diff --git a/libmpv/client.h b/libmpv/client.h
index 2d108ca8d3..d5f512003c 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -144,6 +144,9 @@ extern "C" {
* and set it to SIG_IGN.
* - mpv will reseed the legacy C random number generator by calling srand() at
* some random point once.
+ * - mpv may start sub processes, so overriding SIGCHLD, or waiting on all PIDs
+ * (such as calling wait()) by the parent process or any other library within
+ * the process must be avoided. libmpv itself only waits for its own PIDs.
*
* Encoding of filenames
* ---------------------
@@ -229,7 +232,7 @@ extern "C" {
* relational operators (<, >, <=, >=).
*/
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
-#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 107)
+#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 108)
/**
* The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before
@@ -389,6 +392,24 @@ void mpv_free(void *data);
const char *mpv_client_name(mpv_handle *ctx);
/**
+ * Return the ID of this client handle. Every client has its own unique ID. This
+ * ID is never reused by the core, even if the mpv_handle at hand gets destroyed
+ * and new handles get allocated.
+ *
+ * IDs are never 0 or negative.
+ *
+ * Some mpv APIs (not necessarily all) accept a name in the form "@<id>" in
+ * addition of the proper mpv_client_name(), where "<id>" is the ID in decimal
+ * form (e.g. "@123"). For example, the "script-message-to" command takes the
+ * client name as first argument, but also accepts the client ID formatted in
+ * this manner.
+ *
+ * @return The client name. The string is read-only and is valid until the
+ * mpv_handle is destroyed.
+ */
+int64_t mpv_client_id(mpv_handle *ctx);
+
+/**
* Create a new mpv instance and an associated client API handle to control
* the mpv instance. This instance is in a pre-initialized state,
* and needs to be initialized to be actually used with most other API
@@ -645,7 +666,9 @@ int64_t mpv_get_time_us(mpv_handle *ctx);
*/
typedef enum mpv_format {
/**
- * Invalid. Sometimes used for empty values.
+ * Invalid. Sometimes used for empty values. This is always defined to 0,
+ * so a normal 0-init of mpv_format (or e.g. mpv_node) is guaranteed to set
+ * this it to MPV_FORMAT_NONE (which makes some things saner as consequence).
*/
MPV_FORMAT_NONE = 0,
/**
@@ -1008,6 +1031,11 @@ int mpv_command_string(mpv_handle *ctx, const char *args);
* error code set if running the command failed. For commands that
* return data, the data is put into mpv_event_command.result.
*
+ * The only case when you do not receive an event is when the function call
+ * itself fails. This happens only if parsing the command itself (or otherwise
+ * validating it) fails, i.e. the return code of the API call is not 0 or
+ * positive.
+ *
* Safe to be called from mpv render API threads.
*
* @param reply_userdata the value mpv_event.reply_userdata of the reply will
@@ -1289,6 +1317,7 @@ typedef enum mpv_event_id {
MPV_EVENT_COMMAND_REPLY = 5,
/**
* Notification before playback start of a file (before the file is loaded).
+ * See also mpv_event and mpv_event_start_file.
*/
MPV_EVENT_START_FILE = 6,
/**
@@ -1320,15 +1349,19 @@ typedef enum mpv_event_id {
* and might be removed in the far future.
*/
MPV_EVENT_TRACK_SWITCHED = 10,
-#endif
/**
* Idle mode was entered. In this mode, no file is played, and the playback
* core waits for new commands. (The command line player normally quits
* instead of entering idle mode, unless --idle was specified. If mpv
* was started with mpv_create(), idle mode is enabled by default.)
+ *
+ * @deprecated This is equivalent to using mpv_observe_property() on the
+ * "idle-active" property. The event is redundant, and might be
+ * removed in the far future. As a further warning, this event
+ * is not necessarily sent at the right point anymore (at the
+ * start of the program), while the property behaves correctly.
*/
MPV_EVENT_IDLE = 11,
-#if MPV_ENABLE_DEPRECATED
/**
* Playback was paused. This indicates the user pause state.
*
@@ -1421,9 +1454,9 @@ typedef enum mpv_event_id {
MPV_EVENT_SEEK = 20,
/**
* There was a discontinuity of some sort (like a seek), and playback
- * was reinitialized. Usually happens after seeking, or ordered chapter
- * segment switches. The main purpose is allowing the client to detect
- * when a seek request is finished.
+ * was reinitialized. Usually happens on start of playback and after
+ * seeking. The main purpose is allowing the client to detect when a seek
+ * request is finished.
*/
MPV_EVENT_PLAYBACK_RESTART = 21,
/**
@@ -1583,6 +1616,14 @@ typedef enum mpv_end_file_reason {
MPV_END_FILE_REASON_REDIRECT = 5,
} mpv_end_file_reason;
+/// Since API version 1.108.
+typedef struct mpv_event_start_file {
+ /**
+ * Playlist entry ID of the file being loaded now.
+ */
+ int64_t playlist_entry_id;
+} mpv_event_start_file;
+
typedef struct mpv_event_end_file {
/**
* Corresponds to the values in enum mpv_end_file_reason (the "int" type
@@ -1598,6 +1639,34 @@ typedef struct mpv_event_end_file {
* Since API version 1.9.
*/
int error;
+ /**
+ * Playlist entry ID of the file that was being played or attempted to be
+ * played. This has the same value as the playlist_entry_id field in the
+ * corresponding mpv_event_start_file event.
+ * Since API version 1.108.
+ */
+ int64_t playlist_entry_id;
+ /**
+ * If loading ended, because the playlist entry to be played was for example
+ * a playlist, and the current playlist entry is replaced with a number of
+ * other entries. This may happen at least with MPV_END_FILE_REASON_REDIRECT
+ * (other event types may use this for similar but different purposes in the
+ * future). In this case, playlist_insert_id will be set to the playlist
+ * entry ID of the first inserted entry, and playlist_insert_num_entries to
+ * the total number of inserted playlist entries. Note this in this specific
+ * case, the ID of the last inserted entry is playlist_insert_id+num-1.
+ * Beware that depending on circumstances, you may observe the new playlist
+ * entries before seeing the event (e.g. reading the "playlist" property or
+ * getting a property change notification before receiving the event).
+ * Since API version 1.108.
+ */
+ int64_t playlist_insert_id;
+ /**
+ * See playlist_insert_id. Only non-0 if playlist_insert_id is valid. Never
+ * negative.
+ * Since API version 1.108.
+ */
+ int playlist_insert_num_entries;
} mpv_event_end_file;
#if MPV_ENABLE_DEPRECATED
@@ -1677,6 +1746,7 @@ typedef struct mpv_event {
* MPV_EVENT_PROPERTY_CHANGE: mpv_event_property*
* MPV_EVENT_LOG_MESSAGE: mpv_event_log_message*
* MPV_EVENT_CLIENT_MESSAGE: mpv_event_client_message*
+ * MPV_EVENT_START_FILE: mpv_event_start_file* (since v1.108)
* MPV_EVENT_END_FILE: mpv_event_end_file*
* MPV_EVENT_HOOK: mpv_event_hook*
* MPV_EVENT_COMMAND_REPLY* mpv_event_command*
@@ -1689,6 +1759,31 @@ typedef struct mpv_event {
} mpv_event;
/**
+ * Convert the given src event to a mpv_node, and set *dst to the result. *dst
+ * is set to a MPV_FORMAT_NODE_MAP, with fields for corresponding mpv_event and
+ * mpv_event.data/mpv_event_* fields.
+ *
+ * The exact details are not completely documented out of laziness. A start
+ * is located in the "Events" section of the manpage.
+ *
+ * *dst may point to newly allocated memory, or pointers in mpv_event. You must
+ * copy the entire mpv_node if you want to reference it after mpv_event becomes
+ * invalid (such as making a new mpv_wait_event() call, or destroying the
+ * mpv_handle from which it was returned). Call mpv_free_node_contents() to free
+ * any memory allocations made by this API function.
+ *
+ * Safe to be called from mpv render API threads.
+ *
+ * @param dst Target. This is not read and fully overwritten. Must be released
+ * with mpv_free_node_contents(). Do not write to pointers returned
+ * by it. (On error, this may be left as an empty node.)
+ * @param src The source event. Not modified (it's not const due to the author's
+ * prejudice of the C version of const).
+ * @return error code (MPV_ERROR_NOMEM only, if at all)
+ */
+int mpv_event_to_node(mpv_node *dst, mpv_event *src);
+
+/**
* Enable or disable the given event.
*
* Some events are enabled by default. Some events can't be disabled.
diff --git a/libmpv/mpv.def b/libmpv/mpv.def
index a2c6fd1ac9..50a713531f 100644
--- a/libmpv/mpv.def
+++ b/libmpv/mpv.def
@@ -1,5 +1,6 @@
mpv_abort_async_command
mpv_client_api_version
+mpv_client_id
mpv_client_name
mpv_command
mpv_command_async
@@ -13,6 +14,7 @@ mpv_create_weak_client
mpv_destroy
mpv_detach_destroy
mpv_error_string
+mpv_event_to_node
mpv_event_name
mpv_free
mpv_free_node_contents
diff --git a/libmpv/qthelper.hpp b/libmpv/qthelper.hpp
deleted file mode 100644
index 3af86e36e7..0000000000
--- a/libmpv/qthelper.hpp
+++ /dev/null
@@ -1,386 +0,0 @@
-/* Copyright (C) 2017 the mpv developers
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MPV_CLIENT_API_QTHELPER_H_
-#define MPV_CLIENT_API_QTHELPER_H_
-
-#include <mpv/client.h>
-
-#if !MPV_ENABLE_DEPRECATED
-#error "This helper is deprecated. Copy it into your project instead."
-#else
-
-/**
- * Note: these helpers are provided for convenience for C++/Qt applications.
- * This is based on the public API in client.h, and it does not encode any
- * knowledge that is not known or guaranteed outside of the C client API. You
- * can even copy and modify this code as you like, or implement similar things
- * for other languages.
- */
-
-#include <cstring>
-
-#include <QVariant>
-#include <QString>
-#include <QList>
-#include <QHash>
-#include <QSharedPointer>
-#include <QMetaType>
-
-namespace mpv {
-namespace qt {
-
-// Wrapper around mpv_handle. Does refcounting under the hood.
-class Handle
-{
- struct container {
- container(mpv_handle *h) : mpv(h) {}
- ~container() { mpv_terminate_destroy(mpv); }
- mpv_handle *mpv;
- };
- QSharedPointer<container> sptr;
-public:
- // Construct a new Handle from a raw mpv_handle with refcount 1. If the
- // last Handle goes out of scope, the mpv_handle will be destroyed with
- // mpv_terminate_destroy().
- // Never destroy the mpv_handle manually when using this wrapper. You
- // will create dangling pointers. Just let the wrapper take care of
- // destroying the mpv_handle.
- // Never create multiple wrappers from the same raw mpv_handle; copy the
- // wrapper instead (that's what it's for).
- static Handle FromRawHandle(mpv_handle *handle) {
- Handle h;
- h.sptr = QSharedPointer<container>(new container(handle));
- return h;
- }
-
- // Return the raw handle; for use with the libmpv C API.
- operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; }
-};
-
-static inline QVariant node_to_variant(const mpv_node *node)
-{
- switch (node->format) {
- case MPV_FORMAT_STRING:
- return QVariant(QString::fromUtf8(node->u.string));
- case MPV_FORMAT_FLAG:
- return QVariant(static_cast<bool>(node->u.flag));
- case MPV_FORMAT_INT64:
- return QVariant(static_cast<qlonglong>(node->u.int64));
- case MPV_FORMAT_DOUBLE:
- return QVariant(node->u.double_);
- case MPV_FORMAT_NODE_ARRAY: {
- mpv_node_list *list = node->u.list;
- QVariantList qlist;
- for (int n = 0; n < list->num; n++)
- qlist.append(node_to_variant(&list->values[n]));
- return QVariant(qlist);
- }
- case MPV_FORMAT_NODE_MAP: {
- mpv_node_list *list = node->u.list;
- QVariantMap qmap;
- for (int n = 0; n < list->num; n++) {
- qmap.insert(QString::fromUtf8(list->keys[n]),
- node_to_variant(&list->values[n]));
- }
- return QVariant(qmap);
- }
- default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
- return QVariant();
- }
-}
-
-struct node_builder {
- node_builder(const QVariant& v) {
- set(&node_, v);
- }
- ~node_builder() {
- free_node(&node_);
- }
- mpv_node *node() { return &node_; }
-private:
- Q_DISABLE_COPY(node_builder)
- mpv_node node_;
- mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) {
- dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
- mpv_node_list *list = new mpv_node_list();
- dst->u.list = list;
- if (!list)
- goto err;
- list->values = new mpv_node[num]();
- if (!list->values)
- goto err;
- if (is_map) {
- list->keys = new char*[num]();
- if (!list->keys)
- goto err;
- }
- return list;
- err:
- free_node(dst);
- return NULL;
- }
- char *dup_qstring(const QString &s) {
- QByteArray b = s.toUtf8();
- char *r = new char[b.size() + 1];
- if (r)
- std::memcpy(r, b.data(), b.size() + 1);
- return r;
- }
- bool test_type(const QVariant &v, QMetaType::Type t) {
- // The Qt docs say: "Although this function is declared as returning
- // "QVariant::Type(obsolete), the return value should be interpreted
- // as QMetaType::Type."
- // So a cast really seems to be needed to avoid warnings (urgh).
- return static_cast<int>(v.type()) == static_cast<int>(t);
- }
- void set(mpv_node *dst, const QVariant &src) {
- if (test_type(src, QMetaType::QString)) {
- dst->format = MPV_FORMAT_STRING;
- dst->u.string = dup_qstring(src.toString());
- if (!dst->u.string)
- goto fail;
- } else if (test_type(src, QMetaType::Bool)) {
- dst->format = MPV_FORMAT_FLAG;
- dst->u.flag = src.toBool() ? 1 : 0;
- } else if (test_type(src, QMetaType::Int) ||
- test_type(src, QMetaType::LongLong) ||
- test_type(src, QMetaType::UInt) ||
- test_type(src, QMetaType::ULongLong))
- {
- dst->format = MPV_FORMAT_INT64;
- dst->u.int64 = src.toLongLong();
- } else if (test_type(src, QMetaType::Double)) {
- dst->format = MPV_FORMAT_DOUBLE;
- dst->u.double_ = src.toDouble();
- } else if (src.canConvert<QVariantList>()) {
- QVariantList qlist = src.toList();
- mpv_node_list *list = create_list(dst, false, qlist.size());
- if (!list)
- goto fail;
- list->num = qlist.size();
- for (int n = 0; n < qlist.size(); n++)
- set(&list->values[n], qlist[n]);
- } else if (src.canConvert<QVariantMap>()) {
- QVariantMap qmap = src.toMap();
- mpv_node_list *list = create_list(dst, true, qmap.size());
- if (!list)
- goto fail;
- list->num = qmap.size();
- for (int n = 0; n < qmap.size(); n++) {
- list->keys[n] = dup_qstring(qmap.keys()[n]);
- if (!list->keys[n]) {
- free_node(dst);
- goto fail;
- }
- set(&list->values[n], qmap.values()[n]);
- }
- } else {
- goto fail;
- }
- return;
- fail:
- dst->format = MPV_FORMAT_NONE;
- }
- void free_node(mpv_node *dst) {
- switch (dst->format) {
- case MPV_FORMAT_STRING:
- delete[] dst->u.string;
- break;
- case MPV_FORMAT_NODE_ARRAY:
- case MPV_FORMAT_NODE_MAP: {
- mpv_node_list *list = dst->u.list;
- if (list) {
- for (int n = 0; n < list->num; n++) {
- if (list->keys)
- delete[] list->keys[n];
- if (list->values)
- free_node(&list->values[n]);
- }
- delete[] list->keys;
- delete[] list->values;
- }
- delete list;
- break;
- }
- default: ;
- }
- dst->format = MPV_FORMAT_NONE;
- }
-};
-
-/**
- * RAII wrapper that calls mpv_free_node_contents() on the pointer.
- */
-struct node_autofree {
- mpv_node *ptr;
- node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {}
- ~node_autofree() { mpv_free_node_contents(ptr); }
-};
-
-#if MPV_ENABLE_DEPRECATED
-
-/**
- * Return the given property as mpv_node converted to QVariant, or QVariant()
- * on error.
- *
- * @deprecated use get_property() instead
- *
- * @param name the property name
- */
-static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name)
-{
- mpv_node node;
- if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0)
- return QVariant();
- node_autofree f(&node);
- return node_to_variant(&node);
-}
-
-/**
- * Set the given property as mpv_node converted from the QVariant argument.
-
- * @deprecated use set_property() instead
- */
-static inline int set_property_variant(mpv_handle *ctx, const QString &name,
- const QVariant &v)
-{
- node_builder node(v);
- return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
-}
-
-/**
- * Set the given option as mpv_node converted from the QVariant argument.
- *
- * @deprecated use set_property() instead
- */
-static inline int set_option_variant(mpv_handle *ctx, const QString &name,
- const QVariant &v)
-{
- node_builder node(v);
- return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
-}
-
-/**
- * mpv_command_node() equivalent. Returns QVariant() on error (and
- * unfortunately, the same on success).
- *
- * @deprecated use command() instead
- */
-static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args)
-{
- node_builder node(args);
- mpv_node res;
- if (mpv_command_node(ctx, node.node(), &res) < 0)
- return QVariant();
- node_autofree f(&res);
- return node_to_variant(&res);
-}
-
-#endif
-
-/**
- * This is used to return error codes wrapped in QVariant for functions which
- * return QVariant.
- *
- * You can use get_error() or is_error() to extract the error status from a
- * QVariant value.
- */
-struct ErrorReturn
-{
- /**
- * enum mpv_error value (or a value outside of it if ABI was extended)
- */
- int error;
-
- ErrorReturn() : error(0) {}
- explicit ErrorReturn(int err) : error(err) {}
-};
-
-/**
- * Return the mpv error code packed into a QVariant, or 0 (success) if it's not
- * an error value.
- *
- * @return error code (<0) or success (>=0)
- */
-static inline int get_error(const QVariant &v)
-{
- if (!v.canConvert<ErrorReturn>())
- return 0;
- return v.value<ErrorReturn>().error;
-}
-
-/**
- * Return whether the QVariant carries a mpv error code.
- */
-static inline bool is_error(const QVariant &v)
-{
- return get_error(v) < 0;
-}
-
-/**
- * Return the given property as mpv_node converted to QVariant, or QVariant()
- * on error.
- *
- * @param name the property name
- * @return the property value, or an ErrorReturn with the error code
- */
-static inline QVariant get_property(mpv_handle *ctx, const QString &name)
-{
- mpv_node node;
- int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
- if (err < 0)
- return QVariant::fromValue(ErrorReturn(err));
- node_autofree f(&node);
- return node_to_variant(&node);
-}
-
-/**
- * Set the given property as mpv_node converted from the QVariant argument.
- *
- * @return mpv error code (<0 on error, >= 0 on success)
- */
-static inline int set_property(mpv_handle *ctx, const QString &name,
- const QVariant &v)
-{
- node_builder node(v);
- return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
-}
-
-/**
- * mpv_command_node() equivalent.
- *
- * @param args command arguments, with args[0] being the command name as string
- * @return the property value, or an ErrorReturn with the error code
- */
-static inline QVariant command(mpv_handle *ctx, const QVariant &args)
-{
- node_builder node(args);
- mpv_node res;
- int err = mpv_command_node(ctx, node.node(), &res);
- if (err < 0)
- return QVariant::fromValue(ErrorReturn(err));
- node_autofree f(&res);
- return node_to_variant(&res);
-}
-
-}
-}
-
-Q_DECLARE_METATYPE(mpv::qt::ErrorReturn)
-
-#endif /* else #if MPV_ENABLE_DEPRECATED */
-
-#endif
diff --git a/misc/dispatch.c b/misc/dispatch.c
index 1102c564ae..0c3c574afa 100644
--- a/misc/dispatch.c
+++ b/misc/dispatch.c
@@ -30,6 +30,8 @@ struct mp_dispatch_queue {
pthread_cond_t cond;
void (*wakeup_fn)(void *wakeup_ctx);
void *wakeup_ctx;
+ void (*onlock_fn)(void *onlock_ctx);
+ void *onlock_ctx;
// Time at which mp_dispatch_queue_process() should return.
int64_t wait;
// Make mp_dispatch_queue_process() exit if it's idle.
@@ -94,8 +96,9 @@ struct mp_dispatch_queue *mp_dispatch_create(void *ta_parent)
// the wakeup_fn could for example write a byte into a "wakeup" pipe in order
// to unblock the select(). The wakeup_fn is called from the dispatch queue
// when there are new dispatch items, and the target thread should then enter
-// mp_dispatch_queue_process() as soon as possible. Note that wakeup_fn is
-// called under no lock, so you might have to do synchronization yourself.
+// mp_dispatch_queue_process() as soon as possible.
+// Note that this setter does not do internal synchronization, so you must set
+// it before other threads see it.
void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
void (*wakeup_fn)(void *wakeup_ctx),
void *wakeup_ctx)
@@ -104,6 +107,22 @@ void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
queue->wakeup_ctx = wakeup_ctx;
}
+// Set a function that will be called by mp_dispatch_lock() if the target thread
+// is not calling mp_dispatch_queue_process() right now. This is an obscure,
+// optional mechanism to make a worker thread react to external events more
+// quickly. The idea is that the callback will make the worker thread to stop
+// doing whatever (e.g. by setting a flag), and call mp_dispatch_queue_process()
+// in order to let mp_dispatch_lock() calls continue sooner.
+// Like wakeup_fn, this setter does no internal synchronization, and you must
+// not access the dispatch queue itself from the callback.
+void mp_dispatch_set_onlock_fn(struct mp_dispatch_queue *queue,
+ void (*onlock_fn)(void *onlock_ctx),
+ void *onlock_ctx)
+{
+ queue->onlock_fn = onlock_fn;
+ queue->onlock_ctx = onlock_ctx;
+}
+
static void mp_dispatch_append(struct mp_dispatch_queue *queue,
struct mp_dispatch_item *item)
{
@@ -356,6 +375,8 @@ void mp_dispatch_lock(struct mp_dispatch_queue *queue)
// And now wait until the target thread gets "trapped" within the
// mp_dispatch_queue_process() call, which will mean we get exclusive
// access to the target's thread state.
+ if (queue->onlock_fn)
+ queue->onlock_fn(queue->onlock_ctx);
while (!queue->in_process) {
pthread_mutex_unlock(&queue->lock);
if (queue->wakeup_fn)
diff --git a/misc/dispatch.h b/misc/dispatch.h
index 3367018390..fbf826037b 100644
--- a/misc/dispatch.h
+++ b/misc/dispatch.h
@@ -10,6 +10,9 @@ struct mp_dispatch_queue *mp_dispatch_create(void *talloc_parent);
void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
void (*wakeup_fn)(void *wakeup_ctx),
void *wakeup_ctx);
+void mp_dispatch_set_onlock_fn(struct mp_dispatch_queue *queue,
+ void (*onlock_fn)(void *onlock_ctx),
+ void *onlock_ctx);
void mp_dispatch_enqueue(struct mp_dispatch_queue *queue,
mp_dispatch_fn fn, void *fn_data);
void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue,
diff --git a/options/m_config.h b/options/m_config.h
index c2783feee3..d2ce2b4467 100644
--- a/options/m_config.h
+++ b/options/m_config.h
@@ -1,366 +1 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * mpv is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MPLAYER_M_CONFIG_H
-#define MPLAYER_M_CONFIG_H
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "misc/bstr.h"
-
-// m_config provides an API to manipulate the config variables in MPlayer.
-// It makes use of the Options API to provide a context stack that
-// allows saving and later restoring the state of all variables.
-
-typedef struct m_profile m_profile_t;
-struct m_option;
-struct m_option_type;
-struct m_sub_options;
-struct m_obj_desc;
-struct m_obj_settings;
-struct mp_log;
-struct mp_dispatch_queue;
-
-// Config option
-struct m_config_option {
- bool is_set_from_cmdline : 1; // Set by user from command line
- bool is_set_from_config : 1; // Set by a config file
- bool is_set_locally : 1; // Has a backup entry
- bool warning_was_printed : 1;
- int16_t group_index; // Index into m_config.groups
- const char *name; // Full name (ie option-subopt)
- const struct m_option *opt; // Option description
- void *data; // Raw value of the option
-};
-
-// Config object
-/** \ingroup Config */
-typedef struct m_config {
- struct mp_log *log;
- struct mpv_global *global; // can be NULL
-
- // Registered options.
- struct m_config_option *opts; // all options, even suboptions
- int num_opts;
-
- // List of defined profiles.
- struct m_profile *profiles;
- // Depth when recursively including profiles.
- int profile_depth;
-
- struct m_opt_backup *backup_opts;
-
- bool use_profiles;
- bool is_toplevel;
- int (*includefunc)(void *ctx, char *filename, int flags);
- void *includefunc_ctx;
-
- // Notification after an option was successfully written to.
- // Uses flags as set in UPDATE_OPTS_MASK.
- // self_update==true means the update was caused by a call to
- // m_config_notify_change_opt_ptr(). If false, it's caused either by
- // m_config_set_option_*() (and similar) calls or external updates.
- void (*option_change_callback)(void *ctx, struct m_config_option *co,
- int flags, bool self_update);
- void *option_change_callback_ctx;
-
- // For the command line parser
- int recursion_depth;
-
- void *optstruct; // struct mpopts or other
-
- // Private. Non-NULL if data was allocated. m_config_option.data uses it.
- // API users call m_config_set_update_dispatch_queue() to get async updates.
- struct m_config_cache *cache;
-
- // Private. Thread-safe shadow memory; only set for the main m_config.
- struct m_config_shadow *shadow;
-} m_config_t;
-
-// Create a new config object.
-// talloc_ctx: talloc parent context for the m_config allocation
-// root: description of all options
-// Note that the m_config object will keep pointers to root and log.
-struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
- const struct m_sub_options *root);
-
-// Create a m_config for the given desc. This is for --af/--vf, which have
-// different sub-options for every filter (represented by separate desc
-// structs).
-// args is an array of key/value pairs (args=[k0, v0, k1, v1, ..., NULL]).
-// name/defaults is only needed for the legacy af-defaults/vf-defaults options.
-struct m_config *m_config_from_obj_desc_and_args(void *ta_parent,
- struct mp_log *log, struct mpv_global *global, struct m_obj_desc *desc,
- const char *name, struct m_obj_settings *defaults, char **args);
-
-// Like m_config_from_obj_desc_and_args(), but don't allocate option the
-// struct, i.e. m_config.optstruct==NULL. This is used by the sub-option
-// parser (--af/--vf, to a lesser degree --ao/--vo) to check sub-option names
-// and types.
-struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
- struct mp_log *log,
- struct m_obj_desc *desc);
-
-// Allocate a priv struct that is backed by global options (like AOs and VOs,
-// anything that uses m_obj_list.use_global_options == true).
-// The result contains a snapshot of the current option values of desc->options.
-// For convenience, desc->options can be NULL; then priv struct is allocated
-// with just zero (or priv_defaults if set).
-void *m_config_group_from_desc(void *ta_parent, struct mp_log *log,
- struct mpv_global *global, struct m_obj_desc *desc, const char *name);
-
-// Make sure the option is backed up. If it's already backed up, do nothing.
-// All backed up options can be restored with m_config_restore_backups().
-void m_config_backup_opt(struct m_config *config, const char *opt);
-
-// Call m_config_backup_opt() on all options.
-void m_config_backup_all_opts(struct m_config *config);
-
-// Restore all options backed up with m_config_backup_opt(), and delete the
-// backups afterwards.
-void m_config_restore_backups(struct m_config *config);
-
-enum {
- M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt.
- M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value
- M_SETOPT_FROM_CONFIG_FILE = 4, // Reject M_OPT_NOCFG opt. (print error)
- M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line
- M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before
- M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE
- M_SETOPT_NO_PRE_PARSE = 128, // Reject M_OPT_PREPARSE options
- M_SETOPT_NO_OVERWRITE = 256, // Skip options marked with FROM_*
-};
-
-// Set the named option to the given string. This is for command line and config
-// file use only.
-// flags: combination of M_SETOPT_* flags (0 for normal operation)
-// Returns >= 0 on success, otherwise see OptionParserReturn.
-int m_config_set_option_cli(struct m_config *config, struct bstr name,
- struct bstr param, int flags);
-
-// Similar to m_config_set_option_cli(), but set as data in its native format.
-// This takes care of some details like sending change notifications.
-// The type data points to is as in: co->opt
-int m_config_set_option_raw(struct m_config *config, struct m_config_option *co,
- void *data, int flags);
-
-void m_config_mark_co_flags(struct m_config_option *co, int flags);
-
-// Convert the mpv_node to raw option data, then call m_config_set_option_raw().
-struct mpv_node;
-int m_config_set_option_node(struct m_config *config, bstr name,
- struct mpv_node *data, int flags);
-
-// Return option descriptor. You shouldn't use this.
-struct m_config_option *m_config_get_co(const struct m_config *config,
- struct bstr name);
-// Same as above, but does not resolve aliases or trigger warning messages.
-struct m_config_option *m_config_get_co_raw(const struct m_config *config,
- struct bstr name);
-
-// Special uses only. Look away.
-int m_config_get_co_count(struct m_config *config);
-struct m_config_option *m_config_get_co_index(struct m_config *config, int index);
-const void *m_config_get_co_default(const struct m_config *config,
- struct m_config_option *co);
-
-// Return the n-th option by position. n==0 is the first option. If there are
-// less than (n + 1) options, return NULL.
-const char *m_config_get_positional_option(const struct m_config *config, int n);
-
-// Return a hint to the option parser whether a parameter is/may be required.
-// The option may still accept empty/non-empty parameters independent from
-// this, and this function is useful only for handling ambiguous options like
-// flags (e.g. "--a" is ok, "--a=yes" is also ok).
-// Returns: error code (<0), or number of expected params (0, 1)
-int m_config_option_requires_param(struct m_config *config, bstr name);
-
-// Notify m_config_cache users that the option has (probably) changed its value.
-// This will force a self-notification back to config->option_change_callback.
-void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr);
-
-// Exactly like m_config_notify_change_opt_ptr(), but the option change callback
-// (config->option_change_callback()) is invoked with self_update=false, if at all.
-void m_config_notify_change_opt_ptr_notify(struct m_config *config, void *ptr);
-
-// Return all (visible) option names as NULL terminated string list.
-char **m_config_list_options(void *ta_parent, const struct m_config *config);
-
-void m_config_print_option_list(const struct m_config *config, const char *name);
-
-
-/* Find the profile with the given name.
- * \param config The config object.
- * \param arg The profile's name.
- * \return The profile object or NULL.
- */
-struct m_profile *m_config_get_profile0(const struct m_config *config,
- char *name);
-struct m_profile *m_config_get_profile(const struct m_config *config, bstr name);
-
-// Apply and clear the default profile - it's the only profile that new config
-// files do not simply append to (for configfile parser).
-void m_config_finish_default_profile(struct m_config *config, int flags);
-
-/* Get the profile with the given name, creating it if necessary.
- * \param config The config object.
- * \param arg The profile's name.
- * \return The profile object.
- */
-struct m_profile *m_config_add_profile(struct m_config *config, char *name);
-
-/* Set the description of a profile.
- * Used by the config file parser when defining a profile.
- *
- * \param p The profile object.
- * \param arg The profile's name.
- */
-void m_profile_set_desc(struct m_profile *p, bstr desc);
-
-/* Add an option to a profile.
- * Used by the config file parser when defining a profile.
- *
- * \param config The config object.
- * \param p The profile object.
- * \param name The option's name.
- * \param val The option's value.
- */
-int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
- bstr name, bstr val);
-
-/* Enables profile usage
- * Used by the config file parser when loading a profile.
- *
- * \param config The config object.
- * \param p The profile object.
- * \param flags M_SETOPT_* bits
- * Returns error code (<0) or 0 on success
- */
-int m_config_set_profile(struct m_config *config, char *name, int flags);
-
-struct mpv_node m_config_get_profiles(struct m_config *config);
-
-// Run async option updates here. This will call option_change_callback() on it.
-void m_config_set_update_dispatch_queue(struct m_config *config,
- struct mp_dispatch_queue *dispatch);
-
-// This can be used to create and synchronize per-thread option structs,
-// which then can be read without synchronization. No concurrent access to
-// the cache itself is allowed.
-struct m_config_cache {
- // The struct as indicated by m_config_cache_alloc's group parameter.
- // (Internally the same as internal->gdata[0]->udata.)
- void *opts;
- // Accumulated change flags. The user can set this to 0 to unset all flags.
- // They are set when calling any of the update functions. A flag is only set
- // once the new value is visible in ->opts.
- uint64_t change_flags;
-
- // Set to non-NULL for logging all option changes as they are retrieved
- // with one of the update functions (like m_config_cache_update()).
- struct mp_log *debug;
-
- // Do not access.
- struct config_cache *internal;
-};
-
-// Create a mirror copy from the global options.
-// Keep in mind that a m_config_cache object is not thread-safe; it merely
-// provides thread-safe access to the global options. All API functions for
-// the same m_config_cache object must synchronized, unless otherwise noted.
-// This does not create an initial change event (m_config_cache_update() will
-// return false), but note that a change might be asynchronously signaled at any
-// time.
-// ta_parent: parent for the returned allocation
-// global: option data source
-// group: the option group to return
-struct m_config_cache *m_config_cache_alloc(void *ta_parent,
- struct mpv_global *global,
- const struct m_sub_options *group);
-
-// If any of the options in the group possibly changes, call this callback. The
-// callback must not actually access the cache or anything option related.
-// Instead, it must wake up the thread that normally accesses the cache.
-void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
- void (*cb)(void *ctx), void *cb_ctx);
-
-// If any of the options in the group change, call this callback on the given
-// dispatch queue. This is higher level than m_config_cache_set_wakeup_cb(),
-// and you can do anything you want in the callback (assuming the dispatch
-// queue is processed in the same thread that accesses m_config_cache API).
-// To ensure clean shutdown, you must destroy the m_config_cache (or unset the
-// callback) before the dispatch queue is destroyed.
-void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
- struct mp_dispatch_queue *dispatch,
- void (*cb)(void *ctx), void *cb_ctx);
-
-// Update the options in cache->opts to current global values. Return whether
-// there was an update notification at all (which may or may not indicate that
-// some options have changed).
-// Keep in mind that while the cache->opts pointer does not change, the option
-// data itself will (e.g. string options might be reallocated).
-// New change flags are or-ed into cache->change_flags with this call (if you
-// use them, you should probably do cache->change_flags=0 before this call).
-bool m_config_cache_update(struct m_config_cache *cache);
-
-// Check for changes and return fine grained change information.
-// Warning: this conflicts with m_config_cache_update(). If you call
-// m_config_cache_update(), all options will be marked as "not changed",
-// and this function will return false. Also, calling this function and
-// then m_config_cache_update() is not supported, and may skip updating
-// some fields.
-// This returns true as long as there is a changed option, and false if all
-// changed options have been returned.
-// If multiple options have changed, the new option value is visible only once
-// this function has returned the change for it.
-// out_ptr: pointer to a void*, which is set to the cache->opts field associated
-// with the changed option if the function returns true; set to NULL
-// if no option changed.
-// returns: *out_ptr!=NULL (true if there was a changed option)
-bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **out_ptr);
-
-// Copy the option field pointed to by ptr to the global option storage. This
-// is sort of similar to m_config_set_option_raw(), except doesn't require
-// access to the main thread. (And you can't pass any flags.)
-// You write the new value to the option struct, and then call this function
-// with the pointer to it. You will not get a change notification for it (though
-// you might still get a redundant wakeup callback).
-// Changing the option struct and not calling this function before any update
-// function (like m_config_cache_update()) will leave the value inconsistent,
-// and will possibly (but not necessarily) overwrite it with the next update
-// call.
-// ptr: points to any field in cache->opts that is managed by an option. If
-// this is not the case, the function crashes for your own good.
-// returns: if true, this was an update; if false, shadow had same value
-bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr);
-
-// Like m_config_cache_alloc(), but return the struct (m_config_cache->opts)
-// directly, with no way to update the config. Basically this returns a copy
-// with a snapshot of the current option values.
-void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
- const struct m_sub_options *group);
-
-// Read a single global option in a thread-safe way. For multiple options,
-// use m_config_cache. The option must exist and match the provided type (the
-// type is used as a sanity check only). Performs semi-expensive lookup.
-// Warning: new code must not use this.
-void mp_read_option_raw(struct mpv_global *global, const char *name,
- const struct m_option_type *type, void *dst);
-
-#endif /* MPLAYER_M_CONFIG_H */
+#include "m_config_core.h" \ No newline at end of file
diff --git a/options/m_config_core.c b/options/m_config_core.c
new file mode 100644
index 0000000000..f82b886670
--- /dev/null
+++ b/options/m_config_core.c
@@ -0,0 +1,910 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <pthread.h>
+
+#include "m_config_core.h"
+#include "options/m_option.h"
+#include "common/common.h"
+#include "common/global.h"
+#include "common/msg.h"
+#include "common/msg_control.h"
+#include "misc/dispatch.h"
+#include "osdep/atomic.h"
+
+// For use with m_config_cache.
+struct m_config_shadow {
+ pthread_mutex_t lock;
+ // Incremented on every option change.
+ mp_atomic_uint64 ts;
+ // -- immutable after init
+ // List of m_sub_options instances.
+ // Index 0 is the top-level and is always present.
+ // Immutable after init.
+ // Invariant: a parent is always at a lower index than any of its children.
+ struct m_config_group *groups;
+ int num_groups;
+ // -- protected by lock
+ struct m_config_data *data; // protected shadow copy of the option data
+ struct config_cache **listeners;
+ int num_listeners;
+};
+
+// Represents a sub-struct (OPT_SUBSTRUCT()).
+struct m_config_group {
+ const struct m_sub_options *group;
+ int opt_count; // cached opt. count; group->opts[opt_count].name==NULL
+ int group_count; // 1 + number of all sub groups owned by this (so
+ // m_config_shadow.groups[idx..idx+group_count] is used
+ // by the entire tree of sub groups included by this
+ // group)
+ int parent_group; // index of parent group into m_config_shadow.groups[],
+ // or -1 for group 0
+ int parent_ptr; // ptr offset in the parent group's data, or -1 if
+ // none
+ const char *prefix; // concat_name(_, prefix, opt->name) => full name
+ // (the parent names are already included in this)
+};
+
+// A copy of option data. Used for the main option struct, the shadow data,
+// and copies for m_config_cache.
+struct m_config_data {
+ struct m_config_shadow *shadow; // option definitions etc., main data copy
+ int group_index; // start index into m_config.groups[]
+ struct m_group_data *gdata; // user struct allocation (our copy of data)
+ int num_gdata; // (group_index+num_gdata = end index)
+};
+
+struct config_cache {
+ struct m_config_cache *public;
+
+ struct m_config_data *data; // public data
+ struct m_config_data *src; // global data (currently ==shadow->data)
+ struct m_config_shadow *shadow; // global metadata
+ int group_start, group_end; // derived from data->group_index etc.
+ uint64_t ts; // timestamp of this data copy
+ bool in_list; // part of m_config_shadow->listeners[]
+ int upd_group; // for "incremental" change notification
+ int upd_opt;
+
+
+ // --- Implicitly synchronized by setting/unsetting wakeup_cb.
+ struct mp_dispatch_queue *wakeup_dispatch_queue;
+ void (*wakeup_dispatch_cb)(void *ctx);
+ void *wakeup_dispatch_cb_ctx;
+
+ // --- Protected by shadow->lock
+ void (*wakeup_cb)(void *ctx);
+ void *wakeup_cb_ctx;
+};
+
+// Per m_config_data state for each m_config_group.
+struct m_group_data {
+ char *udata; // pointer to group user option struct
+ uint64_t ts; // timestamp of the data copy
+};
+
+static const union m_option_value default_value = {0};
+
+static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
+ int parent_group_index, int parent_ptr,
+ const struct m_sub_options *subopts);
+
+static struct m_group_data *m_config_gdata(struct m_config_data *data,
+ int group_index)
+{
+ if (group_index < data->group_index ||
+ group_index >= data->group_index + data->num_gdata)
+ return NULL;
+
+ return &data->gdata[group_index - data->group_index];
+}
+
+// Like concat_name(), but returns either a, b, or buf. buf/buf_size is used as
+// target for snprintf(). (buf_size is recommended to be MAX_OPT_NAME_LEN.)
+static const char *concat_name_buf(char *buf, size_t buf_size,
+ const char *a, const char *b)
+{
+ assert(a);
+ assert(b);
+ if (!a[0])
+ return b;
+ if (!b[0])
+ return a;
+ snprintf(buf, buf_size, "%s-%s", a, b);
+ return buf;
+}
+
+// Return full option name from prefix (a) and option name (b). Returns either
+// a, b, or a talloc'ed string under ta_parent.
+static const char *concat_name(void *ta_parent, const char *a, const char *b)
+{
+ char buf[M_CONFIG_MAX_OPT_NAME_LEN];
+ const char *r = concat_name_buf(buf, sizeof(buf), a, b);
+ return r == buf ? talloc_strdup(ta_parent, r) : r;
+}
+
+static bool iter_next(struct m_config_shadow *shadow, int group_start,
+ int group_end, int32_t *p_id)
+{
+ int32_t id = *p_id;
+ int group_index = id == -1 ? group_start : id >> 16;
+ int opt_index = id == -1 ? -1 : id & 0xFFFF;
+
+ assert(group_index >= group_start && group_index <= group_end);
+
+ while (1) {
+ if (group_index >= group_end)
+ return false;
+
+ struct m_config_group *g = &shadow->groups[group_index];
+ const struct m_option *opts = g->group->opts;
+
+ assert(opt_index >= -1 && opt_index < g->opt_count);
+
+ opt_index += 1;
+
+ if (!opts || !opts[opt_index].name) {
+ group_index += 1;
+ opt_index = -1;
+ continue;
+ }
+
+ if (opts[opt_index].type == &m_option_type_subconfig)
+ continue;
+
+ *p_id = (group_index << 16) | opt_index;
+ return true;
+ }
+}
+
+bool m_config_shadow_get_next_opt(struct m_config_shadow *shadow, int32_t *p_id)
+{
+ return iter_next(shadow, 0, shadow->num_groups, p_id);
+}
+
+bool m_config_cache_get_next_opt(struct m_config_cache *cache, int32_t *p_id)
+{
+ return iter_next(cache->shadow, cache->internal->group_start,
+ cache->internal->group_end, p_id);
+}
+
+static void get_opt_from_id(struct m_config_shadow *shadow, int32_t id,
+ int *out_group_index, int *out_opt_index)
+{
+ int group_index = id >> 16;
+ int opt_index = id & 0xFFFF;
+
+ assert(group_index >= 0 && group_index < shadow->num_groups);
+ assert(opt_index >= 0 && opt_index < shadow->groups[group_index].opt_count);
+
+ *out_group_index = group_index;
+ *out_opt_index = opt_index;
+}
+
+const struct m_option *m_config_shadow_get_opt(struct m_config_shadow *shadow,
+ int32_t id)
+{
+ int group_index, opt_index;
+ get_opt_from_id(shadow, id, &group_index, &opt_index);
+
+ return &shadow->groups[group_index].group->opts[opt_index];
+}
+
+const char *m_config_shadow_get_opt_name(struct m_config_shadow *shadow,
+ int32_t id, char *buf, size_t buf_size)
+{
+ int group_index, opt_index;
+ get_opt_from_id(shadow, id, &group_index, &opt_index);
+
+ struct m_config_group *g = &shadow->groups[group_index];
+ return concat_name_buf(buf, buf_size, g->prefix,
+ g->group->opts[opt_index].name);
+}
+
+const void *m_config_shadow_get_opt_default(struct m_config_shadow *shadow,
+ int32_t id)
+{
+ int group_index, opt_index;
+ get_opt_from_id(shadow, id, &group_index, &opt_index);
+
+ const struct m_sub_options *subopt = shadow->groups[group_index].group;
+ const struct m_option *opt = &subopt->opts[opt_index];
+
+ if (opt->offset < 0)
+ return NULL;
+
+ if (opt->defval)
+ return opt->defval;
+
+ if (subopt->defaults)
+ return (char *)subopt->defaults + opt->offset;
+
+ return &default_value;
+}
+
+void *m_config_cache_get_opt_data(struct m_config_cache *cache, int32_t id)
+{
+ int group_index, opt_index;
+ get_opt_from_id(cache->shadow, id, &group_index, &opt_index);
+
+ assert(group_index >= cache->internal->group_start &&
+ group_index < cache->internal->group_end);
+
+ struct m_group_data *gd = m_config_gdata(cache->internal->data, group_index);
+ const struct m_option *opt =
+ &cache->shadow->groups[group_index].group->opts[opt_index];
+
+ return gd && opt->offset >= 0 ? gd->udata + opt->offset : NULL;
+}
+
+static uint64_t get_opt_change_mask(struct m_config_shadow *shadow, int group_index,
+ int group_root, const struct m_option *opt)
+{
+ uint64_t changed = opt->flags & UPDATE_OPTS_MASK;
+ while (group_index != group_root) {
+ struct m_config_group *g = &shadow->groups[group_index];
+ changed |= g->group->change_flags;
+ group_index = g->parent_group;
+ }
+ return changed;
+}
+
+uint64_t m_config_cache_get_option_change_mask(struct m_config_cache *cache,
+ int32_t id)
+{
+ struct m_config_shadow *shadow = cache->shadow;
+ int group_index, opt_index;
+ get_opt_from_id(shadow, id, &group_index, &opt_index);
+
+ assert(group_index >= cache->internal->group_start &&
+ group_index < cache->internal->group_end);
+
+ return get_opt_change_mask(cache->shadow, group_index,
+ cache->internal->data->group_index,
+ &shadow->groups[group_index].group->opts[opt_index]);
+}
+
+// The memcpys are supposed to work around the strict aliasing violation,
+// that would result if we just dereferenced a void** (where the void** is
+// actually casted from struct some_type* ). The dummy struct type is in
+// theory needed, because void* and struct pointers could have different
+// representations, while pointers to different struct types don't.
+static void *substruct_read_ptr(const void *ptr)
+{
+ struct mp_dummy_ *res;
+ memcpy(&res, ptr, sizeof(res));
+ return res;
+}
+static void substruct_write_ptr(void *ptr, void *val)
+{
+ struct mp_dummy_ *src = val;
+ memcpy(ptr, &src, sizeof(src));
+}
+
+// Initialize a field with a given value. In case this is dynamic data, it has
+// to be allocated and copied. src can alias dst.
+static void init_opt_inplace(const struct m_option *opt, void *dst,
+ const void *src)
+{
+ // The option will use dynamic memory allocation iff it has a free callback.
+ if (opt->type->free) {
+ union m_option_value temp;
+ memcpy(&temp, src, opt->type->size);
+ memset(dst, 0, opt->type->size);
+ m_option_copy(opt, dst, &temp);
+ } else if (src != dst) {
+ memcpy(dst, src, opt->type->size);
+ }
+}
+
+static void alloc_group(struct m_config_data *data, int group_index,
+ struct m_config_data *copy)
+{
+ assert(group_index == data->group_index + data->num_gdata);
+ assert(group_index < data->shadow->num_groups);
+ struct m_config_group *group = &data->shadow->groups[group_index];
+ const struct m_sub_options *opts = group->group;
+
+ MP_TARRAY_GROW(data, data->gdata, data->num_gdata);
+ struct m_group_data *gdata = &data->gdata[data->num_gdata++];
+
+ struct m_group_data *copy_gdata =
+ copy ? m_config_gdata(copy, group_index) : NULL;
+
+ *gdata = (struct m_group_data){
+ .udata = talloc_zero_size(data, opts->size),
+ .ts = copy_gdata ? copy_gdata->ts : 0,
+ };
+
+ if (opts->defaults)
+ memcpy(gdata->udata, opts->defaults, opts->size);
+
+ char *copy_src = copy_gdata ? copy_gdata->udata : NULL;
+
+ for (int n = 0; opts->opts && opts->opts[n].name; n++) {
+ const struct m_option *opt = &opts->opts[n];
+
+ if (opt->offset < 0 || opt->type->size == 0)
+ continue;
+
+ void *dst = gdata->udata + opt->offset;
+ const void *defptr = opt->defval ? opt->defval : dst;
+ if (copy_src)
+ defptr = copy_src + opt->offset;
+
+ init_opt_inplace(opt, dst, defptr);
+ }
+
+ // If there's a parent, update its pointer to the new struct.
+ if (group->parent_group >= data->group_index && group->parent_ptr >= 0) {
+ struct m_group_data *parent_gdata =
+ m_config_gdata(data, group->parent_group);
+ assert(parent_gdata);
+
+ substruct_write_ptr(parent_gdata->udata + group->parent_ptr, gdata->udata);
+ }
+}
+
+static void free_option_data(void *p)
+{
+ struct m_config_data *data = p;
+
+ for (int i = 0; i < data->num_gdata; i++) {
+ struct m_group_data *gdata = &data->gdata[i];
+ struct m_config_group *group =
+ &data->shadow->groups[data->group_index + i];
+ const struct m_option *opts = group->group->opts;
+
+ for (int n = 0; opts && opts[n].name; n++) {
+ const struct m_option *opt = &opts[n];
+
+ if (opt->offset >= 0 && opt->type->size > 0)
+ m_option_free(opt, gdata->udata + opt->offset);
+ }
+ }
+}
+
+// Allocate data using the option description in shadow, starting at group_index
+// (index into m_config.groups[]).
+// If copy is not NULL, copy all data from there (for groups which are in both
+// m_config_data instances), in all other cases init the data with the defaults.
+static struct m_config_data *allocate_option_data(void *ta_parent,
+ struct m_config_shadow *shadow,
+ int group_index,
+ struct m_config_data *copy)
+{
+ assert(group_index >= 0 && group_index < shadow->num_groups);
+ struct m_config_data *data = talloc_zero(ta_parent, struct m_config_data);
+ talloc_set_destructor(data, free_option_data);
+
+ data->shadow = shadow;
+ data->group_index = group_index;
+
+ struct m_config_group *root_group = &shadow->groups[group_index];
+ assert(root_group->group_count > 0);
+
+ for (int n = group_index; n < group_index + root_group->group_count; n++)
+ alloc_group(data, n, copy);
+
+ return data;
+}
+
+static void shadow_destroy(void *p)
+{
+ struct m_config_shadow *shadow = p;
+
+ // must all have been unregistered
+ assert(shadow->num_listeners == 0);
+
+ talloc_free(shadow->data);
+ pthread_mutex_destroy(&shadow->lock);
+}
+
+struct m_config_shadow *m_config_shadow_new(const struct m_sub_options *root)
+{
+ struct m_config_shadow *shadow = talloc_zero(NULL, struct m_config_shadow);
+ talloc_set_destructor(shadow, shadow_destroy);
+ pthread_mutex_init(&shadow->lock, NULL);
+
+ add_sub_group(shadow, NULL, -1, -1, root);
+
+ if (!root->size)
+ return shadow;
+
+ shadow->data = allocate_option_data(shadow, shadow, 0, NULL);
+
+ return shadow;
+}
+
+static void init_obj_settings_list(struct m_config_shadow *shadow,
+ int parent_group_index,
+ const struct m_obj_list *list)
+{
+ struct m_obj_desc desc;
+ for (int n = 0; ; n++) {
+ if (!list->get_desc(&desc, n))
+ break;
+ if (desc.global_opts) {
+ add_sub_group(shadow, NULL, parent_group_index, -1,
+ desc.global_opts);
+ }
+ if (list->use_global_options && desc.options) {
+ struct m_sub_options *conf = talloc_ptrtype(shadow, conf);
+ *conf = (struct m_sub_options){
+ .prefix = desc.options_prefix,
+ .opts = desc.options,
+ .defaults = desc.priv_defaults,
+ .size = desc.priv_size,
+ };
+ add_sub_group(shadow, NULL, parent_group_index, -1, conf);
+ }
+ }
+}
+
+static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
+ int parent_group_index, int parent_ptr,
+ const struct m_sub_options *subopts)
+{
+ // Can't be used multiple times.
+ for (int n = 0; n < shadow->num_groups; n++)
+ assert(shadow->groups[n].group != subopts);
+
+ if (!name_prefix)
+ name_prefix = "";
+ if (subopts->prefix && subopts->prefix[0]) {
+ assert(!name_prefix[0]);
+ name_prefix = subopts->prefix;
+ }
+
+ // You can only use UPDATE_ flags here.
+ assert(!(subopts->change_flags & ~(unsigned)UPDATE_OPTS_MASK));
+
+ assert(parent_group_index >= -1 && parent_group_index < shadow->num_groups);
+
+ int group_index = shadow->num_groups++;
+ MP_TARRAY_GROW(shadow, shadow->groups, group_index);
+ shadow->groups[group_index] = (struct m_config_group){
+ .group = subopts,
+ .parent_group = parent_group_index,
+ .parent_ptr = parent_ptr,
+ .prefix = name_prefix,
+ };
+
+ for (int i = 0; subopts->opts && subopts->opts[i].name; i++) {
+ const struct m_option *opt = &subopts->opts[i];
+
+ if (opt->type == &m_option_type_subconfig) {
+ const struct m_sub_options *new_subopts = opt->priv;
+
+ // Providing default structs in-place is not allowed.
+ if (opt->offset >= 0 && subopts->defaults) {
+ void *ptr = (char *)subopts->defaults + opt->offset;
+ assert(!substruct_read_ptr(ptr));
+ }
+
+ const char *prefix = concat_name(shadow, name_prefix, opt->name);
+ add_sub_group(shadow, prefix, group_index, opt->offset, new_subopts);
+
+ } else if (opt->type == &m_option_type_obj_settings_list) {
+ const struct m_obj_list *objlist = opt->priv;
+ init_obj_settings_list(shadow, group_index, objlist);
+ }
+
+ shadow->groups[group_index].opt_count = i + 1;
+ }
+
+ if (subopts->get_sub_options) {
+ for (int i = 0; ; i++) {
+ const struct m_sub_options *sub = NULL;
+ if (!subopts->get_sub_options(i, &sub))
+ break;
+ if (sub)
+ add_sub_group(shadow, NULL, group_index, -1, sub);
+ }
+ }
+
+ shadow->groups[group_index].group_count = shadow->num_groups - group_index;
+}
+
+static void cache_destroy(void *p)
+{
+ struct m_config_cache *cache = p;
+
+ // (technically speaking, being able to call them both without anything
+ // breaking is a feature provided by these functions)
+ m_config_cache_set_wakeup_cb(cache, NULL, NULL);
+ m_config_cache_set_dispatch_change_cb(cache, NULL, NULL, NULL);
+}
+
+struct m_config_cache *m_config_cache_from_shadow(void *ta_parent,
+ struct m_config_shadow *shadow,
+ const struct m_sub_options *group)
+{
+ int group_index = -1;
+
+ for (int n = 0; n < shadow->num_groups; n++) {
+ if (shadow->groups[n].group == group) {
+ group_index = n;
+ break;
+ }
+ }
+
+ assert(group_index >= 0); // invalid group (or not in option tree)
+
+ struct cache_alloc {
+ struct m_config_cache a;
+ struct config_cache b;
+ };
+ struct cache_alloc *alloc = talloc_zero(ta_parent, struct cache_alloc);
+ assert((void *)&alloc->a == (void *)alloc);
+ struct m_config_cache *cache = &alloc->a;
+ talloc_set_destructor(cache, cache_destroy);
+ cache->internal = &alloc->b;
+ cache->shadow = shadow;
+
+ struct config_cache *in = cache->internal;
+ in->shadow = shadow;
+ in->src = shadow->data;
+
+ pthread_mutex_lock(&shadow->lock);
+ in->data = allocate_option_data(cache, shadow, group_index, in->src);
+ pthread_mutex_unlock(&shadow->lock);
+
+ cache->opts = in->data->gdata[0].udata;
+
+ in->group_start = in->data->group_index;
+ in->group_end = in->group_start + in->data->num_gdata;
+ assert(shadow->groups[in->group_start].group_count == in->data->num_gdata);
+
+ in->upd_group = -1;
+
+ return cache;
+}
+
+struct m_config_cache *m_config_cache_alloc(void *ta_parent,
+ struct mpv_global *global,
+ const struct m_sub_options *group)
+{
+ return m_config_cache_from_shadow(ta_parent, global->config, group);
+}
+
+static void update_next_option(struct m_config_cache *cache, void **p_opt)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_data *dst = in->data;
+ struct m_config_data *src = in->src;
+
+ assert(src->group_index == 0); // must be the option root currently
+
+ *p_opt = NULL;
+
+ while (in->upd_group < dst->group_index + dst->num_gdata) {
+ struct m_group_data *gsrc = m_config_gdata(src, in->upd_group);
+ struct m_group_data *gdst = m_config_gdata(dst, in->upd_group);
+ assert(gsrc && gdst);
+
+ if (gdst->ts < gsrc->ts) {
+ struct m_config_group *g = &dst->shadow->groups[in->upd_group];
+ const struct m_option *opts = g->group->opts;
+
+ while (opts && opts[in->upd_opt].name) {
+ const struct m_option *opt = &opts[in->upd_opt];
+
+ if (opt->offset >= 0 && opt->type->size) {
+ void *dsrc = gsrc->udata + opt->offset;
+ void *ddst = gdst->udata + opt->offset;
+
+ if (!m_option_equal(opt, ddst, dsrc)) {
+ uint64_t ch = get_opt_change_mask(dst->shadow,
+ in->upd_group, dst->group_index, opt);
+
+ if (cache->debug) {
+ char *vdst = m_option_print(opt, ddst);
+ char *vsrc = m_option_print(opt, dsrc);
+ mp_warn(cache->debug, "Option '%s' changed from "
+ "'%s' to' %s' (flags = 0x%"PRIx64")\n",
+ opt->name, vdst, vsrc, ch);
+ talloc_free(vdst);
+ talloc_free(vsrc);
+ }
+
+ m_option_copy(opt, ddst, dsrc);
+ cache->change_flags |= ch;
+
+ in->upd_opt++; // skip this next time
+ *p_opt = ddst;
+ return;
+ }
+ }
+
+ in->upd_opt++;
+ }
+
+ gdst->ts = gsrc->ts;
+ }
+
+ in->upd_group++;
+ in->upd_opt = 0;
+ }
+
+ in->upd_group = -1;
+}
+
+static bool cache_check_update(struct m_config_cache *cache)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_shadow *shadow = in->shadow;
+
+ // Using atomics and checking outside of the lock - it's unknown whether
+ // this makes it faster or slower. Just cargo culting it.
+ uint64_t new_ts = atomic_load(&shadow->ts);
+ if (in->ts >= new_ts)
+ return false;
+
+ in->ts = new_ts;
+ in->upd_group = in->data->group_index;
+ in->upd_opt = 0;
+ return true;
+}
+
+bool m_config_cache_update(struct m_config_cache *cache)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_shadow *shadow = in->shadow;
+
+ if (!cache_check_update(cache))
+ return false;
+
+ pthread_mutex_lock(&shadow->lock);
+ bool res = false;
+ while (1) {
+ void *p;
+ update_next_option(cache, &p);
+ if (!p)
+ break;
+ res = true;
+ }
+ pthread_mutex_unlock(&shadow->lock);
+ return res;
+}
+
+bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **opt)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_shadow *shadow = in->shadow;
+
+ *opt = NULL;
+ if (!cache_check_update(cache) && in->upd_group < 0)
+ return false;
+
+ pthread_mutex_lock(&shadow->lock);
+ update_next_option(cache, opt);
+ pthread_mutex_unlock(&shadow->lock);
+ return !!*opt;
+}
+
+static void find_opt(struct m_config_shadow *shadow, struct m_config_data *data,
+ void *ptr, int *group_idx, int *opt_idx)
+{
+ *group_idx = -1;
+ *opt_idx = -1;
+
+ for (int n = data->group_index; n < data->group_index + data->num_gdata; n++)
+ {
+ struct m_group_data *gd = m_config_gdata(data, n);
+ struct m_config_group *g = &shadow->groups[n];
+ const struct m_option *opts = g->group->opts;
+
+ for (int i = 0; opts && opts[i].name; i++) {
+ const struct m_option *opt = &opts[i];
+
+ if (opt->offset >= 0 && opt->type->size &&
+ ptr == gd->udata + opt->offset)
+ {
+ *group_idx = n;
+ *opt_idx = i;
+ return;
+ }
+ }
+ }
+}
+
+bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_shadow *shadow = in->shadow;
+
+ int group_idx = -1;
+ int opt_idx = -1;
+ find_opt(shadow, in->data, ptr, &group_idx, &opt_idx);
+
+ // ptr was not in cache->opts, or no option declaration matching it.
+ assert(group_idx >= 0);
+
+ struct m_config_group *g = &shadow->groups[group_idx];
+ const struct m_option *opt = &g->group->opts[opt_idx];
+
+ pthread_mutex_lock(&shadow->lock);
+
+ struct m_group_data *gdst = m_config_gdata(in->data, group_idx);
+ struct m_group_data *gsrc = m_config_gdata(in->src, group_idx);
+ assert(gdst && gsrc);
+
+ bool changed = !m_option_equal(opt, gsrc->udata + opt->offset, ptr);
+ if (changed) {
+ m_option_copy(opt, gsrc->udata + opt->offset, ptr);
+
+ gsrc->ts = atomic_fetch_add(&shadow->ts, 1) + 1;
+
+ for (int n = 0; n < shadow->num_listeners; n++) {
+ struct config_cache *listener = shadow->listeners[n];
+ if (listener->wakeup_cb && m_config_gdata(listener->data, group_idx))
+ listener->wakeup_cb(listener->wakeup_cb_ctx);
+ }
+ }
+
+ pthread_mutex_unlock(&shadow->lock);
+
+ return changed;
+}
+
+void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
+ void (*cb)(void *ctx), void *cb_ctx)
+{
+ struct config_cache *in = cache->internal;
+ struct m_config_shadow *shadow = in->shadow;
+
+ pthread_mutex_lock(&shadow->lock);
+ if (in->in_list) {
+ for (int n = 0; n < shadow->num_listeners; n++) {
+ if (shadow->listeners[n] == in) {
+ MP_TARRAY_REMOVE_AT(shadow->listeners, shadow->num_listeners, n);
+ break;
+ }
+ }
+ for (int n = 0; n < shadow->num_listeners; n++)
+ assert(shadow->listeners[n] != in); // only 1 wakeup_cb per cache
+ // (The deinitialization path relies on this to free all memory.)
+ if (!shadow->num_listeners) {
+ talloc_free(shadow->listeners);
+ shadow->listeners = NULL;
+ }
+ }
+ if (cb) {
+ MP_TARRAY_APPEND(NULL, shadow->listeners, shadow->num_listeners, in);
+ in->in_list = true;
+ in->wakeup_cb = cb;
+ in->wakeup_cb_ctx = cb_ctx;
+ }
+ pthread_mutex_unlock(&shadow->lock);
+}
+
+static void dispatch_notify(void *p)
+{
+ struct config_cache *in = p;
+
+ assert(in->wakeup_dispatch_queue);
+ mp_dispatch_enqueue_notify(in->wakeup_dispatch_queue,
+ in->wakeup_dispatch_cb,
+ in->wakeup_dispatch_cb_ctx);
+}
+
+void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
+ struct mp_dispatch_queue *dispatch,
+ void (*cb)(void *ctx), void *cb_ctx)
+{
+ struct config_cache *in = cache->internal;
+
+ // Removing the old one is tricky. First make sure no new notifications will
+ // come.
+ m_config_cache_set_wakeup_cb(cache, NULL, NULL);
+ // Remove any pending notifications (assume we're on the same thread as
+ // any potential mp_dispatch_queue_process() callers).
+ if (in->wakeup_dispatch_queue) {
+ mp_dispatch_cancel_fn(in->wakeup_dispatch_queue,
+ in->wakeup_dispatch_cb,
+ in->wakeup_dispatch_cb_ctx);
+ }
+
+ in->wakeup_dispatch_queue = NULL;
+ in->wakeup_dispatch_cb = NULL;
+ in->wakeup_dispatch_cb_ctx = NULL;
+
+ if (cb) {
+ in->wakeup_dispatch_queue = dispatch;
+ in->wakeup_dispatch_cb = cb;
+ in->wakeup_dispatch_cb_ctx = cb_ctx;
+ m_config_cache_set_wakeup_cb(cache, dispatch_notify, in);
+ }
+}
+
+void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
+ const struct m_sub_options *group)
+{
+ struct m_config_cache *cache = m_config_cache_alloc(NULL, global, group);
+ // Make talloc_free(cache->opts) free the entire cache.
+ ta_set_parent(cache->opts, ta_parent);
+ ta_set_parent(cache, cache->opts);
+ return cache->opts;
+}
+
+void mp_read_option_raw(struct mpv_global *global, const char *name,
+ const struct m_option_type *type, void *dst)
+{
+ struct m_config_shadow *shadow = global->config;
+
+ int32_t optid = -1;
+ while (m_config_shadow_get_next_opt(shadow, &optid)) {
+ char buf[M_CONFIG_MAX_OPT_NAME_LEN];
+ const char *opt_name =
+ m_config_shadow_get_opt_name(shadow, optid, buf, sizeof(buf));
+
+ if (strcmp(name, opt_name) == 0) {
+ const struct m_option *opt = m_config_shadow_get_opt(shadow, optid);
+
+ int group_index, opt_index;
+ get_opt_from_id(shadow, optid, &group_index, &opt_index);
+
+ struct m_group_data *gdata = m_config_gdata(shadow->data, group_index);
+ assert(gdata);
+
+ assert(opt->offset >= 0);
+ assert(opt->type == type);
+
+ memset(dst, 0, opt->type->size);
+ m_option_copy(opt, dst, gdata->udata + opt->offset);
+ return;
+ }
+ }
+
+ assert(0); // not found
+}
+
+static const struct m_config_group *find_group(struct mpv_global *global,
+ const struct m_option *cfg)
+{
+ struct m_config_shadow *shadow = global->config;
+
+ for (int n = 0; n < shadow->num_groups; n++) {
+ if (shadow->groups[n].group->opts == cfg)
+ return &shadow->groups[n];
+ }
+
+ return NULL;
+}
+
+void *m_config_group_from_desc(void *ta_parent, struct mp_log *log,
+ struct mpv_global *global, struct m_obj_desc *desc, const char *name)
+{
+ const struct m_config_group *group = find_group(global, desc->options);
+ if (group) {
+ return mp_get_config_group(ta_parent, global, group->group);
+ } else {
+ void *d = talloc_zero_size(ta_parent, desc->priv_size);
+ if (desc->priv_defaults)
+ memcpy(d, desc->priv_defaults, desc->priv_size);
+ return d;
+ }
+}
diff --git a/options/m_config_core.h b/options/m_config_core.h
new file mode 100644
index 0000000000..c4902be9d1
--- /dev/null
+++ b/options/m_config_core.h
@@ -0,0 +1,201 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_M_CONFIG_H
+#define MPLAYER_M_CONFIG_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+struct mp_dispatch_queue;
+struct m_sub_options;
+struct m_option_type;
+struct m_option;
+struct mpv_global;
+
+// This can be used to create and synchronize per-thread option structs,
+// which then can be read without synchronization. No concurrent access to
+// the cache itself is allowed.
+struct m_config_cache {
+ // The struct as indicated by m_config_cache_alloc's group parameter.
+ // (Internally the same as internal->gdata[0]->udata.)
+ void *opts;
+ // Accumulated change flags. The user can set this to 0 to unset all flags.
+ // They are set when calling any of the update functions. A flag is only set
+ // once the new value is visible in ->opts.
+ uint64_t change_flags;
+
+ // Set to non-NULL for logging all option changes as they are retrieved
+ // with one of the update functions (like m_config_cache_update()).
+ struct mp_log *debug;
+
+ // Global instance of option data. Read only.
+ struct m_config_shadow *shadow;
+
+ // Do not access.
+ struct config_cache *internal;
+};
+
+// Maximum possibly option name buffer length (as it appears to the user).
+#define M_CONFIG_MAX_OPT_NAME_LEN 80
+
+// Create a mirror copy from the global options.
+// Keep in mind that a m_config_cache object is not thread-safe; it merely
+// provides thread-safe access to the global options. All API functions for
+// the same m_config_cache object must synchronized, unless otherwise noted.
+// This does not create an initial change event (m_config_cache_update() will
+// return false), but note that a change might be asynchronously signaled at any
+// time.
+// This simply calls m_config_cache_from_shadow(ta_parent, global->shadow, group).
+// ta_parent: parent for the returned allocation
+// global: option data source
+// group: the option group to return
+struct m_config_cache *m_config_cache_alloc(void *ta_parent,
+ struct mpv_global *global,
+ const struct m_sub_options *group);
+
+// If any of the options in the group possibly changes, call this callback. The
+// callback must not actually access the cache or anything option related.
+// Instead, it must wake up the thread that normally accesses the cache.
+void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
+ void (*cb)(void *ctx), void *cb_ctx);
+
+// If any of the options in the group change, call this callback on the given
+// dispatch queue. This is higher level than m_config_cache_set_wakeup_cb(),
+// and you can do anything you want in the callback (assuming the dispatch
+// queue is processed in the same thread that accesses m_config_cache API).
+// To ensure clean shutdown, you must destroy the m_config_cache (or unset the
+// callback) before the dispatch queue is destroyed.
+void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
+ struct mp_dispatch_queue *dispatch,
+ void (*cb)(void *ctx), void *cb_ctx);
+
+// Update the options in cache->opts to current global values. Return whether
+// there was an update notification at all (which may or may not indicate that
+// some options have changed).
+// Keep in mind that while the cache->opts pointer does not change, the option
+// data itself will (e.g. string options might be reallocated).
+// New change flags are or-ed into cache->change_flags with this call (if you
+// use them, you should probably do cache->change_flags=0 before this call).
+bool m_config_cache_update(struct m_config_cache *cache);
+
+// Check for changes and return fine grained change information.
+// Warning: this conflicts with m_config_cache_update(). If you call
+// m_config_cache_update(), all options will be marked as "not changed",
+// and this function will return false. Also, calling this function and
+// then m_config_cache_update() is not supported, and may skip updating
+// some fields.
+// This returns true as long as there is a changed option, and false if all
+// changed options have been returned.
+// If multiple options have changed, the new option value is visible only once
+// this function has returned the change for it.
+// out_ptr: pointer to a void*, which is set to the cache->opts field associated
+// with the changed option if the function returns true; set to NULL
+// if no option changed.
+// returns: *out_ptr!=NULL (true if there was a changed option)
+bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **out_ptr);
+
+// Copy the option field pointed to by ptr to the global option storage. This
+// is sort of similar to m_config_set_option_raw(), except doesn't require
+// access to the main thread. (And you can't pass any flags.)
+// You write the new value to the option struct, and then call this function
+// with the pointer to it. You will not get a change notification for it (though
+// you might still get a redundant wakeup callback).
+// Changing the option struct and not calling this function before any update
+// function (like m_config_cache_update()) will leave the value inconsistent,
+// and will possibly (but not necessarily) overwrite it with the next update
+// call.
+// ptr: points to any field in cache->opts that is managed by an option. If
+// this is not the case, the function crashes for your own good.
+// returns: if true, this was an update; if false, shadow had same value
+bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr);
+
+// Like m_config_cache_alloc(), but return the struct (m_config_cache->opts)
+// directly, with no way to update the config. Basically this returns a copy
+// with a snapshot of the current option values.
+void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
+ const struct m_sub_options *group);
+
+// Read a single global option in a thread-safe way. For multiple options,
+// use m_config_cache. The option must exist and match the provided type (the
+// type is used as a sanity check only). Performs semi-expensive lookup.
+// Warning: new code must not use this.
+void mp_read_option_raw(struct mpv_global *global, const char *name,
+ const struct m_option_type *type, void *dst);
+
+// Allocate a priv struct that is backed by global options (like AOs and VOs,
+// anything that uses m_obj_list.use_global_options == true).
+// The result contains a snapshot of the current option values of desc->options.
+// For convenience, desc->options can be NULL; then priv struct is allocated
+// with just zero (or priv_defaults if set).
+// Bad function.
+struct m_obj_desc;
+void *m_config_group_from_desc(void *ta_parent, struct mp_log *log,
+ struct mpv_global *global, struct m_obj_desc *desc, const char *name);
+
+// Allocate new option shadow storage with all options set to defaults.
+// root must stay valid for the lifetime of the return value.
+// Result can be freed with ta_free().
+struct m_config_shadow *m_config_shadow_new(const struct m_sub_options *root);
+
+// See m_config_cache_alloc().
+struct m_config_cache *m_config_cache_from_shadow(void *ta_parent,
+ struct m_config_shadow *shadow,
+ const struct m_sub_options *group);
+
+// Iterate over all registered global options. *p_id must be set to -1 when this
+// is called for the first time. Each time this call returns true, *p_id is set
+// to a new valid option ID. p_id must not be changed for the next call. If
+// false is returned, iteration ends.
+bool m_config_shadow_get_next_opt(struct m_config_shadow *shadow, int32_t *p_id);
+
+// Similar to m_config_shadow_get_next_opt(), but return only options that are
+// covered by the m_config_cache.
+bool m_config_cache_get_next_opt(struct m_config_cache *cache, int32_t *p_id);
+
+// Return the m_option that was used to declare this option.
+// id must be a valid option ID as returned by m_config_shadow_get_next_opt() or
+// m_config_cache_get_next_opt().
+const struct m_option *m_config_shadow_get_opt(struct m_config_shadow *shadow,
+ int32_t id);
+
+// Return the full (global) option name. buf must be supplied, but may not
+// always be used. It should have the size M_CONFIG_MAX_OPT_NAME_LEN.
+// The returned point points either to buf or a static string.
+// id must be a valid option ID as returned by m_config_shadow_get_next_opt() or
+// m_config_cache_get_next_opt().
+const char *m_config_shadow_get_opt_name(struct m_config_shadow *shadow,
+ int32_t id, char *buf, size_t buf_size);
+
+// Pointer to default value, using m_option.type. NULL if option without data.
+// id must be a valid option ID as returned by m_config_shadow_get_next_opt() or
+// m_config_cache_get_next_opt().
+const void *m_config_shadow_get_opt_default(struct m_config_shadow *shadow,
+ int32_t id);
+
+// Return the pointer to the allocated option data (the same pointers that are
+// returned by m_config_cache_get_next_changed()). NULL if option without data.
+// id must be a valid option ID as returned by m_config_cache_get_next_opt().
+void *m_config_cache_get_opt_data(struct m_config_cache *cache, int32_t id);
+
+// Return or-ed UPDATE_OPTS_MASK part of the option and containing sub-options.
+// id must be a valid option ID as returned by m_config_cache_get_next_opt().
+uint64_t m_config_cache_get_option_change_mask(struct m_config_cache *cache,
+ int32_t id);
+
+#endif /* MPLAYER_M_CONFIG_H */
diff --git a/options/m_config.c b/options/m_config_frontend.c
index e38a3b12f6..467c13eb8f 100644
--- a/options/m_config.c
+++ b/options/m_config_frontend.c
@@ -15,11 +15,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-/// \file
-/// \ingroup Config
-
-#include "config.h"
-
+#include <float.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
@@ -31,9 +27,8 @@
#include "libmpv/client.h"
-#include "mpv_talloc.h"
-
#include "m_config.h"
+#include "m_config_frontend.h"
#include "options/m_option.h"
#include "common/common.h"
#include "common/global.h"
@@ -45,87 +40,12 @@
extern const char mp_help_text[];
-static const union m_option_value default_value;
-
// Profiles allow to predefine some sets of options that can then
// be applied later on with the internal -profile option.
#define MAX_PROFILE_DEPTH 20
// Maximal include depth.
#define MAX_RECURSION_DEPTH 8
-// Maximum possibly option name length (as it appears to the user).
-#define MAX_OPT_NAME_LEN 80
-
-// For use with m_config_cache.
-struct m_config_shadow {
- pthread_mutex_t lock;
- // Incremented on every option change.
- mp_atomic_uint64 ts;
- // -- immutable after init
- // List of m_sub_options instances.
- // Index 0 is the top-level and is always present.
- // Immutable after init.
- // Invariant: a parent is always at a lower index than any of its children.
- struct m_config_group *groups;
- int num_groups;
- // -- protected by lock
- struct m_config_data *data; // protected shadow copy of the option data
- struct config_cache **listeners;
- int num_listeners;
-};
-
-// Represents a sub-struct (OPT_SUBSTRUCT()).
-struct m_config_group {
- const struct m_sub_options *group;
- int group_count; // 1 + number of all sub groups owned by this (so
- // m_config_shadow.groups[idx..idx+group_count] is used
- // by the entire tree of sub groups included by this
- // group)
- int parent_group; // index of parent group into m_config_shadow.groups[],
- // or -1 for group 0
- int parent_ptr; // ptr offset in the parent group's data, or -1 if
- // none
- const char *prefix; // concat_name(_, prefix, opt->name) => full name
- // (the parent names are already included in this)
-};
-
-// A copy of option data. Used for the main option struct, the shadow data,
-// and copies for m_config_cache.
-struct m_config_data {
- struct m_config_shadow *shadow; // option definitions etc., main data copy
- int group_index; // start index into m_config.groups[]
- struct m_group_data *gdata; // user struct allocation (our copy of data)
- int num_gdata; // (group_index+num_gdata = end index)
-};
-
-struct config_cache {
- struct m_config_cache *public;
-
- struct m_config_data *data; // public data
- struct m_config_data *src; // global data (currently ==shadow->data)
- struct m_config_shadow *shadow; // global metadata
- uint64_t ts; // timestamp of this data copy
- bool in_list; // part of m_config_shadow->listeners[]
- int upd_group; // for "incremental" change notification
- int upd_opt;
-
-
- // --- Implicitly synchronized by setting/unsetting wakeup_cb.
- struct mp_dispatch_queue *wakeup_dispatch_queue;
- void (*wakeup_dispatch_cb)(void *ctx);
- void *wakeup_dispatch_cb_ctx;
-
- // --- Protected by shadow->lock
- void (*wakeup_cb)(void *ctx);
- void *wakeup_cb_ctx;
-};
-
-// Per m_config_data state for each m_config_group.
-struct m_group_data {
- char *udata; // pointer to group user option struct
- uint64_t ts; // timestamp of the data copy
-};
-
struct m_profile {
struct m_profile *next;
char *name;
@@ -142,109 +62,6 @@ struct m_opt_backup {
void *backup;
};
-static struct m_config_cache *m_config_cache_alloc_internal(void *ta_parent,
- struct m_config_shadow *shadow,
- const struct m_sub_options *group);
-static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
- int parent_group_index, int parent_ptr,
- const struct m_sub_options *subopts);
-
-static struct m_group_data *m_config_gdata(struct m_config_data *data,
- int group_index)
-{
- if (group_index < data->group_index ||
- group_index >= data->group_index + data->num_gdata)
- return NULL;
-
- return &data->gdata[group_index - data->group_index];
-}
-
-// Like concat_name(), but returns either a, b, or buf. buf/buf_size is used as
-// target for snprintf(). (buf_size is recommended to be MAX_OPT_NAME_LEN.)
-static const char *concat_name_buf(char *buf, size_t buf_size,
- const char *a, const char *b)
-{
- assert(a);
- assert(b);
- if (!a[0])
- return b;
- if (!b[0])
- return a;
- snprintf(buf, buf_size, "%s-%s", a, b);
- return buf;
-}
-
-// Return full option name from prefix (a) and option name (b). Returns either
-// a, b, or a talloc'ed string under ta_parent.
-static const char *concat_name(void *ta_parent, const char *a, const char *b)
-{
- char buf[MAX_OPT_NAME_LEN];
- const char *r = concat_name_buf(buf, sizeof(buf), a, b);
- return r == buf ? talloc_strdup(ta_parent, r) : r;
-}
-
-struct opt_iterate_state {
- // User can read these fields.
- int group_index;
- int opt_index;
- const struct m_option *opt;
- const char *full_name; // may point to name_buf
-
- // Internal.
- int group_index_end;
- char name_buf[MAX_OPT_NAME_LEN];
- struct m_config_shadow *shadow;
-};
-
-// Start iterating all options and sub-options in the given group.
-static void opt_iterate_init(struct opt_iterate_state *iter,
- struct m_config_shadow *shadow, int group_index)
-{
- assert(group_index >= 0 && group_index < shadow->num_groups);
- iter->group_index = group_index;
- iter->group_index_end = group_index + shadow->groups[group_index].group_count;
- iter->opt_index = -1;
- iter->shadow = shadow;
-}
-
-// Get the first or next option. Returns false if end reached. If this returns
-// true, most fields in *iter are valid.
-// This does not return pseudo-option entries (like m_option_type_subconfig).
-static bool opt_iterate_next(struct opt_iterate_state *iter)
-{
- if (iter->group_index < 0)
- return false;
-
-
- while (1) {
- if (iter->group_index >= iter->group_index_end) {
- iter->group_index = -1;
- return false;
- }
-
- iter->opt_index += 1;
-
- struct m_config_group *g = &iter->shadow->groups[iter->group_index];
- const struct m_option *opts = g->group->opts;
-
- if (!opts || !opts[iter->opt_index].name) {
- iter->group_index += 1;
- iter->opt_index = -1;
- continue;
- }
-
- iter->opt = &opts[iter->opt_index];
-
- if (iter->opt->type == &m_option_type_subconfig)
- continue;
-
- iter->full_name = concat_name_buf(iter->name_buf, sizeof(iter->name_buf),
- g->prefix, iter->opt->name);
- return true;
- }
- assert(0);
-}
-
static void list_profiles(struct m_config *config)
{
MP_INFO(config, "Available profiles:\n");
@@ -292,205 +109,6 @@ static int show_profile(struct m_config *config, bstr param)
return M_OPT_EXIT;
}
-// The memcpys are supposed to work around the strict aliasing violation,
-// that would result if we just dereferenced a void** (where the void** is
-// actually casted from struct some_type* ). The dummy struct type is in
-// theory needed, because void* and struct pointers could have different
-// representations, while pointers to different struct types don't.
-static void *substruct_read_ptr(const void *ptr)
-{
- struct mp_dummy_ *res;
- memcpy(&res, ptr, sizeof(res));
- return res;
-}
-static void substruct_write_ptr(void *ptr, void *val)
-{
- struct mp_dummy_ *src = val;
- memcpy(ptr, &src, sizeof(src));
-}
-
-// Initialize a field with a given value. In case this is dynamic data, it has
-// to be allocated and copied. src can alias dst.
-static void init_opt_inplace(const struct m_option *opt, void *dst,
- const void *src)
-{
- // The option will use dynamic memory allocation iff it has a free callback.
- if (opt->type->free) {
- union m_option_value temp;
- memcpy(&temp, src, opt->type->size);
- memset(dst, 0, opt->type->size);
- m_option_copy(opt, dst, &temp);
- } else if (src != dst) {
- memcpy(dst, src, opt->type->size);
- }
-}
-
-static void alloc_group(struct m_config_data *data, int group_index,
- struct m_config_data *copy)
-{
- assert(group_index == data->group_index + data->num_gdata);
- assert(group_index < data->shadow->num_groups);
- struct m_config_group *group = &data->shadow->groups[group_index];
- const struct m_sub_options *opts = group->group;
-
- MP_TARRAY_GROW(data, data->gdata, data->num_gdata);
- struct m_group_data *gdata = &data->gdata[data->num_gdata++];
-
- struct m_group_data *copy_gdata =
- copy ? m_config_gdata(copy, group_index) : NULL;
-
- *gdata = (struct m_group_data){
- .udata = talloc_zero_size(data, opts->size),
- .ts = copy_gdata ? copy_gdata->ts : 0,
- };
-
- if (opts->defaults)
- memcpy(gdata->udata, opts->defaults, opts->size);
-
- char *copy_src = copy_gdata ? copy_gdata->udata : NULL;
-
- for (int n = 0; opts->opts && opts->opts[n].name; n++) {
- const struct m_option *opt = &opts->opts[n];
-
- if (opt->offset < 0 || opt->type->size == 0)
- continue;
-
- void *dst = gdata->udata + opt->offset;
- const void *defptr = opt->defval ? opt->defval : dst;
- if (copy_src)
- defptr = copy_src + opt->offset;
-
- init_opt_inplace(opt, dst, defptr);
- }
-
- // If there's a parent, update its pointer to the new struct.
- if (group->parent_group >= data->group_index && group->parent_ptr >= 0) {
- struct m_group_data *parent_gdata =
- m_config_gdata(data, group->parent_group);
- assert(parent_gdata);
-
- substruct_write_ptr(parent_gdata->udata + group->parent_ptr, gdata->udata);
- }
-}
-
-static void free_option_data(void *p)
-{
- struct m_config_data *data = p;
-
- for (int i = 0; i < data->num_gdata; i++) {
- struct m_group_data *gdata = &data->gdata[i];
- struct m_config_group *group =
- &data->shadow->groups[data->group_index + i];
- const struct m_option *opts = group->group->opts;
-
- for (int n = 0; opts && opts[n].name; n++) {
- const struct m_option *opt = &opts[n];
-
- if (opt->offset >= 0 && opt->type->size > 0)
- m_option_free(opt, gdata->udata + opt->offset);
- }
- }
-}
-
-// Allocate data using the option description in shadow, starting at group_index
-// (index into m_config.groups[]).
-// If copy is not NULL, copy all data from there (for groups which are in both
-// m_config_data instances), in all other cases init the data with the defaults.
-static struct m_config_data *allocate_option_data(void *ta_parent,
- struct m_config_shadow *shadow,
- int group_index,
- struct m_config_data *copy)
-{
- assert(group_index >= 0 && group_index < shadow->num_groups);
- struct m_config_data *data = talloc_zero(ta_parent, struct m_config_data);
- talloc_set_destructor(data, free_option_data);
-
- data->shadow = shadow;
- data->group_index = group_index;
-
- struct m_config_group *root_group = &shadow->groups[group_index];
- assert(root_group->group_count > 0);
-
- for (int n = group_index; n < group_index + root_group->group_count; n++)
- alloc_group(data, n, copy);
-
- return data;
-}
-
-static void shadow_destroy(void *p)
-{
- struct m_config_shadow *shadow = p;
-
- // must all have been unregistered
- assert(shadow->num_listeners == 0);
-
- talloc_free(shadow->data);
- pthread_mutex_destroy(&shadow->lock);
-}
-
-static struct m_config_shadow *m_config_shadow_new(const struct m_sub_options *root)
-{
- struct m_config_shadow *shadow = talloc_zero(NULL, struct m_config_shadow);
- talloc_set_destructor(shadow, shadow_destroy);
- pthread_mutex_init(&shadow->lock, NULL);
-
- add_sub_group(shadow, NULL, -1, -1, root);
-
- if (!root->size)
- return shadow;
-
- shadow->data = allocate_option_data(shadow, shadow, 0, NULL);
-
- return shadow;
-}
-
-static void config_destroy(void *p)
-{
- struct m_config *config = p;
- config->option_change_callback = NULL;
- m_config_restore_backups(config);
-
- talloc_free(config->cache);
- talloc_free(config->shadow);
-}
-
-struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
- const struct m_sub_options *root)
-{
- struct m_config *config = talloc(talloc_ctx, struct m_config);
- talloc_set_destructor(config, config_destroy);
- *config = (struct m_config){.log = log,};
-
- config->shadow = m_config_shadow_new(root);
-
- if (root->size) {
- config->cache =
- m_config_cache_alloc_internal(config, config->shadow, root);
- config->optstruct = config->cache->opts;
- }
-
- struct opt_iterate_state it;
- opt_iterate_init(&it, config->shadow, 0);
- while (opt_iterate_next(&it)) {
- struct m_config_option co = {
- .name = talloc_strdup(config, it.full_name),
- .opt = it.opt,
- .group_index = it.group_index,
- };
-
- struct m_group_data *gdata = config->cache
- ? m_config_gdata(config->cache->internal->data, it.group_index)
- : NULL;
-
- if (gdata && co.opt->offset >= 0)
- co.data = gdata->udata + co.opt->offset;
-
- MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
- }
-
- return config;
-}
-
static struct m_config *m_config_from_obj_desc(void *talloc_ctx,
struct mp_log *log,
struct mpv_global *global,
@@ -517,33 +135,6 @@ struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
return m_config_from_obj_desc(talloc_ctx, log, NULL, desc);
}
-static const struct m_config_group *find_group(struct mpv_global *global,
- const struct m_option *cfg)
-{
- struct m_config_shadow *shadow = global->config;
-
- for (int n = 0; n < shadow->num_groups; n++) {
- if (shadow->groups[n].group->opts == cfg)
- return &shadow->groups[n];
- }
-
- return NULL;
-}
-
-void *m_config_group_from_desc(void *ta_parent, struct mp_log *log,
- struct mpv_global *global, struct m_obj_desc *desc, const char *name)
-{
- const struct m_config_group *group = find_group(global, desc->options);
- if (group) {
- return mp_get_config_group(ta_parent, global, group->group);
- } else {
- void *d = talloc_zero_size(ta_parent, desc->priv_size);
- if (desc->priv_defaults)
- memcpy(d, desc->priv_defaults, desc->priv_size);
- return d;
- }
-}
-
static int m_config_set_obj_params(struct m_config *config, struct mp_log *log,
struct mpv_global *global,
struct m_obj_desc *desc, char **args)
@@ -630,106 +221,6 @@ void m_config_backup_all_opts(struct m_config *config)
ensure_backup(config, &config->opts[n]);
}
-static void init_obj_settings_list(struct m_config_shadow *shadow,
- int parent_group_index,
- const struct m_obj_list *list)
-{
- struct m_obj_desc desc;
- for (int n = 0; ; n++) {
- if (!list->get_desc(&desc, n))
- break;
- if (desc.global_opts) {
- add_sub_group(shadow, NULL, parent_group_index, -1,
- desc.global_opts);
- }
- if (list->use_global_options && desc.options) {
- struct m_sub_options *conf = talloc_ptrtype(shadow, conf);
- *conf = (struct m_sub_options){
- .prefix = desc.options_prefix,
- .opts = desc.options,
- .defaults = desc.priv_defaults,
- .size = desc.priv_size,
- };
- add_sub_group(shadow, NULL, parent_group_index, -1, conf);
- }
- }
-}
-
-static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
- int parent_group_index, int parent_ptr,
- const struct m_sub_options *subopts)
-{
- // Can't be used multiple times.
- for (int n = 0; n < shadow->num_groups; n++)
- assert(shadow->groups[n].group != subopts);
-
- if (!name_prefix)
- name_prefix = "";
- if (subopts->prefix && subopts->prefix[0]) {
- assert(!name_prefix[0]);
- name_prefix = subopts->prefix;
- }
-
- // You can only use UPDATE_ flags here.
- assert(!(subopts->change_flags & ~(unsigned)UPDATE_OPTS_MASK));
-
- assert(parent_group_index >= -1 && parent_group_index < shadow->num_groups);
-
- int group_index = shadow->num_groups++;
- MP_TARRAY_GROW(shadow, shadow->groups, group_index);
- shadow->groups[group_index] = (struct m_config_group){
- .group = subopts,
- .parent_group = parent_group_index,
- .parent_ptr = parent_ptr,
- .prefix = name_prefix,
- };
-
- for (int i = 0; subopts->opts && subopts->opts[i].name; i++) {
- const struct m_option *opt = &subopts->opts[i];
-
- if (opt->type == &m_option_type_subconfig) {
- const struct m_sub_options *new_subopts = opt->priv;
-
- // Providing default structs in-place is not allowed.
- if (opt->offset >= 0 && subopts->defaults) {
- void *ptr = (char *)subopts->defaults + opt->offset;
- assert(!substruct_read_ptr(ptr));
- }
-
- const char *prefix = concat_name(shadow, name_prefix, opt->name);
- add_sub_group(shadow, prefix, group_index, opt->offset, new_subopts);
-
- } else if (opt->type == &m_option_type_obj_settings_list) {
- const struct m_obj_list *objlist = opt->priv;
- init_obj_settings_list(shadow, group_index, objlist);
- }
- }
-
- if (subopts->get_sub_options) {
- for (int i = 0; ; i++) {
- const struct m_sub_options *sub = NULL;
- if (!subopts->get_sub_options(i, &sub))
- break;
- if (sub)
- add_sub_group(shadow, NULL, group_index, -1, sub);
- }
- }
-
- shadow->groups[group_index].group_count = shadow->num_groups - group_index;
-}
-
-static uint64_t get_option_change_mask(struct m_config_shadow *shadow,
- int group_index, int group_root,
- const struct m_option *opt)
-{
- uint64_t changed = opt->flags & UPDATE_OPTS_MASK;
- while (group_index != group_root) {
- struct m_config_group *g = &shadow->groups[group_index];
- changed |= g->group->change_flags;
- group_index = g->parent_group;
- }
- return changed;
-}
struct m_config_option *m_config_get_co_raw(const struct m_config *config,
struct bstr name)
@@ -819,16 +310,7 @@ struct m_config_option *m_config_get_co_index(struct m_config *config, int index
const void *m_config_get_co_default(const struct m_config *config,
struct m_config_option *co)
{
- if (co->opt->defval)
- return co->opt->defval;
-
- const struct m_sub_options *subopt =
- config->shadow->groups[co->group_index].group;
-
- if (co->opt->offset >= 0 && subopt->defaults)
- return (char *)subopt->defaults + co->opt->offset;
-
- return NULL;
+ return m_config_shadow_get_opt_default(config->shadow, co->opt_id);
}
const char *m_config_get_positional_option(const struct m_config *config, int p)
@@ -982,6 +464,51 @@ void m_config_set_update_dispatch_queue(struct m_config *config,
async_change_cb, config);
}
+static void config_destroy(void *p)
+{
+ struct m_config *config = p;
+ config->option_change_callback = NULL;
+ m_config_restore_backups(config);
+
+ talloc_free(config->cache);
+ talloc_free(config->shadow);
+}
+
+struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
+ const struct m_sub_options *root)
+{
+ struct m_config *config = talloc(talloc_ctx, struct m_config);
+ talloc_set_destructor(config, config_destroy);
+ *config = (struct m_config){.log = log,};
+
+ config->shadow = m_config_shadow_new(root);
+
+ if (root->size) {
+ config->cache = m_config_cache_from_shadow(config, config->shadow, root);
+ config->optstruct = config->cache->opts;
+ }
+
+ int32_t optid = -1;
+ while (m_config_shadow_get_next_opt(config->shadow, &optid)) {
+ char buf[M_CONFIG_MAX_OPT_NAME_LEN];
+ const char *opt_name =
+ m_config_shadow_get_opt_name(config->shadow, optid, buf, sizeof(buf));
+
+ struct m_config_option co = {
+ .name = talloc_strdup(config, opt_name),
+ .opt = m_config_shadow_get_opt(config->shadow, optid),
+ .opt_id = optid,
+ };
+
+ if (config->cache)
+ co.data = m_config_cache_get_opt_data(config->cache, optid);
+
+ MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
+ }
+
+ return config;
+}
+
// Normally m_config_cache will not send notifications when _we_ change our
// own stuff. For whatever funny reasons, we need that, though.
static void force_self_notify_change_opt(struct m_config *config,
@@ -989,7 +516,7 @@ static void force_self_notify_change_opt(struct m_config *config,
bool self_notification)
{
int changed =
- get_option_change_mask(config->shadow, co->group_index, 0, co->opt);
+ m_config_cache_get_option_change_mask(config->cache, co->opt_id);
if (config->option_change_callback) {
config->option_change_callback(config->option_change_callback_ctx, co,
@@ -1260,22 +787,23 @@ void m_config_print_option_list(const struct m_config *config, const char *name)
struct m_opt_choice_alternatives *alt = opt->priv;
for (int n = 0; alt[n].name; n++)
MP_INFO(config, " %s", alt[n].name);
- if (opt->flags & (M_OPT_MIN | M_OPT_MAX))
+ if (opt->min < opt->max)
MP_INFO(config, " (or an integer)");
} else {
MP_INFO(config, " %s", opt->type->name);
}
- if (opt->flags & (M_OPT_MIN | M_OPT_MAX)) {
+ if ((opt->type->flags & M_OPT_TYPE_USES_RANGE) && opt->min < opt->max) {
snprintf(min, sizeof(min), "any");
snprintf(max, sizeof(max), "any");
- if (opt->flags & M_OPT_MIN)
+ if (opt->min != DBL_MIN)
snprintf(min, sizeof(min), "%.14g", opt->min);
- if (opt->flags & M_OPT_MAX)
+ if (opt->max != DBL_MAX)
snprintf(max, sizeof(max), "%.14g", opt->max);
MP_INFO(config, " (%s to %s)", min, max);
}
char *def = NULL;
const void *defptr = m_config_get_co_default(config, co);
+ const union m_option_value default_value = {0};
if (!defptr)
defptr = &default_value;
if (defptr)
@@ -1429,345 +957,3 @@ struct mpv_node m_config_get_profiles(struct m_config *config)
return root;
}
-
-static void cache_destroy(void *p)
-{
- struct m_config_cache *cache = p;
-
- // (technically speaking, being able to call them both without anything
- // breaking is a feature provided by these functions)
- m_config_cache_set_wakeup_cb(cache, NULL, NULL);
- m_config_cache_set_dispatch_change_cb(cache, NULL, NULL, NULL);
-}
-
-static struct m_config_cache *m_config_cache_alloc_internal(void *ta_parent,
- struct m_config_shadow *shadow,
- const struct m_sub_options *group)
-{
- int group_index = -1;
-
- for (int n = 0; n < shadow->num_groups; n++) {
- if (shadow->groups[n].group == group) {
- group_index = n;
- break;
- }
- }
-
- assert(group_index >= 0); // invalid group (or not in option tree)
-
- struct cache_alloc {
- struct m_config_cache a;
- struct config_cache b;
- };
- struct cache_alloc *alloc = talloc_zero(ta_parent, struct cache_alloc);
- assert((void *)&alloc->a == (void *)alloc);
- struct m_config_cache *cache = &alloc->a;
- talloc_set_destructor(cache, cache_destroy);
- cache->internal = &alloc->b;
-
- struct config_cache *in = cache->internal;
- in->shadow = shadow;
- in->src = shadow->data;
-
- pthread_mutex_lock(&shadow->lock);
- in->data = allocate_option_data(cache, shadow, group_index, in->src);
- pthread_mutex_unlock(&shadow->lock);
-
- cache->opts = in->data->gdata[0].udata;
-
- in->upd_group = -1;
-
- return cache;
-}
-
-struct m_config_cache *m_config_cache_alloc(void *ta_parent,
- struct mpv_global *global,
- const struct m_sub_options *group)
-{
- return m_config_cache_alloc_internal(ta_parent, global->config, group);
-}
-
-static void update_next_option(struct m_config_cache *cache, void **p_opt)
-{
- struct config_cache *in = cache->internal;
- struct m_config_data *dst = in->data;
- struct m_config_data *src = in->src;
-
- assert(src->group_index == 0); // must be the option root currently
-
- *p_opt = NULL;
-
- while (in->upd_group < dst->group_index + dst->num_gdata) {
- struct m_group_data *gsrc = m_config_gdata(src, in->upd_group);
- struct m_group_data *gdst = m_config_gdata(dst, in->upd_group);
- assert(gsrc && gdst);
-
- if (gdst->ts < gsrc->ts) {
- struct m_config_group *g = &dst->shadow->groups[in->upd_group];
- const struct m_option *opts = g->group->opts;
-
- while (opts && opts[in->upd_opt].name) {
- const struct m_option *opt = &opts[in->upd_opt];
-
- if (opt->offset >= 0 && opt->type->size) {
- void *dsrc = gsrc->udata + opt->offset;
- void *ddst = gdst->udata + opt->offset;
-
- if (!m_option_equal(opt, ddst, dsrc)) {
- uint64_t ch = get_option_change_mask(dst->shadow,
- in->upd_group, dst->group_index, opt);
-
- if (cache->debug) {
- char *vdst = m_option_print(opt, ddst);
- char *vsrc = m_option_print(opt, dsrc);
- mp_warn(cache->debug, "Option '%s' changed from "
- "'%s' to' %s' (flags = 0x%"PRIx64")\n",
- opt->name, vdst, vsrc, ch);
- talloc_free(vdst);
- talloc_free(vsrc);
- }
-
- m_option_copy(opt, ddst, dsrc);
- cache->change_flags |= ch;
-
- in->upd_opt++; // skip this next time
- *p_opt = ddst;
- return;
- }
- }
-
- in->upd_opt++;
- }
-
- gdst->ts = gsrc->ts;
- }
-
- in->upd_group++;
- in->upd_opt = 0;
- }
-
- in->upd_group = -1;
-}
-
-static bool cache_check_update(struct m_config_cache *cache)
-{
- struct config_cache *in = cache->internal;
- struct m_config_shadow *shadow = in->shadow;
-
- // Using atomics and checking outside of the lock - it's unknown whether
- // this makes it faster or slower. Just cargo culting it.
- uint64_t new_ts = atomic_load(&shadow->ts);
- if (in->ts >= new_ts)
- return false;
-
- in->ts = new_ts;
- in->upd_group = in->data->group_index;
- in->upd_opt = 0;
- return true;
-}
-
-bool m_config_cache_update(struct m_config_cache *cache)
-{
- struct config_cache *in = cache->internal;
- struct m_config_shadow *shadow = in->shadow;
-
- if (!cache_check_update(cache))
- return false;
-
- pthread_mutex_lock(&shadow->lock);
- bool res = false;
- while (1) {
- void *p;
- update_next_option(cache, &p);
- if (!p)
- break;
- res = true;
- }
- pthread_mutex_unlock(&shadow->lock);
- return res;
-}
-
-bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **opt)
-{
- struct config_cache *in = cache->internal;
- struct m_config_shadow *shadow = in->shadow;
-
- *opt = NULL;
- if (!cache_check_update(cache) && in->upd_group < 0)
- return false;
-
- pthread_mutex_lock(&shadow->lock);
- update_next_option(cache, opt);
- pthread_mutex_unlock(&shadow->lock);
- return !!*opt;
-}
-
-static void find_opt(struct m_config_shadow *shadow, struct m_config_data *data,
- void *ptr, int *group_idx, int *opt_idx)
-{
- *group_idx = -1;
- *opt_idx = -1;
-
- for (int n = data->group_index; n < data->group_index + data->num_gdata; n++)
- {
- struct m_group_data *gd = m_config_gdata(data, n);
- struct m_config_group *g = &shadow->groups[n];
- const struct m_option *opts = g->group->opts;
-
- for (int i = 0; opts && opts[i].name; i++) {
- const struct m_option *opt = &opts[i];
-
- if (opt->offset >= 0 && opt->type->size &&
- ptr == gd->udata + opt->offset)
- {
- *group_idx = n;
- *opt_idx = i;
- return;
- }
- }
- }
-}
-
-bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr)
-{
- struct config_cache *in = cache->internal;
- struct m_config_shadow *shadow = in->shadow;
-
- int group_idx = -1;
- int opt_idx = -1;
- find_opt(shadow, in->data, ptr, &group_idx, &opt_idx);
-
- // ptr was not in cache->opts, or no option declaration matching it.
- assert(group_idx >= 0);
-
- struct m_config_group *g = &shadow->groups[group_idx];
- const struct m_option *opt = &g->group->opts[opt_idx];
-
- pthread_mutex_lock(&shadow->lock);
-
- struct m_group_data *gdst = m_config_gdata(in->data, group_idx);
- struct m_group_data *gsrc = m_config_gdata(in->src, group_idx);
- assert(gdst && gsrc);
-
- bool changed = !m_option_equal(opt, gsrc->udata + opt->offset, ptr);
- if (changed) {
- m_option_copy(opt, gsrc->udata + opt->offset, ptr);
-
- gsrc->ts = atomic_fetch_add(&shadow->ts, 1) + 1;
-
- for (int n = 0; n < shadow->num_listeners; n++) {
- struct config_cache *listener = shadow->listeners[n];
- if (listener->wakeup_cb && m_config_gdata(listener->data, group_idx))
- listener->wakeup_cb(listener->wakeup_cb_ctx);
- }
- }
-
- pthread_mutex_unlock(&shadow->lock);
-
- return changed;
-}
-
-void m_config_cache_set_wakeup_cb(struct m_config_cache *cache,
- void (*cb)(void *ctx), void *cb_ctx)
-{
- struct config_cache *in = cache->internal;
- struct m_config_shadow *shadow = in->shadow;
-
- pthread_mutex_lock(&shadow->lock);
- if (in->in_list) {
- for (int n = 0; n < shadow->num_listeners; n++) {
- if (shadow->listeners[n] == in) {
- MP_TARRAY_REMOVE_AT(shadow->listeners, shadow->num_listeners, n);
- break;
- }
- }
- for (int n = 0; n < shadow->num_listeners; n++)
- assert(shadow->listeners[n] != in); // only 1 wakeup_cb per cache
- // (The deinitialization path relies on this to free all memory.)
- if (!shadow->num_listeners) {
- talloc_free(shadow->listeners);
- shadow->listeners = NULL;
- }
- }
- if (cb) {
- MP_TARRAY_APPEND(NULL, shadow->listeners, shadow->num_listeners, in);
- in->in_list = true;
- in->wakeup_cb = cb;
- in->wakeup_cb_ctx = cb_ctx;
- }
- pthread_mutex_unlock(&shadow->lock);
-}
-
-static void dispatch_notify(void *p)
-{
- struct config_cache *in = p;
-
- assert(in->wakeup_dispatch_queue);
- mp_dispatch_enqueue_notify(in->wakeup_dispatch_queue,
- in->wakeup_dispatch_cb,
- in->wakeup_dispatch_cb_ctx);
-}
-
-void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
- struct mp_dispatch_queue *dispatch,
- void (*cb)(void *ctx), void *cb_ctx)
-{
- struct config_cache *in = cache->internal;
-
- // Removing the old one is tricky. First make sure no new notifications will
- // come.
- m_config_cache_set_wakeup_cb(cache, NULL, NULL);
- // Remove any pending notifications (assume we're on the same thread as
- // any potential mp_dispatch_queue_process() callers).
- if (in->wakeup_dispatch_queue) {
- mp_dispatch_cancel_fn(in->wakeup_dispatch_queue,
- in->wakeup_dispatch_cb,
- in->wakeup_dispatch_cb_ctx);
- }
-
- in->wakeup_dispatch_queue = NULL;
- in->wakeup_dispatch_cb = NULL;
- in->wakeup_dispatch_cb_ctx = NULL;
-
- if (cb) {
- in->wakeup_dispatch_queue = dispatch;
- in->wakeup_dispatch_cb = cb;
- in->wakeup_dispatch_cb_ctx = cb_ctx;
- m_config_cache_set_wakeup_cb(cache, dispatch_notify, in);
- }
-}
-
-void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
- const struct m_sub_options *group)
-{
- struct m_config_cache *cache = m_config_cache_alloc(NULL, global, group);
- // Make talloc_free(cache->opts) free the entire cache.
- ta_set_parent(cache->opts, ta_parent);
- ta_set_parent(cache, cache->opts);
- return cache->opts;
-}
-
-void mp_read_option_raw(struct mpv_global *global, const char *name,
- const struct m_option_type *type, void *dst)
-{
- struct m_config_shadow *shadow = global->config;
-
- struct opt_iterate_state it;
- opt_iterate_init(&it, shadow, 0);
- while (opt_iterate_next(&it)) {
- if (strcmp(name, it.full_name) == 0) {
- struct m_group_data *gdata =
- m_config_gdata(shadow->data, it.group_index);
- assert(gdata);
-
- assert(it.opt->offset >= 0);
- assert(it.opt->type == type);
-
- memset(dst, 0, it.opt->type->size);
- m_option_copy(it.opt, dst, gdata->udata + it.opt->offset);
- return;
- }
- }
-
- assert(0); // not found
-}
diff --git a/options/m_config_frontend.h b/options/m_config_frontend.h
new file mode 100644
index 0000000000..19fbcadf25
--- /dev/null
+++ b/options/m_config_frontend.h
@@ -0,0 +1,260 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "common/common.h"
+#include "common/global.h"
+#include "common/msg.h"
+#include "common/msg_control.h"
+#include "m_config_core.h"
+#include "misc/bstr.h"
+#include "misc/dispatch.h"
+#include "options/m_option.h"
+#include "osdep/atomic.h"
+
+// m_config provides an API to manipulate the config variables in MPlayer.
+// It makes use of the Options API to provide a context stack that
+// allows saving and later restoring the state of all variables.
+
+typedef struct m_profile m_profile_t;
+struct m_option;
+struct m_option_type;
+struct m_sub_options;
+struct m_obj_desc;
+struct m_obj_settings;
+struct mp_log;
+struct mp_dispatch_queue;
+
+// Config option
+struct m_config_option {
+ bool is_set_from_cmdline : 1; // Set by user from command line
+ bool is_set_from_config : 1; // Set by a config file
+ bool is_set_locally : 1; // Has a backup entry
+ bool warning_was_printed : 1;
+ int32_t opt_id; // For some m_config APIs
+ const char *name; // Full name (ie option-subopt)
+ const struct m_option *opt; // Option description
+ void *data; // Raw value of the option
+};
+
+// Config object
+/** \ingroup Config */
+typedef struct m_config {
+ struct mp_log *log;
+ struct mpv_global *global; // can be NULL
+
+ // Registered options.
+ struct m_config_option *opts; // all options, even suboptions
+ int num_opts;
+
+ // List of defined profiles.
+ struct m_profile *profiles;
+ // Depth when recursively including profiles.
+ int profile_depth;
+
+ struct m_opt_backup *backup_opts;
+
+ bool use_profiles;
+ bool is_toplevel;
+ int (*includefunc)(void *ctx, char *filename, int flags);
+ void *includefunc_ctx;
+
+ // Notification after an option was successfully written to.
+ // Uses flags as set in UPDATE_OPTS_MASK.
+ // self_update==true means the update was caused by a call to
+ // m_config_notify_change_opt_ptr(). If false, it's caused either by
+ // m_config_set_option_*() (and similar) calls or external updates.
+ void (*option_change_callback)(void *ctx, struct m_config_option *co,
+ int flags, bool self_update);
+ void *option_change_callback_ctx;
+
+ // For the command line parser
+ int recursion_depth;
+
+ void *optstruct; // struct mpopts or other
+
+ // Private. Non-NULL if data was allocated. m_config_option.data uses it.
+ // API users call m_config_set_update_dispatch_queue() to get async updates.
+ struct m_config_cache *cache;
+
+ // Private. Thread-safe shadow memory; only set for the main m_config.
+ struct m_config_shadow *shadow;
+} m_config_t;
+
+// Create a new config object.
+// talloc_ctx: talloc parent context for the m_config allocation
+// root: description of all options
+// Note that the m_config object will keep pointers to root and log.
+struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
+ const struct m_sub_options *root);
+
+// Create a m_config for the given desc. This is for --af/--vf, which have
+// different sub-options for every filter (represented by separate desc
+// structs).
+// args is an array of key/value pairs (args=[k0, v0, k1, v1, ..., NULL]).
+// name/defaults is only needed for the legacy af-defaults/vf-defaults options.
+struct m_config *m_config_from_obj_desc_and_args(void *ta_parent,
+ struct mp_log *log, struct mpv_global *global, struct m_obj_desc *desc,
+ const char *name, struct m_obj_settings *defaults, char **args);
+
+// Like m_config_from_obj_desc_and_args(), but don't allocate option the
+// struct, i.e. m_config.optstruct==NULL. This is used by the sub-option
+// parser (--af/--vf, to a lesser degree --ao/--vo) to check sub-option names
+// and types.
+struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
+ struct mp_log *log,
+ struct m_obj_desc *desc);
+
+// Make sure the option is backed up. If it's already backed up, do nothing.
+// All backed up options can be restored with m_config_restore_backups().
+void m_config_backup_opt(struct m_config *config, const char *opt);
+
+// Call m_config_backup_opt() on all options.
+void m_config_backup_all_opts(struct m_config *config);
+
+// Restore all options backed up with m_config_backup_opt(), and delete the
+// backups afterwards.
+void m_config_restore_backups(struct m_config *config);
+
+enum {
+ M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt.
+ M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value
+ M_SETOPT_FROM_CONFIG_FILE = 4, // Reject M_OPT_NOCFG opt. (print error)
+ M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line
+ M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before
+ M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE
+ M_SETOPT_NO_PRE_PARSE = 128, // Reject M_OPT_PREPARSE options
+ M_SETOPT_NO_OVERWRITE = 256, // Skip options marked with FROM_*
+};
+
+// Set the named option to the given string. This is for command line and config
+// file use only.
+// flags: combination of M_SETOPT_* flags (0 for normal operation)
+// Returns >= 0 on success, otherwise see OptionParserReturn.
+int m_config_set_option_cli(struct m_config *config, struct bstr name,
+ struct bstr param, int flags);
+
+// Similar to m_config_set_option_cli(), but set as data in its native format.
+// This takes care of some details like sending change notifications.
+// The type data points to is as in: co->opt
+int m_config_set_option_raw(struct m_config *config, struct m_config_option *co,
+ void *data, int flags);
+
+void m_config_mark_co_flags(struct m_config_option *co, int flags);
+
+// Convert the mpv_node to raw option data, then call m_config_set_option_raw().
+struct mpv_node;
+int m_config_set_option_node(struct m_config *config, bstr name,
+ struct mpv_node *data, int flags);
+
+// Return option descriptor. You shouldn't use this.
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name);
+// Same as above, but does not resolve aliases or trigger warning messages.
+struct m_config_option *m_config_get_co_raw(const struct m_config *config,
+ struct bstr name);
+
+// Special uses only. Look away.
+int m_config_get_co_count(struct m_config *config);
+struct m_config_option *m_config_get_co_index(struct m_config *config, int index);
+const void *m_config_get_co_default(const struct m_config *config,
+ struct m_config_option *co);
+
+// Return the n-th option by position. n==0 is the first option. If there are
+// less than (n + 1) options, return NULL.
+const char *m_config_get_positional_option(const struct m_config *config, int n);
+
+// Return a hint to the option parser whether a parameter is/may be required.
+// The option may still accept empty/non-empty parameters independent from
+// this, and this function is useful only for handling ambiguous options like
+// flags (e.g. "--a" is ok, "--a=yes" is also ok).
+// Returns: error code (<0), or number of expected params (0, 1)
+int m_config_option_requires_param(struct m_config *config, bstr name);
+
+// Notify m_config_cache users that the option has (probably) changed its value.
+// This will force a self-notification back to config->option_change_callback.
+void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr);
+
+// Exactly like m_config_notify_change_opt_ptr(), but the option change callback
+// (config->option_change_callback()) is invoked with self_update=false, if at all.
+void m_config_notify_change_opt_ptr_notify(struct m_config *config, void *ptr);
+
+// Return all (visible) option names as NULL terminated string list.
+char **m_config_list_options(void *ta_parent, const struct m_config *config);
+
+void m_config_print_option_list(const struct m_config *config, const char *name);
+
+
+/* Find the profile with the given name.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object or NULL.
+ */
+struct m_profile *m_config_get_profile0(const struct m_config *config,
+ char *name);
+struct m_profile *m_config_get_profile(const struct m_config *config, bstr name);
+
+// Apply and clear the default profile - it's the only profile that new config
+// files do not simply append to (for configfile parser).
+void m_config_finish_default_profile(struct m_config *config, int flags);
+
+/* Get the profile with the given name, creating it if necessary.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object.
+ */
+struct m_profile *m_config_add_profile(struct m_config *config, char *name);
+
+/* Set the description of a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param p The profile object.
+ * \param arg The profile's name.
+ */
+void m_profile_set_desc(struct m_profile *p, bstr desc);
+
+/* Add an option to a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param name The option's name.
+ * \param val The option's value.
+ */
+int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
+ bstr name, bstr val);
+
+/* Enables profile usage
+ * Used by the config file parser when loading a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param flags M_SETOPT_* bits
+ * Returns error code (<0) or 0 on success
+ */
+int m_config_set_profile(struct m_config *config, char *name, int flags);
+
+struct mpv_node m_config_get_profiles(struct m_config *config);
+
+// Run async option updates here. This will call option_change_callback() on it.
+void m_config_set_update_dispatch_queue(struct m_config *config,
+ struct mp_dispatch_queue *dispatch);
diff --git a/options/m_option.c b/options/m_option.c
index c55ca06bc8..a7b02b8819 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
+#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
@@ -42,7 +43,7 @@
#include "misc/json.h"
#include "misc/node.h"
#include "m_option.h"
-#include "m_config.h"
+#include "m_config_frontend.h"
#if HAVE_DOS_PATHS
#define OPTION_PATH_SEPARATOR ';'
@@ -52,6 +53,14 @@
const char m_option_path_separator = OPTION_PATH_SEPARATOR;
+// For integer types: since min/max are floats and may not be able to represent
+// the real min/max, and since opt.min/.max may use +/-INFINITY, some care has
+// to be taken. (Also tricky rounding.)
+#define OPT_INT_MIN(opt, T, Tm) ((opt)->min < (opt)->max \
+ ? ((opt)->min <= (double)(Tm) ? (Tm) : (T)((opt)->min)) : (Tm))
+#define OPT_INT_MAX(opt, T, Tm) ((opt)->min < (opt)->max \
+ ? ((opt)->max >= (double)(Tm) ? (Tm) : (T)((opt)->max)) : (Tm))
+
char *m_option_strerror(int code)
{
switch (code) {
@@ -109,11 +118,11 @@ static void copy_opt(const m_option_t *opt, void *dst, const void *src)
memcpy(dst, src, opt->type->size);
}
-// Flag
+// Bool
-#define VAL(x) (*(int *)(x))
+#define VAL(x) (*(bool *)(x))
-static int parse_flag(struct mp_log *log, const m_option_t *opt,
+static int parse_bool(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
if (bstr_equals0(param, "yes") || !param.len) {
@@ -140,12 +149,12 @@ static int parse_flag(struct mp_log *log, const m_option_t *opt,
return is_help ? M_OPT_EXIT : M_OPT_INVALID;
}
-static char *print_flag(const m_option_t *opt, const void *val)
+static char *print_bool(const m_option_t *opt, const void *val)
{
return talloc_strdup(NULL, VAL(val) ? "yes" : "no");
}
-static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
+static void add_bool(const m_option_t *opt, void *val, double add, bool wrap)
{
if (fabs(add) < 0.5)
return;
@@ -154,7 +163,7 @@ static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
VAL(val) = state ? 1 : 0;
}
-static int flag_set(const m_option_t *opt, void *dst, struct mpv_node *src)
+static int bool_set(const m_option_t *opt, void *dst, struct mpv_node *src)
{
if (src->format != MPV_FORMAT_FLAG)
return M_OPT_UNKNOWN;
@@ -162,7 +171,7 @@ static int flag_set(const m_option_t *opt, void *dst, struct mpv_node *src)
return 1;
}
-static int flag_get(const m_option_t *opt, void *ta_parent,
+static int bool_get(const m_option_t *opt, void *ta_parent,
struct mpv_node *dst, void *src)
{
dst->format = MPV_FORMAT_FLAG;
@@ -170,6 +179,67 @@ static int flag_get(const m_option_t *opt, void *ta_parent,
return 1;
}
+static bool bool_equal(const m_option_t *opt, void *a, void *b)
+{
+ return VAL(a) == VAL(b);
+}
+
+const m_option_type_t m_option_type_bool = {
+ .name = "Flag", // same as m_option_type_flag; transparent to user
+ .size = sizeof(bool),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM | M_OPT_TYPE_CHOICE,
+ .parse = parse_bool,
+ .print = print_bool,
+ .copy = copy_opt,
+ .add = add_bool,
+ .set = bool_set,
+ .get = bool_get,
+ .equal = bool_equal,
+};
+
+#undef VAL
+
+// Flag
+
+#define VAL(x) (*(int *)(x))
+
+static int parse_flag(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param, void *dst)
+{
+ bool bdst = false;
+ int r = parse_bool(log, opt, name, param, &bdst);
+ if (dst)
+ VAL(dst) = bdst;
+ return r;
+}
+
+static char *print_flag(const m_option_t *opt, const void *val)
+{
+ return print_bool(opt, &(bool){VAL(val)});
+}
+
+static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ bool bval = VAL(val);
+ add_bool(opt, &bval, add, wrap);
+ VAL(val) = bval;
+}
+
+static int flag_set(const m_option_t *opt, void *dst, struct mpv_node *src)
+{
+ bool bdst = false;
+ int r = bool_set(opt, &bdst, src);
+ if (r >= 0)
+ VAL(dst) = bdst;
+ return r;
+}
+
+static int flag_get(const m_option_t *opt, void *ta_parent,
+ struct mpv_node *dst, void *src)
+{
+ return bool_get(opt, ta_parent, dst, &(bool){VAL(src)});
+}
+
static bool flag_equal(const m_option_t *opt, void *a, void *b)
{
return VAL(a) == VAL(b);
@@ -193,16 +263,19 @@ const m_option_type_t m_option_type_flag = {
#undef VAL
-static int clamp_longlong(const m_option_t *opt, void *val)
+static int clamp_longlong(const m_option_t *opt, long long i_min, long long i_max,
+ void *val)
{
long long v = *(long long *)val;
int r = 0;
- if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
- v = opt->max;
+ long long min = OPT_INT_MIN(opt, long long, i_min);
+ long long max = OPT_INT_MAX(opt, long long, i_max);
+ if (v > max) {
+ v = max;
r = M_OPT_OUT_OF_RANGE;
}
- if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
- v = opt->min;
+ if (v < min) {
+ v = min;
r = M_OPT_OUT_OF_RANGE;
}
*(long long *)val = v;
@@ -210,6 +283,7 @@ static int clamp_longlong(const m_option_t *opt, void *val)
}
static int parse_longlong(struct mp_log *log, const m_option_t *opt,
+ long long i_min, long long i_max,
struct bstr name, struct bstr param, void *dst)
{
if (param.len == 0)
@@ -225,15 +299,17 @@ static int parse_longlong(struct mp_log *log, const m_option_t *opt,
return M_OPT_INVALID;
}
- if ((opt->flags & M_OPT_MIN) && (tmp_int < opt->min)) {
- mp_err(log, "The %.*s option must be >= %d: %.*s\n",
- BSTR_P(name), (int) opt->min, BSTR_P(param));
+ long long min = OPT_INT_MIN(opt, long long, i_min);
+ if (tmp_int < min) {
+ mp_err(log, "The %.*s option must be >= %lld: %.*s\n",
+ BSTR_P(name), min, BSTR_P(param));
return M_OPT_OUT_OF_RANGE;
}
- if ((opt->flags & M_OPT_MAX) && (tmp_int > opt->max)) {
- mp_err(log, "The %.*s option must be <= %d: %.*s\n",
- BSTR_P(name), (int) opt->max, BSTR_P(param));
+ long long max = OPT_INT_MAX(opt, long long, i_max);
+ if (tmp_int > max) {
+ mp_err(log, "The %.*s option must be <= %lld: %.*s\n",
+ BSTR_P(name), max, BSTR_P(param));
return M_OPT_OUT_OF_RANGE;
}
@@ -246,7 +322,7 @@ static int parse_longlong(struct mp_log *log, const m_option_t *opt,
static int clamp_int64(const m_option_t *opt, void *val)
{
long long tmp = *(int64_t *)val;
- int r = clamp_longlong(opt, &tmp);
+ int r = clamp_longlong(opt, INT64_MIN, INT64_MAX, &tmp);
*(int64_t *)val = tmp;
return r;
}
@@ -255,7 +331,7 @@ static int parse_int(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
long long tmp;
- int r = parse_longlong(log, opt, name, param, &tmp);
+ int r = parse_longlong(log, opt, INT_MIN, INT_MAX, name, param, &tmp);
if (r >= 0 && dst)
*(int *)dst = tmp;
return r;
@@ -265,7 +341,7 @@ static int parse_int64(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
long long tmp;
- int r = parse_longlong(log, opt, name, param, &tmp);
+ int r = parse_longlong(log, opt, INT64_MIN, INT64_MAX, name, param, &tmp);
if (r >= 0 && dst)
*(int64_t *)dst = tmp;
return r;
@@ -290,8 +366,8 @@ static void add_int64(const m_option_t *opt, void *val, double add, bool wrap)
int64_t nmin = is64 ? INT64_MIN : INT_MIN;
int64_t nmax = is64 ? INT64_MAX : INT_MAX;
- int64_t min = (opt->flags & M_OPT_MIN) ? opt->min : nmin;
- int64_t max = (opt->flags & M_OPT_MAX) ? opt->max : nmax;
+ int64_t min = OPT_INT_MIN(opt, int64_t, nmin);
+ int64_t max = OPT_INT_MAX(opt, int64_t, nmax);
if (v < min)
v = wrap ? max : min;
@@ -314,7 +390,7 @@ static void multiply_int64(const m_option_t *opt, void *val, double f)
int64_t iv = v;
if (v < INT64_MIN)
iv = INT64_MIN;
- if (v > INT64_MAX)
+ if (v >= (double)INT64_MAX)
iv = INT64_MAX;
*(int64_t *)val = iv;
clamp_int64(opt, val);
@@ -332,9 +408,9 @@ static int int64_set(const m_option_t *opt, void *dst, struct mpv_node *src)
if (src->format != MPV_FORMAT_INT64)
return M_OPT_UNKNOWN;
int64_t val = src->u.int64;
- if ((opt->flags & M_OPT_MIN) && val < opt->min)
+ if (val < OPT_INT_MIN(opt, int64_t, INT64_MIN))
return M_OPT_OUT_OF_RANGE;
- if ((opt->flags & M_OPT_MAX) && val > opt->max)
+ if (val > OPT_INT_MAX(opt, int64_t, INT64_MAX))
return M_OPT_OUT_OF_RANGE;
*(int64_t *)dst = val;
return 1;
@@ -380,6 +456,7 @@ static bool int64_equal(const m_option_t *opt, void *a, void *b)
const m_option_type_t m_option_type_int = {
.name = "Integer",
+ .flags = M_OPT_TYPE_USES_RANGE,
.size = sizeof(int),
.parse = parse_int,
.print = print_int,
@@ -393,6 +470,7 @@ const m_option_type_t m_option_type_int = {
const m_option_type_t m_option_type_int64 = {
.name = "Integer64",
+ .flags = M_OPT_TYPE_USES_RANGE,
.size = sizeof(int64_t),
.parse = parse_int64,
.print = print_int,
@@ -447,15 +525,17 @@ static int parse_byte_size(struct mp_log *log, const m_option_t *opt,
tmp_int *= unit;
- if ((opt->flags & M_OPT_MIN) && (tmp_int < opt->min)) {
- mp_err(log, "The %.*s option must be >= %d: %.*s\n",
- BSTR_P(name), (int) opt->min, BSTR_P(param));
+ int64_t min = OPT_INT_MIN(opt, int64_t, INT64_MIN);
+ if (tmp_int < min) {
+ mp_err(log, "The %.*s option must be >= %"PRId64": %.*s\n",
+ BSTR_P(name), min, BSTR_P(param));
return M_OPT_OUT_OF_RANGE;
}
- if ((opt->flags & M_OPT_MAX) && (tmp_int > opt->max)) {
- mp_err(log, "The %.*s option must be <= %d: %.*s\n",
- BSTR_P(name), (int) opt->max, BSTR_P(param));
+ int64_t max = OPT_INT_MAX(opt, int64_t, INT64_MAX);
+ if (tmp_int > max) {
+ mp_err(log, "The %.*s option must be <= %"PRId64": %.*s\n",
+ BSTR_P(name), max, BSTR_P(param));
return M_OPT_OUT_OF_RANGE;
}
@@ -490,6 +570,7 @@ static char *pretty_print_byte_size(const m_option_t *opt, const void *val)
const m_option_type_t m_option_type_byte_size = {
.name = "ByteSize",
+ .flags = M_OPT_TYPE_USES_RANGE,
.size = sizeof(int64_t),
.parse = parse_byte_size,
.print = print_int,
@@ -502,59 +583,6 @@ const m_option_type_t m_option_type_byte_size = {
.equal = int64_equal,
};
-static int parse_intpair(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param, void *dst)
-{
- if (param.len == 0)
- return M_OPT_MISSING_PARAM;
-
- struct bstr s = param;
- int end = -1;
- int start = bstrtoll(s, &s, 10);
- if (s.len == param.len)
- goto bad;
- if (s.len > 0) {
- if (!bstr_startswith0(s, "-"))
- goto bad;
- s = bstr_cut(s, 1);
- }
- if (s.len > 0)
- end = bstrtoll(s, &s, 10);
- if (s.len > 0)
- goto bad;
-
- if (dst) {
- int *p = dst;
- p[0] = start;
- p[1] = end;
- }
-
- return 1;
-
-bad:
- mp_err(log, "Invalid integer range "
- "specification for option %.*s: %.*s\n",
- BSTR_P(name), BSTR_P(param));
- return M_OPT_INVALID;
-}
-
-static char *print_intpair(const m_option_t *opt, const void *val)
-{
- const int *p = val;
- char *res = talloc_asprintf(NULL, "%d", p[0]);
- if (p[1] != -1)
- res = talloc_asprintf_append(res, "-%d", p[1]);
- return res;
-}
-
-const struct m_option_type m_option_type_intpair = {
- .name = "Int[-Int]",
- .size = sizeof(int[2]),
- .parse = parse_intpair,
- .print = print_intpair,
- .copy = copy_opt,
-};
-
const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices,
int value)
{
@@ -570,7 +598,7 @@ static void print_choice_values(struct mp_log *log, const struct m_option *opt)
struct m_opt_choice_alternatives *alt = opt->priv;
for ( ; alt->name; alt++)
mp_info(log, " %s\n", alt->name[0] ? alt->name : "(passing nothing)");
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX))
+ if (opt->min < opt->max)
mp_info(log, " %g-%g (integer range)\n", opt->min, opt->max);
}
@@ -597,9 +625,11 @@ static int parse_choice(struct mp_log *log, const struct m_option *opt,
}
if (param.len == 0)
return M_OPT_MISSING_PARAM;
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (opt->min < opt->max) {
long long val;
- if (parse_longlong(mp_null_log, opt, name, param, &val) == 1) {
+ if (parse_longlong(mp_null_log, opt, INT_MIN, INT_MAX, name, param,
+ &val) == 1)
+ {
if (dst)
*(int *)dst = val;
return 1;
@@ -626,7 +656,7 @@ static void choice_get_min_max(const struct m_option *opt, int *min, int *max)
*min = MPMIN(*min, alt->value);
*max = MPMAX(*max, alt->value);
}
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (opt->min < opt->max) {
*min = MPMIN(*min, opt->min);
*max = MPMAX(*max, opt->max);
}
@@ -653,7 +683,7 @@ static void add_choice(const m_option_t *opt, void *val, double add, bool wrap)
if (fabs(add) < 0.5)
return;
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (opt->min < opt->max) {
int newval = ival + add;
if (ival >= opt->min && ival <= opt->max &&
newval >= opt->min && newval <= opt->max)
@@ -708,7 +738,7 @@ static struct m_opt_choice_alternatives *get_choice(const m_option_t *opt,
if (alt->value == v)
return alt;
}
- if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (opt->min < opt->max) {
if (v >= opt->min && v <= opt->max) {
*out_val = v;
return NULL;
@@ -761,7 +791,7 @@ static char *print_choice(const m_option_t *opt, const void *val)
const struct m_option_type m_option_type_choice = {
.name = "Choice",
.size = sizeof(int),
- .flags = M_OPT_TYPE_CHOICE,
+ .flags = M_OPT_TYPE_CHOICE | M_OPT_TYPE_USES_RANGE,
.parse = parse_choice,
.print = print_choice,
.copy = copy_opt,
@@ -903,13 +933,15 @@ static int clamp_double(const m_option_t *opt, void *val)
{
double v = VAL(val);
int r = 0;
- if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
- v = opt->max;
- r = M_OPT_OUT_OF_RANGE;
- }
- if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
- v = opt->min;
- r = M_OPT_OUT_OF_RANGE;
+ if (opt->min < opt->max) {
+ if (v > opt->max) {
+ v = opt->max;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ if (v < opt->min) {
+ v = opt->min;
+ r = M_OPT_OUT_OF_RANGE;
+ }
}
// (setting max/min to INFINITY/-INFINITY is allowed)
if (!isfinite(v) && v != opt->max && v != opt->min) {
@@ -978,8 +1010,8 @@ static void add_double(const m_option_t *opt, void *val, double add, bool wrap)
v = v + add;
- double min = (opt->flags & M_OPT_MIN) ? opt->min : -INFINITY;
- double max = (opt->flags & M_OPT_MAX) ? opt->max : +INFINITY;
+ double min = opt->min < opt->max ? opt->min : -INFINITY;
+ double max = opt->min < opt->max ? opt->max : +INFINITY;
if (v < min)
v = wrap ? max : min;
@@ -1037,6 +1069,7 @@ static bool double_equal(const m_option_t *opt, void *a, void *b)
const m_option_type_t m_option_type_double = {
// double precision float or ratio (numerator[:/]denominator)
.name = "Double",
+ .flags = M_OPT_TYPE_USES_RANGE,
.size = sizeof(double),
.parse = parse_double,
.print = print_double,
@@ -1112,6 +1145,7 @@ static bool float_equal(const m_option_t *opt, void *a, void *b)
const m_option_type_t m_option_type_float = {
// floating point number or ratio (numerator[:/]denominator)
.name = "Float",
+ .flags = M_OPT_TYPE_USES_RANGE,
.size = sizeof(float),
.parse = parse_float,
.print = print_float,
@@ -1138,7 +1172,7 @@ static int parse_float_aspect(struct mp_log *log, const m_option_t *opt,
const m_option_type_t m_option_type_aspect = {
.name = "Aspect",
.size = sizeof(float),
- .flags = M_OPT_TYPE_CHOICE,
+ .flags = M_OPT_TYPE_CHOICE | M_OPT_TYPE_USES_RANGE,
.parse = parse_float_aspect,
.print = print_float,
.pretty_print = print_float_f3,
@@ -1155,17 +1189,6 @@ const m_option_type_t m_option_type_aspect = {
#undef VAL
#define VAL(x) (*(char **)(x))
-static int clamp_str(const m_option_t *opt, void *val)
-{
- char *v = VAL(val);
- int len = v ? strlen(v) : 0;
- if ((opt->flags & M_OPT_MIN) && (len < opt->min))
- return M_OPT_OUT_OF_RANGE;
- if ((opt->flags & M_OPT_MAX) && (len > opt->max))
- return M_OPT_OUT_OF_RANGE;
- return 0;
-}
-
static int parse_str(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
@@ -1176,18 +1199,6 @@ static int parse_str(struct mp_log *log, const m_option_t *opt,
return r;
}
- if ((opt->flags & M_OPT_MIN) && (param.len < opt->min)) {
- mp_err(log, "Parameter must be >= %d chars: %.*s\n",
- (int) opt->min, BSTR_P(param));
- return M_OPT_OUT_OF_RANGE;
- }
-
- if ((opt->flags & M_OPT_MAX) && (param.len > opt->max)) {
- mp_err(log, "Parameter must be <= %d chars: %.*s\n",
- (int) opt->max, BSTR_P(param));
- return M_OPT_OUT_OF_RANGE;
- }
-
if (dst) {
talloc_free(VAL(dst));
VAL(dst) = bstrdup0(NULL, param);
@@ -1214,7 +1225,7 @@ static int str_set(const m_option_t *opt, void *dst, struct mpv_node *src)
if (src->format != MPV_FORMAT_STRING)
return M_OPT_UNKNOWN;
char *s = src->u.string;
- int r = s ? clamp_str(opt, &s) : M_OPT_INVALID;
+ int r = s ? 0 : M_OPT_INVALID;
if (r >= 0)
copy_str(opt, dst, &s);
return r;
@@ -1458,9 +1469,6 @@ static int parse_str_list_impl(struct mp_log *log, const m_option_t *opt,
}
if (n == 0 && op != OP_NONE)
return M_OPT_INVALID;
- if (((opt->flags & M_OPT_MIN) && (n < opt->min)) ||
- ((opt->flags & M_OPT_MAX) && (n > opt->max)))
- return M_OPT_OUT_OF_RANGE;
if (!dst)
return 1;
diff --git a/options/m_option.h b/options/m_option.h
index 9eb994a5cf..84336ae3ce 100644
--- a/options/m_option.h
+++ b/options/m_option.h
@@ -18,6 +18,7 @@
#ifndef MPLAYER_M_OPTION_H
#define MPLAYER_M_OPTION_H
+#include <float.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
@@ -37,12 +38,12 @@ struct mpv_global;
///////////////////////////// Options types declarations ////////////////////
// Simple types
+extern const m_option_type_t m_option_type_bool;
extern const m_option_type_t m_option_type_flag;
extern const m_option_type_t m_option_type_dummy_flag;
extern const m_option_type_t m_option_type_int;
extern const m_option_type_t m_option_type_int64;
extern const m_option_type_t m_option_type_byte_size;
-extern const m_option_type_t m_option_type_intpair;
extern const m_option_type_t m_option_type_float;
extern const m_option_type_t m_option_type_double;
extern const m_option_type_t m_option_type_string;
@@ -220,17 +221,16 @@ struct m_sub_options {
#define CONF_TYPE_OBJ_SETTINGS_LIST (&m_option_type_obj_settings_list)
#define CONF_TYPE_TIME (&m_option_type_time)
#define CONF_TYPE_CHOICE (&m_option_type_choice)
-#define CONF_TYPE_INT_PAIR (&m_option_type_intpair)
#define CONF_TYPE_NODE (&m_option_type_node)
// Possible option values. Code is allowed to access option data without going
// through this union. It serves for self-documentation and to get minimal
// size/alignment requirements for option values in general.
union m_option_value {
+ bool bool_;
int flag; // not the C type "bool"!
int int_;
int64_t int64;
- int intpair[2];
float float_;
double double_;
char *string;
@@ -354,6 +354,7 @@ struct m_option_type {
// Option description
struct m_option {
// Option name.
+ // Option declarations can use this as positional field.
const char *name;
// Option type.
@@ -364,13 +365,15 @@ struct m_option {
int offset;
- // \brief Mostly useful for numeric types, the \ref M_OPT_MIN flags must
- // also be set.
- double min;
-
- // \brief Mostly useful for numeric types, the \ref M_OPT_MAX flags must
- // also be set.
- double max;
+ // Most numeric types restrict the range to [min, max] if min<max (this
+ // implies that if min/max are not set, the full range is used). In all
+ // cases, the actual range is clamped to the type's native range.
+ // Float types use [DBL_MIN, DBL_MAX], though by setting min or max to
+ // -/+INFINITY, the range can be extended to INFINITY. (This part is buggy
+ // for "float".)
+ // Preferably use M_RANGE() to set these fields.
+ // Some types will abuse the min or max field for unrelated things.
+ double min, max;
// Type dependent data (for all kinds of extended settings).
void *priv;
@@ -385,15 +388,6 @@ struct m_option {
char *format_file_size(int64_t size);
-// The option has a minimum set in \ref m_option::min.
-#define M_OPT_MIN (1 << 0)
-
-// The option has a maximum set in \ref m_option::max.
-#define M_OPT_MAX (1 << 1)
-
-// The option has a minimum and maximum in m_option::min and m_option::max.
-#define M_OPT_RANGE (M_OPT_MIN | M_OPT_MAX)
-
// The option is forbidden in config files.
#define M_OPT_NOCFG (1 << 2)
@@ -439,9 +433,6 @@ char *format_file_size(int64_t size);
#define M_OPT_OPTIONAL_PARAM (1 << 30)
// These are kept for compatibility with older code.
-#define CONF_MIN M_OPT_MIN
-#define CONF_MAX M_OPT_MAX
-#define CONF_RANGE M_OPT_RANGE
#define CONF_NOCFG M_OPT_NOCFG
#define CONF_PRE_PARSE M_OPT_PRE_PARSE
@@ -458,6 +449,9 @@ char *format_file_size(int64_t size);
// might be allowed too). E.g. m_option_type_choice and m_option_type_flag.
#define M_OPT_TYPE_CHOICE (1 << 1)
+// When m_option.min/max are set, they denote a value range.
+#define M_OPT_TYPE_USES_RANGE (1 << 2)
+
///////////////////////////// Parser flags /////////////////////////////////
// OptionParserReturn
@@ -576,6 +570,9 @@ extern const char m_option_path_separator;
(offsetof(type, member) + (0 && MP_EXPECT_TYPE(expected_member_type*, \
&((type*)0)->member)))
+#define OPT_TYPED_FIELD(type_, c_type, field) \
+ .type = &type_, \
+ .offset = MP_CHECKED_OFFSETOF(OPT_BASE_STRUCT, field, c_type)
#define OPTION_LIST_SEPARATOR ','
@@ -585,183 +582,150 @@ extern const char m_option_path_separator;
#define OPTDEF_FLOAT(f) .defval = (void *)&(const float){f}
#define OPTDEF_DOUBLE(d) .defval = (void *)&(const double){d}
-#define OPT_GENERAL(ctype, optname, varname, flagv, ...) \
- {.name = optname, .flags = flagv, \
- .offset = MP_CHECKED_OFFSETOF(OPT_BASE_STRUCT, varname, ctype), \
- __VA_ARGS__}
+#define M_RANGE(a, b) .min = (a), .max = (b)
-#define OPT_GENERAL_NOTYPE(optname, varname, flagv, ...) \
- {.name = optname, .flags = flagv, \
- .offset = offsetof(OPT_BASE_STRUCT, varname), \
- __VA_ARGS__}
+#define OPT_BOOL(field) \
+ OPT_TYPED_FIELD(m_option_type_bool, bool, field)
-#define OPT_HELPER_REMOVEPAREN(...) __VA_ARGS__
+#define OPT_FLAG(field) \
+ OPT_TYPED_FIELD(m_option_type_flag, int, field)
-/* The OPT_SOMETHING->OPT_SOMETHING_ kind of redirection exists to
- * make the code fully standard-conforming: the C standard requires that
- * __VA_ARGS__ has at least one argument (though GCC for example would accept
- * 0). Thus the first OPT_SOMETHING is a wrapper which just adds one
- * argument to ensure __VA_ARGS__ is not empty when calling the next macro.
- */
+#define OPT_INT(field) \
+ OPT_TYPED_FIELD(m_option_type_int, int, field)
-#define OPT_FLAG(...) \
- OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_flag)
+#define OPT_INT64(field) \
+ OPT_TYPED_FIELD(m_option_type_int64, int64_t, field)
-#define OPT_STRINGLIST(...) \
- OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list)
+#define OPT_FLOAT(field) \
+ OPT_TYPED_FIELD(m_option_type_float, float, field)
-#define OPT_KEYVALUELIST(...) \
- OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_keyvalue_list)
+#define OPT_DOUBLE(field) \
+ OPT_TYPED_FIELD(m_option_type_double, double, field)
-#define OPT_PATHLIST(...) \
- OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list,\
- .priv = (void *)&m_option_path_separator)
+#define OPT_STRING(field) \
+ OPT_TYPED_FIELD(m_option_type_string, char*, field)
-#define OPT_INT(...) \
- OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_int)
+#define OPT_STRINGLIST(field) \
+ OPT_TYPED_FIELD(m_option_type_string_list, char**, field)
-#define OPT_INT64(...) \
- OPT_GENERAL(int64_t, __VA_ARGS__, .type = &m_option_type_int64)
+#define OPT_KEYVALUELIST(field) \
+ OPT_TYPED_FIELD(m_option_type_keyvalue_list, char**, field)
-#define OPT_RANGE_(ctype, optname, varname, flags, minval, maxval, ...) \
- OPT_GENERAL(ctype, optname, varname, (flags) | CONF_RANGE, \
- .min = minval, .max = maxval, __VA_ARGS__)
+#define OPT_PATHLIST(field) \
+ OPT_TYPED_FIELD(m_option_type_string_list, char**, field), \
+ .priv = (void *)&m_option_path_separator
-#define OPT_INTRANGE(...) \
- OPT_RANGE_(int, __VA_ARGS__, .type = &m_option_type_int)
+#define OPT_TIME(field) \
+ OPT_TYPED_FIELD(m_option_type_time, double, field)
-#define OPT_FLOATRANGE(...) \
- OPT_RANGE_(float, __VA_ARGS__, .type = &m_option_type_float)
+#define OPT_REL_TIME(field) \
+ OPT_TYPED_FIELD(m_option_type_rel_time, struct m_rel_time, field)
-#define OPT_DOUBLERANGE(...) \
- OPT_RANGE_(double, __VA_ARGS__, .type = &m_option_type_double)
+#define OPT_COLOR(field) \
+ OPT_TYPED_FIELD(m_option_type_color, struct m_color, field)
-#define OPT_INTPAIR(...) \
- OPT_GENERAL_NOTYPE(__VA_ARGS__, .type = &m_option_type_intpair)
+#define OPT_BYTE_SIZE(field) \
+ OPT_TYPED_FIELD(m_option_type_byte_size, int64_t, field)
-#define OPT_FLOAT(...) \
- OPT_GENERAL(float, __VA_ARGS__, .type = &m_option_type_float)
+// (Approximation of x<=SIZE_MAX/2 for m_option.max, which is double.)
+#define M_MAX_MEM_BYTES MPMIN((1ULL << 62), (size_t)-1 / 2)
-#define OPT_DOUBLE(...) \
- OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_double)
+#define OPT_GEOMETRY(field) \
+ OPT_TYPED_FIELD(m_option_type_geometry, struct m_geometry, field)
-#define OPT_STRING(...) \
- OPT_GENERAL(char*, __VA_ARGS__, .type = &m_option_type_string)
+#define OPT_SIZE_BOX(field) \
+ OPT_TYPED_FIELD(m_option_type_size_box, struct m_geometry, field)
-#define OPT_SETTINGSLIST(optname, varname, flags, objlist, ...) \
- OPT_GENERAL(m_obj_settings_t*, optname, varname, flags, \
- .type = &m_option_type_obj_settings_list, \
- .priv = (void*)MP_EXPECT_TYPE(const struct m_obj_list*, objlist), \
- __VA_ARGS__)
+#define OPT_TRACKCHOICE(field) \
+ OPT_CHOICE(field, {"no", -2}, {"auto", -1}), \
+ M_RANGE(0, 8190)
-#define OPT_IMAGEFORMAT(...) \
- OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_imgfmt)
+#define OPT_MSGLEVELS(field) \
+ OPT_TYPED_FIELD(m_option_type_msglevels, char **, field)
-#define OPT_AUDIOFORMAT(...) \
- OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
+#define OPT_ASPECT(field) \
+ OPT_TYPED_FIELD(m_option_type_aspect, float, field)
-// If .min==1, then passing auto is disallowed, but "" is still accepted, and
-// limit channel list to 1 item.
-#define OPT_CHANNELS(...) \
- OPT_GENERAL(struct m_channels, __VA_ARGS__, .type = &m_option_type_channels)
+#define OPT_IMAGEFORMAT(field) \
+ OPT_TYPED_FIELD(m_option_type_imgfmt, int, field)
-#define M_CHOICES(choices) \
- .priv = (void *)&(const struct m_opt_choice_alternatives[]){ \
- OPT_HELPER_REMOVEPAREN choices, {NULL}}
+#define OPT_AUDIOFORMAT(field) \
+ OPT_TYPED_FIELD(m_option_type_afmt, int, field)
-#define OPT_CHOICE(...) \
- OPT_CHOICE_(__VA_ARGS__, .type = &m_option_type_choice)
-#define OPT_CHOICE_(optname, varname, flags, choices, ...) \
- OPT_GENERAL(int, optname, varname, flags, M_CHOICES(choices), __VA_ARGS__)
-// Variant which takes a pointer to struct m_opt_choice_alternatives directly
-#define OPT_CHOICE_C(optname, varname, flags, choices, ...) \
- OPT_GENERAL(int, optname, varname, flags, .priv = (void *) \
- MP_EXPECT_TYPE(const struct m_opt_choice_alternatives*, choices), \
- .type = &m_option_type_choice, __VA_ARGS__)
-
-#define OPT_FLAGS(...) \
- OPT_CHOICE_(__VA_ARGS__, .type = &m_option_type_flags)
-
-// Union of choices and an int range. The choice values can be included in the
-// int range, or be completely separate - both works.
-#define OPT_CHOICE_OR_INT_(optname, varname, flags, minval, maxval, choices, ...) \
- OPT_GENERAL(int, optname, varname, (flags) | CONF_RANGE, \
- .min = minval, .max = maxval, \
- M_CHOICES(choices), __VA_ARGS__)
-#define OPT_CHOICE_OR_INT(...) \
- OPT_CHOICE_OR_INT_(__VA_ARGS__, .type = &m_option_type_choice)
-
-#define OPT_TIME(...) \
- OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_time)
-
-#define OPT_REL_TIME(...) \
- OPT_GENERAL(struct m_rel_time, __VA_ARGS__, .type = &m_option_type_rel_time)
+// If .min==1, then passing auto is disallowed, but "" is still accepted, and
+// limit channel list to 1 item.
+#define OPT_CHANNELS(field) \
+ OPT_TYPED_FIELD(m_option_type_channels, struct m_channels, field)
-#define OPT_COLOR(...) \
- OPT_GENERAL(struct m_color, __VA_ARGS__, .type = &m_option_type_color)
+#define OPT_STRING_VALIDATE(field, validate_fn) \
+ OPT_TYPED_FIELD(m_option_type_string, char*, field), \
+ .priv = MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn)
-#define OPT_BYTE_SIZE(...) \
- OPT_RANGE_(int64_t, __VA_ARGS__, .type = &m_option_type_byte_size)
+#define M_CHOICES(...) \
+ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ __VA_ARGS__, {0}}
-#define OPT_GEOMETRY(...) \
- OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_geometry)
+// Variant which takes a pointer to struct m_opt_choice_alternatives directly
+#define OPT_CHOICE_C(field, choices) \
+ OPT_TYPED_FIELD(m_option_type_choice, int, field), \
+ .priv = (void *)MP_EXPECT_TYPE(const struct m_opt_choice_alternatives*, choices)
-#define OPT_SIZE_BOX(...) \
- OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_size_box)
+// Variant where you pass a struct m_opt_choice_alternatives initializer
+#define OPT_CHOICE(field, ...) \
+ OPT_TYPED_FIELD(m_option_type_choice, int, field), \
+ M_CHOICES(__VA_ARGS__)
-#define OPT_TRACKCHOICE(name, var, ...) \
- OPT_CHOICE_OR_INT(name, var, 0, 0, 8190, ({"no", -2}, {"auto", -1}), \
- ## __VA_ARGS__)
+#define OPT_FLAGS(field, ...) \
+ OPT_TYPED_FIELD(m_option_type_flags, int, field), \
+ M_CHOICES(__VA_ARGS__)
-#define OPT_ASPECT(...) \
- OPT_GENERAL(float, __VA_ARGS__, .type = &m_option_type_aspect)
+#define OPT_SETTINGSLIST(field, objlist) \
+ OPT_TYPED_FIELD(m_option_type_obj_settings_list, m_obj_settings_t*, field), \
+ .priv = (void*)MP_EXPECT_TYPE(const struct m_obj_list*, objlist)
-#define OPT_STRING_VALIDATE_(optname, varname, flags, validate_fn, ...) \
- OPT_GENERAL(char*, optname, varname, flags, __VA_ARGS__, \
- .priv = MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn))
-#define OPT_STRING_VALIDATE(...) \
- OPT_STRING_VALIDATE_(__VA_ARGS__, .type = &m_option_type_string)
+#define OPT_FOURCC(field) \
+ OPT_TYPED_FIELD(m_option_type_fourcc, int, field)
-#define OPT_PRINT(optname, fn) \
- {.name = optname, \
- .flags = M_OPT_NOCFG | M_OPT_PRE_PARSE | M_OPT_NOPROP, \
- .type = &m_option_type_print_fn, \
- .priv = MP_EXPECT_TYPE(m_opt_print_fn, fn), \
- .offset = -1}
+#define OPT_CYCLEDIR(field) \
+ OPT_TYPED_FIELD(m_option_type_cycle_dir, double, field)
// subconf must have the type struct m_sub_options.
// All sub-options are prefixed with "name-" and are added to the current
// (containing) option list.
// If name is "", add the sub-options directly instead.
-// varname refers to the field, that must be a pointer to a field described by
+// "field" refers to the field, that must be a pointer to a field described by
// the subconf struct.
-#define OPT_SUBSTRUCT(name, varname, subconf, flagv) \
- OPT_GENERAL_NOTYPE(name, varname, flagv, \
- .type = &m_option_type_subconfig, \
- .priv = (void*)&subconf)
+#define OPT_SUBSTRUCT(field, subconf) \
+ .offset = offsetof(OPT_BASE_STRUCT, field), \
+ .type = &m_option_type_subconfig, .priv = (void*)&subconf
+
+// Non-fields
-// Provide a another name for the option.
-#define OPT_ALIAS(optname, newname) \
- {.name = optname, .type = &m_option_type_alias, .priv = newname, \
- .offset = -1}
+#define OPT_ALIAS(newname) \
+ .type = &m_option_type_alias, .priv = newname, .offset = -1
// If "--optname" was removed, but "--newname" has the same semantics.
// It will be redirected, and a warning will be printed on first use.
-#define OPT_REPLACED_MSG(optname, newname, msg) \
- {.name = optname, .type = &m_option_type_alias, .priv = newname, \
- .deprecation_message = (msg), .offset = -1}
+#define OPT_REPLACED_MSG(newname, msg) \
+ .type = &m_option_type_alias, .priv = newname, \
+ .deprecation_message = (msg), .offset = -1
// Same, with a generic deprecation message.
-#define OPT_REPLACED(optname, newname) OPT_REPLACED_MSG(optname, newname, "")
+#define OPT_REPLACED(newname) OPT_REPLACED_MSG(newname, "")
// Alias, resolved on the CLI/config file/profile parser level only.
-#define OPT_CLI_ALIAS(optname, newname) \
- {.name = optname, .type = &m_option_type_cli_alias, .priv = newname, \
- .flags = M_OPT_NOPROP, .offset = -1}
+#define OPT_CLI_ALIAS(newname) \
+ .type = &m_option_type_cli_alias, .priv = newname, \
+ .flags = M_OPT_NOPROP, .offset = -1
// "--optname" doesn't exist, but inform the user about a replacement with msg.
-#define OPT_REMOVED(optname, msg) \
- {.name = optname, .type = &m_option_type_removed, .priv = msg, \
- .deprecation_message = "", .flags = M_OPT_NOPROP, .offset = -1}
+#define OPT_REMOVED(msg) \
+ .type = &m_option_type_removed, .priv = msg, \
+ .deprecation_message = "", .flags = M_OPT_NOPROP, .offset = -1
+
+#define OPT_PRINT(fn) \
+ .flags = M_OPT_NOCFG | M_OPT_PRE_PARSE | M_OPT_NOPROP, \
+ .type = &m_option_type_print_fn, \
+ .priv = MP_EXPECT_TYPE(m_opt_print_fn, fn), \
+ .offset = -1
#endif /* MPLAYER_M_OPTION_H */
diff --git a/options/m_property.c b/options/m_property.c
index 431e16a51a..d286f60ad8 100644
--- a/options/m_property.c
+++ b/options/m_property.c
@@ -159,7 +159,8 @@ int m_property_do(struct mp_log *log, const struct m_property *prop_list,
return r;
}
case M_PROPERTY_GET_CONSTRICTED_TYPE: {
- if ((r = do_action(prop_list, name, action, arg, ctx)) >= 0)
+ r = do_action(prop_list, name, action, arg, ctx);
+ if (r >= 0 || r == M_PROPERTY_UNAVAILABLE)
return r;
if ((r = do_action(prop_list, name, M_PROPERTY_GET_TYPE, arg, ctx)) >= 0)
return r;
diff --git a/options/options.c b/options/options.c
index 3dc85162fd..170472af1c 100644
--- a/options/options.c
+++ b/options/options.c
@@ -22,6 +22,7 @@
* config for cfgparser
*/
+#include <float.h>
#include <stddef.h>
#include <sys/types.h>
#include <limits.h>
@@ -105,65 +106,65 @@ static const struct m_sub_options screenshot_conf = {
#define OPT_BASE_STRUCT struct mp_vo_opts
static const m_option_t mp_vo_opt_list[] = {
- OPT_SETTINGSLIST("vo", video_driver_list, 0, &vo_obj_list, ),
- OPT_FLAG("taskbar-progress", taskbar_progress, 0),
- OPT_FLAG("snap-window", snap_window, 0),
- OPT_FLAG("ontop", ontop, 0),
- OPT_CHOICE_OR_INT("ontop-level", ontop_level, 0, 0, INT_MAX,
- ({"window", -1}, {"system", -2})),
- OPT_FLAG("border", border, 0),
- OPT_FLAG("fit-border", fit_border, 0),
- OPT_FLAG("on-all-workspaces", all_workspaces, 0),
- OPT_GEOMETRY("geometry", geometry, 0),
- OPT_SIZE_BOX("autofit", autofit, 0),
- OPT_SIZE_BOX("autofit-larger", autofit_larger, 0),
- OPT_SIZE_BOX("autofit-smaller", autofit_smaller, 0),
- OPT_DOUBLE("window-scale", window_scale, CONF_RANGE, .min = 0.001, .max = 100),
- OPT_FLAG("window-minimized", window_minimized, 0),
- OPT_FLAG("window-maximized", window_maximized, 0),
- OPT_FLAG("force-window-position", force_window_position, 0),
- OPT_STRING("x11-name", winname, 0),
- OPT_FLOATRANGE("monitoraspect", force_monitor_aspect, 0, 0.0, 9.0),
- OPT_FLOATRANGE("monitorpixelaspect", monitor_pixel_aspect, 0, 1.0/32.0, 32.0),
- OPT_FLAG("fullscreen", fullscreen, 0),
- OPT_ALIAS("fs", "fullscreen"),
- OPT_FLAG("native-keyrepeat", native_keyrepeat, 0),
- OPT_FLOATRANGE("panscan", panscan, 0, 0.0, 1.0),
- OPT_FLOATRANGE("video-zoom", zoom, 0, -20.0, 20.0),
- OPT_FLOATRANGE("video-pan-x", pan_x, 0, -3.0, 3.0),
- OPT_FLOATRANGE("video-pan-y", pan_y, 0, -3.0, 3.0),
- OPT_FLOATRANGE("video-align-x", align_x, 0, -1.0, 1.0),
- OPT_FLOATRANGE("video-align-y", align_y, 0, -1.0, 1.0),
- OPT_FLOATRANGE("video-margin-ratio-left", margin_x[0], 0, 0.0, 1.0),
- OPT_FLOATRANGE("video-margin-ratio-right", margin_x[1], 0, 0.0, 1.0),
- OPT_FLOATRANGE("video-margin-ratio-top", margin_y[0], 0, 0.0, 1.0),
- OPT_FLOATRANGE("video-margin-ratio-bottom", margin_y[1], 0, 0.0, 1.0),
- OPT_CHOICE("video-unscaled", unscaled, 0,
- ({"no", 0}, {"yes", 1}, {"downscale-big", 2})),
- OPT_INT64("wid", WinID, 0),
- OPT_CHOICE_OR_INT("screen", screen_id, 0, 0, 32,
- ({"default", -1})),
- OPT_CHOICE_OR_INT("fs-screen", fsscreen_id, 0, 0, 32,
- ({"all", -2}, {"current", -1})),
- OPT_FLAG("keepaspect", keepaspect, 0),
- OPT_FLAG("keepaspect-window", keepaspect_window, 0),
- OPT_FLAG("hidpi-window-scale", hidpi_window_scale, 0),
- OPT_FLAG("native-fs", native_fs, 0),
- OPT_DOUBLE("override-display-fps", override_display_fps, M_OPT_MIN, .min = 0),
- OPT_DOUBLERANGE("video-timing-offset", timing_offset, 0, 0.0, 1.0),
+ {"vo", OPT_SETTINGSLIST(video_driver_list, &vo_obj_list)},
+ {"taskbar-progress", OPT_FLAG(taskbar_progress)},
+ {"snap-window", OPT_FLAG(snap_window)},
+ {"ontop", OPT_FLAG(ontop)},
+ {"ontop-level", OPT_CHOICE(ontop_level, {"window", -1}, {"system", -2}),
+ M_RANGE(0, INT_MAX)},
+ {"border", OPT_FLAG(border)},
+ {"fit-border", OPT_FLAG(fit_border)},
+ {"on-all-workspaces", OPT_FLAG(all_workspaces)},
+ {"geometry", OPT_GEOMETRY(geometry)},
+ {"autofit", OPT_SIZE_BOX(autofit)},
+ {"autofit-larger", OPT_SIZE_BOX(autofit_larger)},
+ {"autofit-smaller", OPT_SIZE_BOX(autofit_smaller)},
+ {"window-scale", OPT_DOUBLE(window_scale), M_RANGE(0.001, 100)},
+ {"window-minimized", OPT_FLAG(window_minimized)},
+ {"window-maximized", OPT_FLAG(window_maximized)},
+ {"force-window-position", OPT_FLAG(force_window_position)},
+ {"x11-name", OPT_STRING(winname)},
+ {"monitoraspect", OPT_FLOAT(force_monitor_aspect), M_RANGE(0.0, 9.0)},
+ {"monitorpixelaspect", OPT_FLOAT(monitor_pixel_aspect),
+ M_RANGE(1.0/32.0, 32.0)},
+ {"fullscreen", OPT_BOOL(fullscreen)},
+ {"fs", OPT_ALIAS("fullscreen")},
+ {"native-keyrepeat", OPT_FLAG(native_keyrepeat)},
+ {"panscan", OPT_FLOAT(panscan), M_RANGE(0.0, 1.0)},
+ {"video-zoom", OPT_FLOAT(zoom), M_RANGE(-20.0, 20.0)},
+ {"video-pan-x", OPT_FLOAT(pan_x), M_RANGE(-3.0, 3.0)},
+ {"video-pan-y", OPT_FLOAT(pan_y), M_RANGE(-3.0, 3.0)},
+ {"video-align-x", OPT_FLOAT(align_x), M_RANGE(-1.0, 1.0)},
+ {"video-align-y", OPT_FLOAT(align_y), M_RANGE(-1.0, 1.0)},
+ {"video-margin-ratio-left", OPT_FLOAT(margin_x[0]), M_RANGE(0.0, 1.0)},
+ {"video-margin-ratio-right", OPT_FLOAT(margin_x[1]), M_RANGE(0.0, 1.0)},
+ {"video-margin-ratio-top", OPT_FLOAT(margin_y[0]), M_RANGE(0.0, 1.0)},
+ {"video-margin-ratio-bottom", OPT_FLOAT(margin_y[1]), M_RANGE(0.0, 1.0)},
+ {"video-unscaled", OPT_CHOICE(unscaled,
+ {"no", 0}, {"yes", 1}, {"downscale-big", 2})},
+ {"wid", OPT_INT64(WinID)},
+ {"screen", OPT_CHOICE(screen_id, {"default", -1}), M_RANGE(0, 32)},
+ {"fs-screen", OPT_CHOICE(fsscreen_id, {"all", -2}, {"current", -1}),
+ M_RANGE(0, 32)},
+ {"keepaspect", OPT_FLAG(keepaspect)},
+ {"keepaspect-window", OPT_FLAG(keepaspect_window)},
+ {"hidpi-window-scale", OPT_FLAG(hidpi_window_scale)},
+ {"native-fs", OPT_FLAG(native_fs)},
+ {"override-display-fps", OPT_DOUBLE(override_display_fps),
+ M_RANGE(0, DBL_MAX)},
+ {"video-timing-offset", OPT_DOUBLE(timing_offset), M_RANGE(0.0, 1.0)},
#if HAVE_X11
- OPT_CHOICE("x11-netwm", x11_netwm, 0,
- ({"auto", 0}, {"no", -1}, {"yes", 1})),
- OPT_CHOICE("x11-bypass-compositor", x11_bypass_compositor, 0,
- ({"no", 0}, {"yes", 1}, {"fs-only", 2}, {"never", 3})),
+ {"x11-netwm", OPT_CHOICE(x11_netwm, {"auto", 0}, {"no", -1}, {"yes", 1})},
+ {"x11-bypass-compositor", OPT_CHOICE(x11_bypass_compositor,
+ {"no", 0}, {"yes", 1}, {"fs-only", 2}, {"never", 3})},
#endif
#if HAVE_WIN32_DESKTOP
- OPT_STRING("vo-mmcss-profile", mmcss_profile, 0),
+ {"vo-mmcss-profile", OPT_STRING(mmcss_profile)},
#endif
#if HAVE_DRM
- OPT_SUBSTRUCT("", drm_opts, drm_conf, 0),
+ {"", OPT_SUBSTRUCT(drm_opts, drm_conf)},
#endif
- OPT_INTRANGE("swapchain-depth", swapchain_depth, 0, 1, 8),
+ {"swapchain-depth", OPT_INT(swapchain_depth), M_RANGE(1, 8)},
{0}
};
@@ -199,11 +200,11 @@ const struct m_sub_options vo_sub_opts = {
const struct m_sub_options mp_sub_filter_opts = {
.opts = (const struct m_option[]){
- OPT_FLAG("sub-filter-sdh", sub_filter_SDH, 0),
- OPT_FLAG("sub-filter-sdh-harder", sub_filter_SDH_harder, 0),
- OPT_FLAG("sub-filter-regex-enable", rf_enable, 0),
- OPT_STRINGLIST("sub-filter-regex", rf_items, 0),
- OPT_FLAG("sub-filter-regex-warn", rf_warn, 0),
+ {"sub-filter-sdh", OPT_FLAG(sub_filter_SDH)},
+ {"sub-filter-sdh-harder", OPT_FLAG(sub_filter_SDH_harder)},
+ {"sub-filter-regex-enable", OPT_FLAG(rf_enable)},
+ {"sub-filter-regex", OPT_STRINGLIST(rf_items)},
+ {"sub-filter-regex-warn", OPT_FLAG(rf_warn)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
@@ -218,43 +219,44 @@ const struct m_sub_options mp_sub_filter_opts = {
const struct m_sub_options mp_subtitle_sub_opts = {
.opts = (const struct m_option[]){
- OPT_FLOAT("sub-delay", sub_delay, 0),
- OPT_FLOAT("sub-fps", sub_fps, 0),
- OPT_FLOAT("sub-speed", sub_speed, 0),
- OPT_FLAG("sub-visibility", sub_visibility, 0),
- OPT_FLAG("sub-forced-only", forced_subs_only, 0),
- OPT_FLAG("stretch-dvd-subs", stretch_dvd_subs, 0),
- OPT_FLAG("stretch-image-subs-to-screen", stretch_image_subs, 0),
- OPT_FLAG("image-subs-video-resolution", image_subs_video_res, 0),
- OPT_FLAG("sub-fix-timing", sub_fix_timing, 0),
- OPT_INTRANGE("sub-pos", sub_pos, 0, 0, 100),
- OPT_FLOATRANGE("sub-gauss", sub_gauss, 0, 0.0, 3.0),
- OPT_FLAG("sub-gray", sub_gray, 0),
- OPT_FLAG("sub-ass", ass_enabled, 0),
- OPT_FLOATRANGE("sub-scale", sub_scale, 0, 0, 100),
- OPT_FLOATRANGE("sub-ass-line-spacing", ass_line_spacing, 0, -1000, 1000),
- OPT_FLAG("sub-use-margins", sub_use_margins, 0),
- OPT_FLAG("sub-ass-force-margins", ass_use_margins, 0),
- OPT_FLAG("sub-ass-vsfilter-aspect-compat", ass_vsfilter_aspect_compat, 0),
- OPT_CHOICE("sub-ass-vsfilter-color-compat", ass_vsfilter_color_compat, 0,
- ({"no", 0}, {"basic", 1}, {"full", 2}, {"force-601", 3})),
- OPT_FLAG("sub-ass-vsfilter-blur-compat", ass_vsfilter_blur_compat, 0),
- OPT_FLAG("embeddedfonts", use_embedded_fonts, 0),
- OPT_STRINGLIST("sub-ass-force-style", ass_force_style_list, 0),
- OPT_STRING("sub-ass-styles", ass_styles_file, M_OPT_FILE),
- OPT_CHOICE("sub-ass-hinting", ass_hinting, 0,
- ({"none", 0}, {"light", 1}, {"normal", 2}, {"native", 3})),
- OPT_CHOICE("sub-ass-shaper", ass_shaper, 0,
- ({"simple", 0}, {"complex", 1})),
- OPT_FLAG("sub-ass-justify", ass_justify, 0),
- OPT_CHOICE("sub-ass-override", ass_style_override, 0,
- ({"no", 0}, {"yes", 1}, {"force", 3}, {"scale", 4}, {"strip", 5})),
- OPT_FLAG("sub-scale-by-window", sub_scale_by_window, 0),
- OPT_FLAG("sub-scale-with-window", sub_scale_with_window, 0),
- OPT_FLAG("sub-ass-scale-with-window", ass_scale_with_window, 0),
- OPT_SUBSTRUCT("sub", sub_style, sub_style_conf, 0),
- OPT_FLAG("sub-clear-on-seek", sub_clear_on_seek, 0),
- OPT_INTRANGE("teletext-page", teletext_page, 0, 1, 999),
+ {"sub-delay", OPT_FLOAT(sub_delay)},
+ {"sub-fps", OPT_FLOAT(sub_fps)},
+ {"sub-speed", OPT_FLOAT(sub_speed)},
+ {"sub-visibility", OPT_FLAG(sub_visibility)},
+ {"sub-forced-only", OPT_FLAG(forced_subs_only)},
+ {"stretch-dvd-subs", OPT_FLAG(stretch_dvd_subs)},
+ {"stretch-image-subs-to-screen", OPT_FLAG(stretch_image_subs)},
+ {"image-subs-video-resolution", OPT_FLAG(image_subs_video_res)},
+ {"sub-fix-timing", OPT_FLAG(sub_fix_timing)},
+ {"sub-pos", OPT_INT(sub_pos), M_RANGE(0, 100)},
+ {"sub-gauss", OPT_FLOAT(sub_gauss), M_RANGE(0.0, 3.0)},
+ {"sub-gray", OPT_FLAG(sub_gray)},
+ {"sub-ass", OPT_FLAG(ass_enabled)},
+ {"sub-scale", OPT_FLOAT(sub_scale), M_RANGE(0, 100)},
+ {"sub-ass-line-spacing", OPT_FLOAT(ass_line_spacing),
+ M_RANGE(-1000, 1000)},
+ {"sub-use-margins", OPT_FLAG(sub_use_margins)},
+ {"sub-ass-force-margins", OPT_FLAG(ass_use_margins)},
+ {"sub-ass-vsfilter-aspect-compat", OPT_FLAG(ass_vsfilter_aspect_compat)},
+ {"sub-ass-vsfilter-color-compat", OPT_CHOICE(ass_vsfilter_color_compat,
+ {"no", 0}, {"basic", 1}, {"full", 2}, {"force-601", 3})},
+ {"sub-ass-vsfilter-blur-compat", OPT_FLAG(ass_vsfilter_blur_compat)},
+ {"embeddedfonts", OPT_FLAG(use_embedded_fonts)},
+ {"sub-ass-force-style", OPT_STRINGLIST(ass_force_style_list)},
+ {"sub-ass-styles", OPT_STRING(ass_styles_file), .flags = M_OPT_FILE},
+ {"sub-ass-hinting", OPT_CHOICE(ass_hinting,
+ {"none", 0}, {"light", 1}, {"normal", 2}, {"native", 3})},
+ {"sub-ass-shaper", OPT_CHOICE(ass_shaper,
+ {"simple", 0}, {"complex", 1})},
+ {"sub-ass-justify", OPT_FLAG(ass_justify)},
+ {"sub-ass-override", OPT_CHOICE(ass_style_override,
+ {"no", 0}, {"yes", 1}, {"force", 3}, {"scale", 4}, {"strip", 5})},
+ {"sub-scale-by-window", OPT_FLAG(sub_scale_by_window)},
+ {"sub-scale-with-window", OPT_FLAG(sub_scale_with_window)},
+ {"sub-ass-scale-with-window", OPT_FLAG(ass_scale_with_window)},
+ {"sub", OPT_SUBSTRUCT(sub_style, sub_style_conf)},
+ {"sub-clear-on-seek", OPT_FLAG(sub_clear_on_seek)},
+ {"teletext-page", OPT_INT(teletext_page), M_RANGE(1, 999)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
@@ -285,14 +287,14 @@ const struct m_sub_options mp_subtitle_sub_opts = {
const struct m_sub_options mp_osd_render_sub_opts = {
.opts = (const struct m_option[]){
- OPT_FLOATRANGE("osd-bar-align-x", osd_bar_align_x, 0, -1.0, +1.0),
- OPT_FLOATRANGE("osd-bar-align-y", osd_bar_align_y, 0, -1.0, +1.0),
- OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
- OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
- OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
- OPT_FLOATRANGE("osd-scale", osd_scale, 0, 0, 100),
- OPT_FLAG("osd-scale-by-window", osd_scale_by_window, 0),
- OPT_FLAG("force-rgba-osd-rendering", force_rgba_osd, 0),
+ {"osd-bar-align-x", OPT_FLOAT(osd_bar_align_x), M_RANGE(-1.0, +1.0)},
+ {"osd-bar-align-y", OPT_FLOAT(osd_bar_align_y), M_RANGE(-1.0, +1.0)},
+ {"osd-bar-w", OPT_FLOAT(osd_bar_w), M_RANGE(1, 100)},
+ {"osd-bar-h", OPT_FLOAT(osd_bar_h), M_RANGE(0.1, 50)},
+ {"osd", OPT_SUBSTRUCT(osd_style, osd_style_conf)},
+ {"osd-scale", OPT_FLOAT(osd_scale), M_RANGE(0, 100)},
+ {"osd-scale-by-window", OPT_FLAG(osd_scale_by_window)},
+ {"force-rgba-osd-rendering", OPT_FLAG(force_rgba_osd)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
@@ -311,9 +313,9 @@ const struct m_sub_options mp_osd_render_sub_opts = {
const struct m_sub_options dvd_conf = {
.opts = (const struct m_option[]){
- OPT_STRING("dvd-device", device, M_OPT_FILE),
- OPT_INT("dvd-speed", speed, 0),
- OPT_INTRANGE("dvd-angle", angle, 0, 1, 99),
+ {"dvd-device", OPT_STRING(device), .flags = M_OPT_FILE},
+ {"dvd-speed", OPT_INT(speed)},
+ {"dvd-angle", OPT_INT(angle), M_RANGE(1, 99)},
{0}
},
.size = sizeof(struct dvd_opts),
@@ -327,7 +329,7 @@ const struct m_sub_options dvd_conf = {
const struct m_sub_options filter_conf = {
.opts = (const struct m_option[]){
- OPT_FLAG("deinterlace", deinterlace, 0),
+ {"deinterlace", OPT_FLAG(deinterlace)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
@@ -341,8 +343,7 @@ static const m_option_t mp_opts[] = {
// handled in command line pre-parser (parse_commandline.c)
{"v", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
.offset = -1},
- {"playlist", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_MIN | M_OPT_FILE,
- .min = 1, .offset = -1},
+ {"playlist", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_FILE, .offset = -1},
{"{", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
.offset = -1},
{"}", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
@@ -355,565 +356,561 @@ static const m_option_t mp_opts[] = {
M_OPT_OPTIONAL_PARAM, .offset = -1},
{ "list-options", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
.offset = -1},
- OPT_FLAG("list-properties", property_print_help, CONF_NOCFG | M_OPT_NOPROP),
+ {"list-properties", OPT_FLAG(property_print_help),
+ .flags = CONF_NOCFG | M_OPT_NOPROP},
{ "help", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_NOPROP | M_OPT_OPTIONAL_PARAM,
.offset = -1},
{ "h", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_NOPROP | M_OPT_OPTIONAL_PARAM,
.offset = -1},
- OPT_PRINT("list-protocols", stream_print_proto_list),
- OPT_PRINT("version", print_version),
- OPT_PRINT("V", print_version),
+ {"list-protocols", OPT_PRINT(stream_print_proto_list)},
+ {"version", OPT_PRINT(print_version)},
+ {"V", OPT_PRINT(print_version)},
#if HAVE_TESTS
- OPT_STRING("unittest", test_mode, CONF_NOCFG | M_OPT_NOPROP),
+ {"unittest", OPT_STRING(test_mode), .flags = CONF_NOCFG | M_OPT_NOPROP},
#endif
- OPT_CHOICE("player-operation-mode", operation_mode,
- M_OPT_PRE_PARSE | M_OPT_NOPROP,
- ({"cplayer", 0}, {"pseudo-gui", 1})),
+ {"player-operation-mode", OPT_CHOICE(operation_mode,
+ {"cplayer", 0}, {"pseudo-gui", 1}),
+ .flags = M_OPT_PRE_PARSE | M_OPT_NOPROP},
- OPT_FLAG("shuffle", shuffle, 0),
+ {"shuffle", OPT_FLAG(shuffle)},
// ------------------------- common options --------------------
- OPT_FLAG("quiet", quiet, 0),
- OPT_FLAG("really-quiet", msg_really_quiet, CONF_PRE_PARSE | UPDATE_TERM),
- OPT_FLAG("terminal", use_terminal, CONF_PRE_PARSE | UPDATE_TERM),
- OPT_GENERAL(char**, "msg-level", msg_levels, CONF_PRE_PARSE | UPDATE_TERM,
- .type = &m_option_type_msglevels),
- OPT_STRING("dump-stats", dump_stats, UPDATE_TERM | CONF_PRE_PARSE | M_OPT_FILE),
- OPT_FLAG("msg-color", msg_color, CONF_PRE_PARSE | UPDATE_TERM),
- OPT_STRING("log-file", log_file, CONF_PRE_PARSE | M_OPT_FILE | UPDATE_TERM),
- OPT_FLAG("msg-module", msg_module, UPDATE_TERM),
- OPT_FLAG("msg-time", msg_time, UPDATE_TERM),
+ {"quiet", OPT_FLAG(quiet)},
+ {"really-quiet", OPT_FLAG(msg_really_quiet),
+ .flags = CONF_PRE_PARSE | UPDATE_TERM},
+ {"terminal", OPT_FLAG(use_terminal), .flags = CONF_PRE_PARSE | UPDATE_TERM},
+ {"msg-level", OPT_MSGLEVELS(msg_levels),
+ .flags = CONF_PRE_PARSE | UPDATE_TERM},
+ {"dump-stats", OPT_STRING(dump_stats),
+ .flags = UPDATE_TERM | CONF_PRE_PARSE | M_OPT_FILE},
+ {"msg-color", OPT_FLAG(msg_color), .flags = CONF_PRE_PARSE | UPDATE_TERM},
+ {"log-file", OPT_STRING(log_file),
+ .flags = CONF_PRE_PARSE | M_OPT_FILE | UPDATE_TERM},
+ {"msg-module", OPT_FLAG(msg_module), .flags = UPDATE_TERM},
+ {"msg-time", OPT_FLAG(msg_time), .flags = UPDATE_TERM},
#if HAVE_WIN32_DESKTOP
- OPT_CHOICE("priority", w32_priority, UPDATE_PRIORITY,
- ({"no", 0},
- {"realtime", REALTIME_PRIORITY_CLASS},
- {"high", HIGH_PRIORITY_CLASS},
- {"abovenormal", ABOVE_NORMAL_PRIORITY_CLASS},
- {"normal", NORMAL_PRIORITY_CLASS},
- {"belownormal", BELOW_NORMAL_PRIORITY_CLASS},
- {"idle", IDLE_PRIORITY_CLASS})),
+ {"priority", OPT_CHOICE(w32_priority,
+ {"no", 0},
+ {"realtime", REALTIME_PRIORITY_CLASS},
+ {"high", HIGH_PRIORITY_CLASS},
+ {"abovenormal", ABOVE_NORMAL_PRIORITY_CLASS},
+ {"normal", NORMAL_PRIORITY_CLASS},
+ {"belownormal", BELOW_NORMAL_PRIORITY_CLASS},
+ {"idle", IDLE_PRIORITY_CLASS}),
+ .flags = UPDATE_PRIORITY},
#endif
- OPT_FLAG("config", load_config, CONF_PRE_PARSE),
- OPT_STRING("config-dir", force_configdir,
- CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
- OPT_STRINGLIST("reset-on-next-file", reset_options, 0),
+ {"config", OPT_FLAG(load_config), .flags = CONF_PRE_PARSE},
+ {"config-dir", OPT_STRING(force_configdir),
+ .flags = CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE},
+ {"reset-on-next-file", OPT_STRINGLIST(reset_options)},
#if HAVE_LUA || HAVE_JAVASCRIPT
- OPT_PATHLIST("scripts", script_files, M_OPT_FILE),
- OPT_CLI_ALIAS("script", "scripts-append"),
- OPT_KEYVALUELIST("script-opts", script_opts, 0),
- OPT_FLAG("load-scripts", auto_load_scripts, 0),
+ {"scripts", OPT_PATHLIST(script_files), .flags = M_OPT_FILE},
+ {"script", OPT_CLI_ALIAS("scripts-append")},
+ {"script-opts", OPT_KEYVALUELIST(script_opts)},
+ {"load-scripts", OPT_FLAG(auto_load_scripts)},
#endif
#if HAVE_LUA
- OPT_FLAG("osc", lua_load_osc, UPDATE_BUILTIN_SCRIPTS),
- OPT_FLAG("ytdl", lua_load_ytdl, UPDATE_BUILTIN_SCRIPTS),
- OPT_STRING("ytdl-format", lua_ytdl_format, 0),
- OPT_KEYVALUELIST("ytdl-raw-options", lua_ytdl_raw_options, 0),
- OPT_FLAG("load-stats-overlay", lua_load_stats, UPDATE_BUILTIN_SCRIPTS),
- OPT_FLAG("load-osd-console", lua_load_console, UPDATE_BUILTIN_SCRIPTS),
+ {"osc", OPT_FLAG(lua_load_osc), .flags = UPDATE_BUILTIN_SCRIPTS},
+ {"ytdl", OPT_FLAG(lua_load_ytdl), .flags = UPDATE_BUILTIN_SCRIPTS},
+ {"ytdl-format", OPT_STRING(lua_ytdl_format)},
+ {"ytdl-raw-options", OPT_KEYVALUELIST(lua_ytdl_raw_options)},
+ {"load-stats-overlay", OPT_FLAG(lua_load_stats),
+ .flags = UPDATE_BUILTIN_SCRIPTS},
+ {"load-osd-console", OPT_FLAG(lua_load_console),
+ .flags = UPDATE_BUILTIN_SCRIPTS},
#endif
// ------------------------- stream options --------------------
#if HAVE_DVDNAV
- OPT_SUBSTRUCT("", dvd_opts, dvd_conf, 0),
+ {"", OPT_SUBSTRUCT(dvd_opts, dvd_conf)},
#endif
- OPT_CHOICE_OR_INT("edition", edition_id, 0, 0, 8190,
- ({"auto", -1})),
+ {"edition", OPT_CHOICE(edition_id, {"auto", -1}), M_RANGE(0, 8190)},
#if HAVE_LIBBLURAY
- OPT_STRING("bluray-device", bluray_device, M_OPT_FILE),
+ {"bluray-device", OPT_STRING(bluray_device), .flags = M_OPT_FILE},
#endif /* HAVE_LIBBLURAY */
// ------------------------- demuxer options --------------------
- OPT_CHOICE_OR_INT("frames", play_frames, 0, 0, INT_MAX, ({"all", -1})),
+ {"frames", OPT_CHOICE(play_frames, {"all", -1}), M_RANGE(0, INT_MAX)},
- OPT_REL_TIME("start", play_start, 0),
- OPT_REL_TIME("end", play_end, 0),
- OPT_REL_TIME("length", play_length, 0),
+ {"start", OPT_REL_TIME(play_start)},
+ {"end", OPT_REL_TIME(play_end)},
+ {"length", OPT_REL_TIME(play_length)},
- OPT_CHOICE("play-dir", play_dir, 0,
- ({"forward", 1}, {"+", 1}, {"backward", -1}, {"-", -1})),
- OPT_BYTE_SIZE("video-reversal-buffer", video_reverse_size, 0, 0, (size_t)-1),
- OPT_BYTE_SIZE("audio-reversal-buffer", audio_reverse_size, 0, 0, (size_t)-1),
+ {"play-dir", OPT_CHOICE(play_dir,
+ {"forward", 1}, {"+", 1}, {"backward", -1}, {"-", -1})},
- OPT_FLAG("rebase-start-time", rebase_start_time, 0),
+ {"rebase-start-time", OPT_FLAG(rebase_start_time)},
- OPT_TIME("ab-loop-a", ab_loop[0], 0, .min = MP_NOPTS_VALUE),
- OPT_TIME("ab-loop-b", ab_loop[1], 0, .min = MP_NOPTS_VALUE),
- OPT_CHOICE_OR_INT("ab-loop-count", ab_loop_count, 0, 0, INT_MAX,
- ({"inf", -1})),
+ {"ab-loop-a", OPT_TIME(ab_loop[0]), .min = MP_NOPTS_VALUE},
+ {"ab-loop-b", OPT_TIME(ab_loop[1]), .min = MP_NOPTS_VALUE},
+ {"ab-loop-count", OPT_CHOICE(ab_loop_count, {"inf", -1}),
+ M_RANGE(0, INT_MAX)},
- OPT_CHOICE_OR_INT("playlist-start", playlist_pos, 0, 0, INT_MAX,
- ({"auto", -1}, {"no", -1})),
+ {"playlist-start", OPT_CHOICE(playlist_pos, {"auto", -1}, {"no", -1}),
+ M_RANGE(0, INT_MAX)},
- OPT_FLAG("pause", pause, 0),
- OPT_CHOICE("keep-open", keep_open, 0,
- ({"no", 0},
- {"yes", 1},
- {"always", 2})),
- OPT_FLAG("keep-open-pause", keep_open_pause, 0),
- OPT_DOUBLE("image-display-duration", image_display_duration,
- M_OPT_RANGE, 0, INFINITY),
+ {"pause", OPT_FLAG(pause)},
+ {"keep-open", OPT_CHOICE(keep_open,
+ {"no", 0},
+ {"yes", 1},
+ {"always", 2})},
+ {"keep-open-pause", OPT_FLAG(keep_open_pause)},
+ {"image-display-duration", OPT_DOUBLE(image_display_duration),
+ M_RANGE(0, INFINITY)},
- OPT_CHOICE("index", index_mode, 0, ({"default", 1}, {"recreate", 0})),
+ {"index", OPT_CHOICE(index_mode, {"default", 1}, {"recreate", 0})},
// select audio/video/subtitle stream
- OPT_TRACKCHOICE("aid", stream_id[0][STREAM_AUDIO]),
- OPT_TRACKCHOICE("vid", stream_id[0][STREAM_VIDEO]),
- OPT_TRACKCHOICE("sid", stream_id[0][STREAM_SUB]),
- OPT_TRACKCHOICE("secondary-sid", stream_id[1][STREAM_SUB]),
- OPT_ALIAS("sub", "sid"),
- OPT_ALIAS("video", "vid"),
- OPT_ALIAS("audio", "aid"),
- OPT_STRINGLIST("alang", stream_lang[STREAM_AUDIO], 0),
- OPT_STRINGLIST("slang", stream_lang[STREAM_SUB], 0),
- OPT_STRINGLIST("vlang", stream_lang[STREAM_VIDEO], 0),
- OPT_FLAG("track-auto-selection", stream_auto_sel, 0),
+ {"aid", OPT_TRACKCHOICE(stream_id[0][STREAM_AUDIO])},
+ {"vid", OPT_TRACKCHOICE(stream_id[0][STREAM_VIDEO])},
+ {"sid", OPT_TRACKCHOICE(stream_id[0][STREAM_SUB])},
+ {"secondary-sid", OPT_TRACKCHOICE(stream_id[1][STREAM_SUB])},
+ {"sub", OPT_ALIAS("sid")},
+ {"video", OPT_ALIAS("vid")},
+ {"audio", OPT_ALIAS("aid")},
+ {"alang", OPT_STRINGLIST(stream_lang[STREAM_AUDIO])},
+ {"slang", OPT_STRINGLIST(stream_lang[STREAM_SUB])},
+ {"vlang", OPT_STRINGLIST(stream_lang[STREAM_VIDEO])},
+ {"track-auto-selection", OPT_FLAG(stream_auto_sel)},
- OPT_STRING("lavfi-complex", lavfi_complex, UPDATE_LAVFI_COMPLEX),
+ {"lavfi-complex", OPT_STRING(lavfi_complex), .flags = UPDATE_LAVFI_COMPLEX},
- OPT_CHOICE("audio-display", audio_display, 0,
- ({"no", 0}, {"attachment", 1})),
+ {"audio-display", OPT_CHOICE(audio_display, {"no", 0}, {"attachment", 1})},
- OPT_CHOICE_OR_INT("hls-bitrate", hls_bitrate, 0, 0, INT_MAX,
- ({"no", -1}, {"min", 0}, {"max", INT_MAX})),
+ {"hls-bitrate", OPT_CHOICE(hls_bitrate,
+ {"no", -1}, {"min", 0}, {"max", INT_MAX}), M_RANGE(0, INT_MAX)},
- OPT_STRINGLIST("display-tags", display_tags, 0),
+ {"display-tags", OPT_STRINGLIST(display_tags)},
#if HAVE_CDDA
- OPT_SUBSTRUCT("cdda", stream_cdda_opts, stream_cdda_conf, 0),
- OPT_STRING("cdrom-device", cdrom_device, M_OPT_FILE),
+ {"cdda", OPT_SUBSTRUCT(stream_cdda_opts, stream_cdda_conf)},
+ {"cdrom-device", OPT_STRING(cdrom_device), .flags = M_OPT_FILE},
#endif
// demuxer.c - select audio/sub file/demuxer
- OPT_PATHLIST("audio-files", audio_files, M_OPT_FILE),
- OPT_CLI_ALIAS("audio-file", "audio-files-append"),
- OPT_STRING("demuxer", demuxer_name, 0),
- OPT_STRING("audio-demuxer", audio_demuxer_name, 0),
- OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
- OPT_FLAG("demuxer-thread", demuxer_thread, 0),
- OPT_DOUBLE("demuxer-termination-timeout", demux_termination_timeout, 0),
- OPT_FLAG("demuxer-cache-wait", demuxer_cache_wait, 0),
- OPT_FLAG("prefetch-playlist", prefetch_open, 0),
- OPT_FLAG("cache-pause", cache_pause, 0),
- OPT_FLAG("cache-pause-initial", cache_pause_initial, 0),
- OPT_FLOAT("cache-pause-wait", cache_pause_wait, M_OPT_MIN, .min = 0),
-
- OPT_DOUBLE("mf-fps", mf_fps, 0),
- OPT_STRING("mf-type", mf_type, 0),
+ {"audio-files", OPT_PATHLIST(audio_files), .flags = M_OPT_FILE},
+ {"audio-file", OPT_CLI_ALIAS("audio-files-append")},
+ {"demuxer", OPT_STRING(demuxer_name)},
+ {"audio-demuxer", OPT_STRING(audio_demuxer_name)},
+ {"sub-demuxer", OPT_STRING(sub_demuxer_name)},
+ {"demuxer-thread", OPT_FLAG(demuxer_thread)},
+ {"demuxer-termination-timeout", OPT_DOUBLE(demux_termination_timeout)},
+ {"demuxer-cache-wait", OPT_FLAG(demuxer_cache_wait)},
+ {"prefetch-playlist", OPT_FLAG(prefetch_open)},
+ {"cache-pause", OPT_FLAG(cache_pause)},
+ {"cache-pause-initial", OPT_FLAG(cache_pause_initial)},
+ {"cache-pause-wait", OPT_FLOAT(cache_pause_wait), M_RANGE(0, DBL_MAX)},
+
+ {"mf-fps", OPT_DOUBLE(mf_fps)},
+ {"mf-type", OPT_STRING(mf_type)},
#if HAVE_DVBIN
- OPT_SUBSTRUCT("dvbin", stream_dvb_opts, stream_dvb_conf, 0),
+ {"dvbin", OPT_SUBSTRUCT(stream_dvb_opts, stream_dvb_conf)},
#endif
- OPT_SUBSTRUCT("", stream_lavf_opts, stream_lavf_conf, 0),
+ {"", OPT_SUBSTRUCT(stream_lavf_opts, stream_lavf_conf)},
// ------------------------- a-v sync options --------------------
// set A-V sync correction speed (0=disables it):
- OPT_FLOATRANGE("mc", default_max_pts_correction, 0, 0, 100),
+ {"mc", OPT_FLOAT(default_max_pts_correction), M_RANGE(0, 100)},
- // force video/audio rate:
- OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
- OPT_INTRANGE("audio-samplerate", force_srate, UPDATE_AUDIO, 0, 16*48000),
- OPT_CHANNELS("audio-channels", audio_output_channels, UPDATE_AUDIO),
- OPT_AUDIOFORMAT("audio-format", audio_output_format, UPDATE_AUDIO),
- OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE, .min = 0.01, .max = 100.0),
+ {"audio-samplerate", OPT_INT(force_srate), .flags = UPDATE_AUDIO,
+ M_RANGE(0, 16*48000)},
+ {"audio-channels", OPT_CHANNELS(audio_output_channels), .flags = UPDATE_AUDIO},
+ {"audio-format", OPT_AUDIOFORMAT(audio_output_format), .flags = UPDATE_AUDIO},
+ {"speed", OPT_DOUBLE(playback_speed), M_RANGE(0.01, 100.0)},
- OPT_FLAG("audio-pitch-correction", pitch_correction, 0),
+ {"audio-pitch-correction", OPT_FLAG(pitch_correction)},
// set a-v distance
- OPT_FLOAT("audio-delay", audio_delay, 0),
+ {"audio-delay", OPT_FLOAT(audio_delay)},
// ------------------------- codec/vfilter options --------------------
- OPT_SETTINGSLIST("af-defaults", af_defs, 0, &af_obj_list,
- .deprecation_message = "use --af + enable/disable flags"),
- OPT_SETTINGSLIST("af", af_settings, 0, &af_obj_list, ),
- OPT_SETTINGSLIST("vf-defaults", vf_defs, 0, &vf_obj_list,
- .deprecation_message = "use --vf + enable/disable flags"),
- OPT_SETTINGSLIST("vf", vf_settings, 0, &vf_obj_list, ),
+ {"af-defaults", OPT_SETTINGSLIST(af_defs, &af_obj_list),
+ .deprecation_message = "use --af + enable/disable flags"},
+ {"af", OPT_SETTINGSLIST(af_settings, &af_obj_list)},
+ {"vf-defaults", OPT_SETTINGSLIST(vf_defs, &vf_obj_list),
+ .deprecation_message = "use --vf + enable/disable flags"},
+ {"vf", OPT_SETTINGSLIST(vf_settings, &vf_obj_list)},
- OPT_SUBSTRUCT("", filter_opts, filter_conf, 0),
+ {"", OPT_SUBSTRUCT(filter_opts, filter_conf)},
- OPT_STRING("ad", audio_decoders, 0),
- OPT_STRING("vd", video_decoders, 0),
+ {"", OPT_SUBSTRUCT(dec_wrapper, dec_wrapper_conf)},
+ {"", OPT_SUBSTRUCT(vd_lavc_params, vd_lavc_conf)},
+ {"ad-lavc", OPT_SUBSTRUCT(ad_lavc_params, ad_lavc_conf)},
- OPT_STRING("audio-spdif", audio_spdif, 0),
-
- OPT_ASPECT("video-aspect-override", movie_aspect, UPDATE_IMGPAR | M_OPT_RANGE,
- .min = -1, .max = 10),
- OPT_CHOICE("video-aspect-method", aspect_method, UPDATE_IMGPAR,
- ({"bitstream", 1}, {"container", 2})),
-
- OPT_SUBSTRUCT("", vd_lavc_params, vd_lavc_conf, 0),
- OPT_SUBSTRUCT("ad-lavc", ad_lavc_params, ad_lavc_conf, 0),
-
- OPT_SUBSTRUCT("", demux_lavf, demux_lavf_conf, 0),
- OPT_SUBSTRUCT("demuxer-rawaudio", demux_rawaudio, demux_rawaudio_conf, 0),
- OPT_SUBSTRUCT("demuxer-rawvideo", demux_rawvideo, demux_rawvideo_conf, 0),
- OPT_SUBSTRUCT("demuxer-mkv", demux_mkv, demux_mkv_conf, 0),
- OPT_SUBSTRUCT("demuxer-cue", demux_cue, demux_cue_conf, 0),
+ {"", OPT_SUBSTRUCT(demux_lavf, demux_lavf_conf)},
+ {"demuxer-rawaudio", OPT_SUBSTRUCT(demux_rawaudio, demux_rawaudio_conf)},
+ {"demuxer-rawvideo", OPT_SUBSTRUCT(demux_rawvideo, demux_rawvideo_conf)},
+ {"demuxer-mkv", OPT_SUBSTRUCT(demux_mkv, demux_mkv_conf)},
+ {"demuxer-cue", OPT_SUBSTRUCT(demux_cue, demux_cue_conf)},
// ------------------------- subtitles options --------------------
- OPT_PATHLIST("sub-files", sub_name, M_OPT_FILE),
- OPT_CLI_ALIAS("sub-file", "sub-files-append"),
- OPT_PATHLIST("sub-file-paths", sub_paths, M_OPT_FILE),
- OPT_PATHLIST("audio-file-paths", audiofile_paths, M_OPT_FILE),
- OPT_PATHLIST("external-files", external_files, M_OPT_FILE),
- OPT_CLI_ALIAS("external-file", "external-files-append"),
- OPT_FLAG("autoload-files", autoload_files, 0),
- OPT_CHOICE("sub-auto", sub_auto, 0,
- ({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})),
- OPT_CHOICE("audio-file-auto", audiofile_auto, 0,
- ({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})),
+ {"sub-files", OPT_PATHLIST(sub_name), .flags = M_OPT_FILE},
+ {"sub-file", OPT_CLI_ALIAS("sub-files-append")},
+ {"sub-file-paths", OPT_PATHLIST(sub_paths), .flags = M_OPT_FILE},
+ {"audio-file-paths", OPT_PATHLIST(audiofile_paths), .flags = M_OPT_FILE},
+ {"external-files", OPT_PATHLIST(external_files), .flags = M_OPT_FILE},
+ {"external-file", OPT_CLI_ALIAS("external-files-append")},
+ {"autoload-files", OPT_FLAG(autoload_files)},
+ {"sub-auto", OPT_CHOICE(sub_auto,
+ {"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})},
+ {"audio-file-auto", OPT_CHOICE(audiofile_auto,
+ {"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})},
- OPT_SUBSTRUCT("", subs_rend, mp_subtitle_sub_opts, 0),
- OPT_SUBSTRUCT("", subs_filt, mp_sub_filter_opts, 0),
- OPT_SUBSTRUCT("", osd_rend, mp_osd_render_sub_opts, 0),
+ {"", OPT_SUBSTRUCT(subs_rend, mp_subtitle_sub_opts)},
+ {"", OPT_SUBSTRUCT(subs_filt, mp_sub_filter_opts)},
+ {"", OPT_SUBSTRUCT(osd_rend, mp_osd_render_sub_opts)},
- OPT_FLAG("osd-bar", osd_bar_visible, UPDATE_OSD),
+ {"osd-bar", OPT_FLAG(osd_bar_visible), .flags = UPDATE_OSD},
//---------------------- libao/libvo options ------------------------
- OPT_SUBSTRUCT("", ao_opts, ao_conf, 0),
- OPT_FLAG("audio-exclusive", audio_exclusive, UPDATE_AUDIO),
- OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
- OPT_FLAG("audio-stream-silence", audio_stream_silence, 0),
- OPT_FLOATRANGE("audio-wait-open", audio_wait_open, 0, 0, 60),
- OPT_CHOICE("force-window", force_vo, 0,
- ({"no", 0}, {"yes", 1}, {"immediate", 2})),
-
- OPT_FLOATRANGE("volume-max", softvol_max, 0, 100, 1000),
+ {"", OPT_SUBSTRUCT(ao_opts, ao_conf)},
+ {"audio-exclusive", OPT_FLAG(audio_exclusive), .flags = UPDATE_AUDIO},
+ {"audio-fallback-to-null", OPT_FLAG(ao_null_fallback)},
+ {"audio-stream-silence", OPT_FLAG(audio_stream_silence)},
+ {"audio-wait-open", OPT_FLOAT(audio_wait_open), M_RANGE(0, 60)},
+ {"force-window", OPT_CHOICE(force_vo,
+ {"no", 0}, {"yes", 1}, {"immediate", 2})},
+
+ {"volume-max", OPT_FLOAT(softvol_max), M_RANGE(100, 1000)},
// values <0 for volume and mute are legacy and ignored
- OPT_FLOATRANGE("volume", softvol_volume, UPDATE_VOL, -1, 1000),
- OPT_CHOICE("mute", softvol_mute, UPDATE_VOL,
- ({"no", 0},
- {"auto", 0},
- {"yes", 1})),
- OPT_CHOICE("replaygain", rgain_mode, UPDATE_VOL,
- ({"no", 0},
- {"track", 1},
- {"album", 2})),
- OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, UPDATE_VOL, -15, 15),
- OPT_FLAG("replaygain-clip", rgain_clip, UPDATE_VOL),
- OPT_FLOATRANGE("replaygain-fallback", rgain_fallback, UPDATE_VOL, -200, 60),
- OPT_CHOICE("gapless-audio", gapless_audio, 0,
- ({"no", 0},
- {"yes", 1},
- {"weak", -1})),
-
- OPT_STRING("title", wintitle, 0),
- OPT_STRING("force-media-title", media_title, 0),
- OPT_CHOICE_OR_INT("video-rotate", video_rotate, UPDATE_IMGPAR, 0, 359,
- ({"no", -1})),
-
- OPT_CHOICE_OR_INT("cursor-autohide", cursor_autohide_delay, 0,
- 0, 30000, ({"no", -1}, {"always", -2})),
- OPT_FLAG("cursor-autohide-fs-only", cursor_autohide_fs, 0),
- OPT_FLAG("stop-screensaver", stop_screensaver, UPDATE_SCREENSAVER),
-
- OPT_SUBSTRUCT("", video_equalizer, mp_csp_equalizer_conf, 0),
-
- OPT_FLAG("use-filedir-conf", use_filedir_conf, 0),
- OPT_CHOICE("osd-level", osd_level, 0,
- ({"0", 0}, {"1", 1}, {"2", 2}, {"3", 3})),
- OPT_CHOICE("osd-on-seek", osd_on_seek, 0,
- ({"no", 0},
- {"bar", 1},
- {"msg", 2},
- {"msg-bar", 3})),
- OPT_INTRANGE("osd-duration", osd_duration, 0, 0, 3600000),
- OPT_FLAG("osd-fractions", osd_fractions, 0),
-
- OPT_DOUBLE("sstep", step_sec, CONF_MIN, 0),
-
- OPT_CHOICE("framedrop", frame_dropping, 0,
- ({"no", 0},
- {"vo", 1},
- {"decoder", 2},
- {"decoder+vo", 3})),
- OPT_FLAG("video-latency-hacks", video_latency_hacks, 0),
-
- OPT_FLAG("untimed", untimed, 0),
-
- OPT_STRING("stream-dump", stream_dump, M_OPT_FILE),
-
- OPT_FLAG("stop-playback-on-init-failure", stop_playback_on_init_failure, 0),
-
- OPT_CHOICE_OR_INT("loop-playlist", loop_times, 0, 1, 10000,
- ({"no", 1},
- {"inf", -1}, {"yes", -1},
- {"force", -2})),
- OPT_CHOICE_OR_INT("loop-file", loop_file, 0, 0, 10000,
- ({"no", 0},
- {"yes", -1},
- {"inf", -1})),
- OPT_ALIAS("loop", "loop-file"),
-
- OPT_FLAG("resume-playback", position_resume, 0),
- OPT_FLAG("resume-playback-check-mtime", position_check_mtime, 0),
- OPT_FLAG("save-position-on-quit", position_save_on_quit, 0),
- OPT_FLAG("write-filename-in-watch-later-config", write_filename_in_watch_later_config, 0),
- OPT_FLAG("ignore-path-in-watch-later-config", ignore_path_in_watch_later_config, 0),
- OPT_STRING("watch-later-directory", watch_later_directory, M_OPT_FILE),
-
- OPT_FLAG("ordered-chapters", ordered_chapters, 0),
- OPT_STRING("ordered-chapters-files", ordered_chapters_files, M_OPT_FILE),
- OPT_INTRANGE("chapter-merge-threshold", chapter_merge_threshold, 0, 0, 10000),
-
- OPT_DOUBLE("chapter-seek-threshold", chapter_seek_threshold, 0),
-
- OPT_STRING("chapters-file", chapter_file, M_OPT_FILE),
-
- OPT_FLAG("merge-files", merge_files, 0),
+ {"volume", OPT_FLOAT(softvol_volume), .flags = UPDATE_VOL,
+ M_RANGE(-1, 1000)},
+ {"mute", OPT_CHOICE(softvol_mute,
+ {"no", 0},
+ {"auto", 0},
+ {"yes", 1}),
+ .flags = UPDATE_VOL},
+ {"replaygain", OPT_CHOICE(rgain_mode,
+ {"no", 0},
+ {"track", 1},
+ {"album", 2}),
+ .flags = UPDATE_VOL},
+ {"replaygain-preamp", OPT_FLOAT(rgain_preamp), .flags = UPDATE_VOL,
+ M_RANGE(-15, 15)},
+ {"replaygain-clip", OPT_FLAG(rgain_clip), .flags = UPDATE_VOL},
+ {"replaygain-fallback", OPT_FLOAT(rgain_fallback), .flags = UPDATE_VOL,
+ M_RANGE(-200, 60)},
+ {"gapless-audio", OPT_CHOICE(gapless_audio,
+ {"no", 0},
+ {"yes", 1},
+ {"weak", -1})},
+
+ {"title", OPT_STRING(wintitle)},
+ {"force-media-title", OPT_STRING(media_title)},
+
+ {"cursor-autohide", OPT_CHOICE(cursor_autohide_delay,
+ {"no", -1}, {"always", -2}), M_RANGE(0, 30000)},
+ {"cursor-autohide-fs-only", OPT_FLAG(cursor_autohide_fs)},
+ {"stop-screensaver", OPT_FLAG(stop_screensaver), .flags = UPDATE_SCREENSAVER},
+
+ {"", OPT_SUBSTRUCT(video_equalizer, mp_csp_equalizer_conf)},
+
+ {"use-filedir-conf", OPT_FLAG(use_filedir_conf)},
+ {"osd-level", OPT_CHOICE(osd_level,
+ {"0", 0}, {"1", 1}, {"2", 2}, {"3", 3})},
+ {"osd-on-seek", OPT_CHOICE(osd_on_seek,
+ {"no", 0},
+ {"bar", 1},
+ {"msg", 2},
+ {"msg-bar", 3})},
+ {"osd-duration", OPT_INT(osd_duration), M_RANGE(0, 3600000)},
+ {"osd-fractions", OPT_FLAG(osd_fractions)},
+
+ {"sstep", OPT_DOUBLE(step_sec), M_RANGE(0, DBL_MAX)},
+
+ {"framedrop", OPT_CHOICE(frame_dropping,
+ {"no", 0},
+ {"vo", 1},
+ {"decoder", 2},
+ {"decoder+vo", 3})},
+ {"video-latency-hacks", OPT_FLAG(video_latency_hacks)},
+
+ {"untimed", OPT_FLAG(untimed)},
+
+ {"stream-dump", OPT_STRING(stream_dump), .flags = M_OPT_FILE},
+
+ {"stop-playback-on-init-failure", OPT_FLAG(stop_playback_on_init_failure)},
+
+ {"loop-playlist", OPT_CHOICE(loop_times,
+ {"no", 1},
+ {"inf", -1}, {"yes", -1},
+ {"force", -2}),
+ M_RANGE(1, 10000)},
+ {"loop-file", OPT_CHOICE(loop_file,
+ {"no", 0},
+ {"yes", -1},
+ {"inf", -1}),
+ M_RANGE(0, 10000)},
+ {"loop", OPT_ALIAS("loop-file")},
+
+ {"resume-playback", OPT_FLAG(position_resume)},
+ {"resume-playback-check-mtime", OPT_FLAG(position_check_mtime)},
+ {"save-position-on-quit", OPT_FLAG(position_save_on_quit)},
+ {"write-filename-in-watch-later-config",
+ OPT_FLAG(write_filename_in_watch_later_config)},
+ {"ignore-path-in-watch-later-config",
+ OPT_FLAG(ignore_path_in_watch_later_config)},
+ {"watch-later-directory", OPT_STRING(watch_later_directory),
+ .flags = M_OPT_FILE},
+
+ {"ordered-chapters", OPT_FLAG(ordered_chapters)},
+ {"ordered-chapters-files", OPT_STRING(ordered_chapters_files),
+ .flags = M_OPT_FILE},
+ {"chapter-merge-threshold", OPT_INT(chapter_merge_threshold),
+ M_RANGE(0, 10000)},
+
+ {"chapter-seek-threshold", OPT_DOUBLE(chapter_seek_threshold)},
+
+ {"chapters-file", OPT_STRING(chapter_file), .flags = M_OPT_FILE},
+
+ {"merge-files", OPT_FLAG(merge_files)},
// a-v sync stuff:
- OPT_FLAG("correct-pts", correct_pts, 0),
- OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
- OPT_CHOICE("video-sync", video_sync, 0,
- ({"audio", VS_DEFAULT},
- {"display-resample", VS_DISP_RESAMPLE},
- {"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
- {"display-resample-desync", VS_DISP_RESAMPLE_NONE},
- {"display-adrop", VS_DISP_ADROP},
- {"display-vdrop", VS_DISP_VDROP},
- {"display-desync", VS_DISP_NONE},
- {"desync", VS_NONE})),
- OPT_DOUBLE("video-sync-max-video-change", sync_max_video_change,
- M_OPT_MIN, .min = 0),
- OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
- M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
- OPT_DOUBLE("video-sync-adrop-size", sync_audio_drop_size,
- M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
- OPT_CHOICE("hr-seek", hr_seek, 0,
- ({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
- OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
- OPT_FLAG("hr-seek-framedrop", hr_seek_framedrop, 0),
- OPT_CHOICE_OR_INT("autosync", autosync, 0, 0, 10000,
- ({"no", -1})),
-
- OPT_CHOICE("term-osd", term_osd, 0,
- ({"force", 1},
- {"auto", 2},
- {"no", 0})),
-
- OPT_FLAG("term-osd-bar", term_osd_bar, 0),
- OPT_STRING("term-osd-bar-chars", term_osd_bar_chars, 0),
-
- OPT_STRING("term-playing-msg", playing_msg, 0),
- OPT_STRING("osd-playing-msg", osd_playing_msg, 0),
- OPT_STRING("term-status-msg", status_msg, 0),
- OPT_STRING("osd-status-msg", osd_status_msg, 0),
- OPT_STRING("osd-msg1", osd_msg[0], 0),
- OPT_STRING("osd-msg2", osd_msg[1], 0),
- OPT_STRING("osd-msg3", osd_msg[2], 0),
-
- OPT_FLAG("video-osd", video_osd, 0),
-
- OPT_CHOICE("idle", player_idle_mode, 0,
- ({"no", 0},
- {"once", 1},
- {"yes", 2})),
-
- OPT_FLAG("input-terminal", consolecontrols, UPDATE_TERM),
-
- OPT_STRING("input-file", input_file, M_OPT_FILE,
- .deprecation_message = "use --input-ipc-server"),
- OPT_STRING("input-ipc-server", ipc_path, M_OPT_FILE),
-
- OPT_SUBSTRUCT("screenshot", screenshot_image_opts, screenshot_conf, 0),
- OPT_STRING("screenshot-template", screenshot_template, 0),
- OPT_STRING("screenshot-directory", screenshot_directory, M_OPT_FILE),
-
- OPT_STRING("record-file", record_file, M_OPT_FILE, .deprecation_message =
- "use --stream-record or the dump-cache command"),
-
- OPT_SUBSTRUCT("", resample_opts, resample_conf, 0),
-
- OPT_SUBSTRUCT("", input_opts, input_config, 0),
-
- OPT_SUBSTRUCT("", vo, vo_sub_opts, 0),
- OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
- OPT_SUBSTRUCT("", demux_cache_opts, demux_cache_conf, 0),
- OPT_SUBSTRUCT("", stream_opts, stream_conf, 0),
-
- OPT_SUBSTRUCT("", gl_video_opts, gl_video_conf, 0),
- OPT_SUBSTRUCT("", spirv_opts, spirv_conf, 0),
+ {"initial-audio-sync", OPT_FLAG(initial_audio_sync)},
+ {"video-sync", OPT_CHOICE(video_sync,
+ {"audio", VS_DEFAULT},
+ {"display-resample", VS_DISP_RESAMPLE},
+ {"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
+ {"display-resample-desync", VS_DISP_RESAMPLE_NONE},
+ {"display-adrop", VS_DISP_ADROP},
+ {"display-vdrop", VS_DISP_VDROP},
+ {"display-desync", VS_DISP_NONE},
+ {"desync", VS_NONE})},
+ {"video-sync-max-video-change", OPT_DOUBLE(sync_max_video_change),
+ M_RANGE(0, DBL_MAX)},
+ {"video-sync-max-audio-change", OPT_DOUBLE(sync_max_audio_change),
+ M_RANGE(0, 1)},
+ {"video-sync-adrop-size", OPT_DOUBLE(sync_audio_drop_size),
+ M_RANGE(0, 1)},
+ {"hr-seek", OPT_CHOICE(hr_seek,
+ {"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1}, {"default", 2})},
+ {"hr-seek-demuxer-offset", OPT_FLOAT(hr_seek_demuxer_offset)},
+ {"hr-seek-framedrop", OPT_FLAG(hr_seek_framedrop)},
+ {"autosync", OPT_CHOICE(autosync, {"no", -1}), M_RANGE(0, 10000)},
+
+ {"term-osd", OPT_CHOICE(term_osd,
+ {"force", 1}, {"auto", 2}, {"no", 0})},
+
+ {"term-osd-bar", OPT_FLAG(term_osd_bar)},
+ {"term-osd-bar-chars", OPT_STRING(term_osd_bar_chars)},
+
+ {"term-playing-msg", OPT_STRING(playing_msg)},
+ {"osd-playing-msg", OPT_STRING(osd_playing_msg)},
+ {"term-status-msg", OPT_STRING(status_msg)},
+ {"osd-status-msg", OPT_STRING(osd_status_msg)},
+ {"osd-msg1", OPT_STRING(osd_msg[0])},
+ {"osd-msg2", OPT_STRING(osd_msg[1])},
+ {"osd-msg3", OPT_STRING(osd_msg[2])},
+
+ {"video-osd", OPT_FLAG(video_osd)},
+
+ {"idle", OPT_CHOICE(player_idle_mode,
+ {"no", 0}, {"once", 1}, {"yes", 2})},
+
+ {"input-terminal", OPT_FLAG(consolecontrols), .flags = UPDATE_TERM},
+
+ {"input-ipc-server", OPT_STRING(ipc_path), .flags = M_OPT_FILE},
+
+ {"screenshot", OPT_SUBSTRUCT(screenshot_image_opts, screenshot_conf)},
+ {"screenshot-template", OPT_STRING(screenshot_template)},
+ {"screenshot-directory", OPT_STRING(screenshot_directory),
+ .flags = M_OPT_FILE},
+
+ {"record-file", OPT_STRING(record_file), .flags = M_OPT_FILE,
+ .deprecation_message = "use --stream-record or the dump-cache command"},
+
+ {"", OPT_SUBSTRUCT(resample_opts, resample_conf)},
+
+ {"", OPT_SUBSTRUCT(input_opts, input_config)},
+
+ {"", OPT_SUBSTRUCT(vo, vo_sub_opts)},
+ {"", OPT_SUBSTRUCT(demux_opts, demux_conf)},
+ {"", OPT_SUBSTRUCT(demux_cache_opts, demux_cache_conf)},
+ {"", OPT_SUBSTRUCT(stream_opts, stream_conf)},
+
+ {"", OPT_SUBSTRUCT(gl_video_opts, gl_video_conf)},
+ {"", OPT_SUBSTRUCT(spirv_opts, spirv_conf)},
#if HAVE_GL
- OPT_SUBSTRUCT("", opengl_opts, opengl_conf, 0),
+ {"", OPT_SUBSTRUCT(opengl_opts, opengl_conf)},
#endif
#if HAVE_VULKAN
- OPT_SUBSTRUCT("", vulkan_opts, vulkan_conf, 0),
+ {"", OPT_SUBSTRUCT(vulkan_opts, vulkan_conf)},
#endif
#if HAVE_D3D11
- OPT_SUBSTRUCT("", d3d11_opts, d3d11_conf, 0),
+ {"", OPT_SUBSTRUCT(d3d11_opts, d3d11_conf)},
#if HAVE_D3D_HWACCEL
- OPT_SUBSTRUCT("", d3d11va_opts, d3d11va_conf, 0),
+ {"", OPT_SUBSTRUCT(d3d11va_opts, d3d11va_conf)},
#endif
#endif
#if HAVE_EGL_ANGLE_WIN32
- OPT_SUBSTRUCT("", angle_opts, angle_conf, 0),
+ {"", OPT_SUBSTRUCT(angle_opts, angle_conf)},
#endif
#if HAVE_GL_COCOA
- OPT_SUBSTRUCT("", cocoa_opts, cocoa_conf, 0),
+ {"", OPT_SUBSTRUCT(cocoa_opts, cocoa_conf)},
#endif
#if HAVE_COCOA
- OPT_SUBSTRUCT("", macos_opts, macos_conf, 0),
+ {"", OPT_SUBSTRUCT(macos_opts, macos_conf)},
#endif
#if HAVE_EGL_ANDROID
- OPT_SUBSTRUCT("", android_opts, android_conf, 0),
+ {"", OPT_SUBSTRUCT(android_opts, android_conf)},
#endif
#if HAVE_WAYLAND
- OPT_SUBSTRUCT("", wayland_opts, wayland_conf, 0),
+ {"", OPT_SUBSTRUCT(wayland_opts, wayland_conf)},
#endif
#if HAVE_GL_WIN32
- OPT_CHOICE("opengl-dwmflush", wingl_dwm_flush, 0,
- ({"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})),
+ {"opengl-dwmflush", OPT_CHOICE(wingl_dwm_flush,
+ {"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})},
#endif
#if HAVE_CUDA_HWACCEL
- OPT_CHOICE_OR_INT("cuda-decode-device", cuda_device, 0,
- 0, INT_MAX, ({"auto", -1})),
+ {"cuda-decode-device", OPT_CHOICE(cuda_device, {"auto", -1}),
+ M_RANGE(0, INT_MAX)},
#endif
#if HAVE_VAAPI
- OPT_SUBSTRUCT("vaapi", vaapi_opts, vaapi_conf, 0),
+ {"vaapi", OPT_SUBSTRUCT(vaapi_opts, vaapi_conf)},
#endif
- OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0),
+ {"sws", OPT_SUBSTRUCT(sws_opts, sws_conf)},
#if HAVE_ZIMG
- OPT_SUBSTRUCT("zimg", zimg_opts, zimg_conf, 0),
+ {"zimg", OPT_SUBSTRUCT(zimg_opts, zimg_conf)},
#endif
- OPT_SUBSTRUCT("", encode_opts, encode_config, 0),
-
- OPT_REMOVED("a52drc", "use --ad-lavc-ac3drc=level"),
- OPT_REMOVED("afm", "use --ad=..."),
- OPT_REPLACED("aspect", "video-aspect-override"),
- OPT_REMOVED("ass-bottom-margin", "use --vf=sub=bottom:top"),
- OPT_REPLACED("ass", "sub-ass"),
- OPT_REPLACED("audiofile", "audio-file"),
- OPT_REMOVED("benchmark", "use --untimed (no stats)"),
- 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"),
- OPT_REMOVED("dumpstream", "use --stream-dump=<filename>"),
- OPT_REPLACED("dvdangle", "dvd-angle"),
- OPT_REPLACED("endpos", "length"),
- OPT_REPLACED("font", "osd-font"),
- OPT_REPLACED("forcedsubsonly", "sub-forced-only"),
- OPT_REPLACED("format", "audio-format"),
- OPT_REMOVED("hardframedrop", NULL),
- OPT_REMOVED("identify", "use TOOLS/mpv_identify.sh"),
- OPT_REMOVED("lavdopts", "use --vd-lavc-..."),
- OPT_REMOVED("lavfdopts", "use --demuxer-lavf-..."),
- OPT_REPLACED("lua", "script"),
- OPT_REPLACED("lua-opts", "script-opts"),
- OPT_REMOVED("mixer-channel", "use AO suboptions (alsa, oss)"),
- OPT_REMOVED("mixer", "use AO suboptions (alsa, oss)"),
- OPT_REPLACED("mouse-movements", "input-cursor"),
- OPT_REPLACED("msgcolor", "msg-color"),
- OPT_REMOVED("msglevel", "use --msg-level (changed semantics)"),
- OPT_REPLACED("msgmodule", "msg-module"),
- OPT_REPLACED("name", "x11-name"),
- OPT_REPLACED("noar", "no-input-appleremote"),
- OPT_REPLACED("noautosub", "no-sub-auto"),
- OPT_REPLACED("noconsolecontrols", "no-input-terminal"),
- OPT_REPLACED("nosound", "no-audio"),
- OPT_REPLACED("osdlevel", "osd-level"),
- OPT_REMOVED("panscanrange", "use --video-zoom, --video-pan-x/y"),
- OPT_REPLACED("playing-msg", "term-playing-msg"),
- OPT_REMOVED("pp", NULL),
- OPT_REMOVED("pphelp", NULL),
- OPT_REMOVED("rawaudio", "use --demuxer-rawaudio-..."),
- OPT_REMOVED("rawvideo", "use --demuxer-rawvideo-..."),
- OPT_REPLACED("spugauss", "sub-gauss"),
- OPT_REPLACED("srate", "audio-samplerate"),
- OPT_REPLACED("ss", "start"),
- OPT_REPLACED("stop-xscreensaver", "stop-screensaver"),
- OPT_REPLACED("sub-fuzziness", "sub-auto"),
- OPT_REPLACED("subcp", "sub-codepage"),
- OPT_REPLACED("subdelay", "sub-delay"),
- OPT_REPLACED("subfile", "sub-file"),
- OPT_REPLACED("subfont-text-scale", "sub-scale"),
- OPT_REPLACED("subfont", "sub-text-font"),
- OPT_REPLACED("subfps", "sub-fps"),
- OPT_REPLACED("subpos", "sub-pos"),
- OPT_REPLACED("tvscan", "tv-scan"),
- OPT_REMOVED("use-filename-title", "use --title='${filename}'"),
- OPT_REMOVED("vc", "use --vd=..., --hwdec=..."),
- OPT_REMOVED("vobsub", "use --sub-file (pass the .idx file)"),
- OPT_REMOVED("xineramascreen", "use --screen (different values)"),
- OPT_REMOVED("xy", "use --autofit"),
- OPT_REMOVED("zoom", "Inverse available as ``--video-unscaled"),
- OPT_REPLACED("media-keys", "input-media-keys"),
- OPT_REPLACED("right-alt-gr", "input-right-alt-gr"),
- OPT_REPLACED("autosub", "sub-auto"),
- OPT_REPLACED("autosub-match", "sub-auto"),
- OPT_REPLACED("status-msg", "term-status-msg"),
- OPT_REPLACED("idx", "index"),
- OPT_REPLACED("forceidx", "index"),
- OPT_REMOVED("cache-pause-below", "for 'no', use --no-cache-pause"),
- OPT_REMOVED("no-cache-pause-below", "use --no-cache-pause"),
- OPT_REMOVED("volstep", "edit input.conf directly instead"),
- OPT_REMOVED("fixed-vo", "--fixed-vo=yes is now the default"),
- OPT_REPLACED("mkv-subtitle-preroll", "demuxer-mkv-subtitle-preroll"),
- OPT_REPLACED("ass-use-margins", "sub-use-margins"),
- OPT_REPLACED("media-title", "force-media-title"),
- OPT_REPLACED("input-unix-socket", "input-ipc-server"),
- OPT_REPLACED("softvol-max", "volume-max"),
- OPT_REMOVED("bluray-angle", "this didn't do anything for a few releases"),
- OPT_REPLACED("sub-text-font", "sub-font"),
- OPT_REPLACED("sub-text-font-size", "sub-font-size"),
- OPT_REPLACED("sub-text-color", "sub-color"),
- OPT_REPLACED("sub-text-border-color", "sub-border-color"),
- OPT_REPLACED("sub-text-shadow-color", "sub-shadow-color"),
- OPT_REPLACED("sub-text-back-color", "sub-back-color"),
- OPT_REPLACED("sub-text-border-size", "sub-border-size"),
- OPT_REPLACED("sub-text-shadow-offset", "sub-shadow-offset"),
- OPT_REPLACED("sub-text-spacing", "sub-spacing"),
- OPT_REPLACED("sub-text-margin-x", "sub-margin-x"),
- OPT_REPLACED("sub-text-margin-y", "sub-margin-y"),
- OPT_REPLACED("sub-text-align-x", "sub-align-x"),
- OPT_REPLACED("sub-text-align-y", "sub-align-y"),
- OPT_REPLACED("sub-text-blur", "sub-blur"),
- OPT_REPLACED("sub-text-bold", "sub-bold"),
- OPT_REPLACED("sub-text-italic", "sub-italic"),
- OPT_REPLACED("ass-line-spacing", "sub-ass-line-spacing"),
- OPT_REPLACED("ass-force-margins", "sub-ass-force-margins"),
- OPT_REPLACED("ass-vsfilter-aspect-compat", "sub-ass-vsfilter-aspect-compat"),
- OPT_REPLACED("ass-vsfilter-color-compat", "sub-ass-vsfilter-color-compat"),
- OPT_REPLACED("ass-vsfilter-blur-compat", "sub-ass-vsfilter-blur-compat"),
- OPT_REPLACED("ass-force-style", "sub-ass-force-style"),
- OPT_REPLACED("ass-styles", "sub-ass-styles"),
- OPT_REPLACED("ass-hinting", "sub-ass-hinting"),
- OPT_REPLACED("ass-shaper", "sub-ass-shaper"),
- OPT_REPLACED("ass-style-override", "sub-ass-style-override"),
- OPT_REPLACED("ass-scale-with-window", "sub-ass-scale-with-window"),
- OPT_REPLACED("sub-ass-style-override", "sub-ass-override"),
- OPT_REMOVED("fs-black-out-screens", NULL),
- OPT_REPLACED("sub-paths", "sub-file-paths"),
- OPT_REMOVED("heartbeat-cmd", "use Lua scripting instead"),
- OPT_REMOVED("no-ometadata", "use --no-ocopy-metadata"),
- OPT_REMOVED("video-stereo-mode", "removed, try --vf=stereo3d"),
- OPT_REMOVED("chapter", "use '--start=#123' '--end=#124' (for chapter 123)"),
- OPT_REPLACED("video-aspect", "video-aspect-override"),
- OPT_REPLACED("display-fps", "override-display-fps"),
+ {"", OPT_SUBSTRUCT(encode_opts, encode_config)},
+
+ {"a52drc", OPT_REMOVED("use --ad-lavc-ac3drc=level")},
+ {"afm", OPT_REMOVED("use --ad=...")},
+ {"aspect", OPT_REPLACED("video-aspect-override")},
+ {"ass-bottom-margin", OPT_REMOVED("use --vf=sub=bottom:top")},
+ {"ass", OPT_REPLACED("sub-ass")},
+ {"audiofile", OPT_REPLACED("audio-file")},
+ {"benchmark", OPT_REMOVED("use --untimed (no stats)")},
+ {"capture", OPT_REMOVED(NULL)},
+ {"stream-capture", OPT_REMOVED(NULL)},
+ {"channels", OPT_REMOVED("use --audio-channels (changed semantics)")},
+ {"cursor-autohide-delay", OPT_REPLACED("cursor-autohide")},
+ {"delay", OPT_REPLACED("audio-delay")},
+ {"dumpstream", OPT_REMOVED("use --stream-dump=<filename>")},
+ {"dvdangle", OPT_REPLACED("dvd-angle")},
+ {"endpos", OPT_REPLACED("length")},
+ {"font", OPT_REPLACED("osd-font")},
+ {"forcedsubsonly", OPT_REPLACED("sub-forced-only")},
+ {"format", OPT_REPLACED("audio-format")},
+ {"hardframedrop", OPT_REMOVED(NULL)},
+ {"identify", OPT_REMOVED("use TOOLS/mpv_identify.sh")},
+ {"lavdopts", OPT_REMOVED("use --vd-lavc-...")},
+ {"lavfdopts", OPT_REMOVED("use --demuxer-lavf-...")},
+ {"lua", OPT_REPLACED("script")},
+ {"lua-opts", OPT_REPLACED("script-opts")},
+ {"mixer-channel", OPT_REMOVED("use AO suboptions (alsa, oss)")},
+ {"mixer", OPT_REMOVED("use AO suboptions (alsa, oss)")},
+ {"mouse-movements", OPT_REPLACED("input-cursor")},
+ {"msgcolor", OPT_REPLACED("msg-color")},
+ {"msglevel", OPT_REMOVED("use --msg-level (changed semantics)")},
+ {"msgmodule", OPT_REPLACED("msg-module")},
+ {"name", OPT_REPLACED("x11-name")},
+ {"noar", OPT_REPLACED("no-input-appleremote")},
+ {"noautosub", OPT_REPLACED("no-sub-auto")},
+ {"noconsolecontrols", OPT_REPLACED("no-input-terminal")},
+ {"nosound", OPT_REPLACED("no-audio")},
+ {"osdlevel", OPT_REPLACED("osd-level")},
+ {"panscanrange", OPT_REMOVED("use --video-zoom, --video-pan-x/y")},
+ {"playing-msg", OPT_REPLACED("term-playing-msg")},
+ {"pp", OPT_REMOVED(NULL)},
+ {"pphelp", OPT_REMOVED(NULL)},
+ {"rawaudio", OPT_REMOVED("use --demuxer-rawaudio-...")},
+ {"rawvideo", OPT_REMOVED("use --demuxer-rawvideo-...")},
+ {"spugauss", OPT_REPLACED("sub-gauss")},
+ {"srate", OPT_REPLACED("audio-samplerate")},
+ {"ss", OPT_REPLACED("start")},
+ {"stop-xscreensaver", OPT_REPLACED("stop-screensaver")},
+ {"sub-fuzziness", OPT_REPLACED("sub-auto")},
+ {"subcp", OPT_REPLACED("sub-codepage")},
+ {"subdelay", OPT_REPLACED("sub-delay")},
+ {"subfile", OPT_REPLACED("sub-file")},
+ {"subfont-text-scale", OPT_REPLACED("sub-scale")},
+ {"subfont", OPT_REPLACED("sub-text-font")},
+ {"subfps", OPT_REPLACED("sub-fps")},
+ {"subpos", OPT_REPLACED("sub-pos")},
+ {"tvscan", OPT_REPLACED("tv-scan")},
+ {"use-filename-title", OPT_REMOVED("use --title='${filename}'")},
+ {"vc", OPT_REMOVED("use --vd=..., --hwdec=...")},
+ {"vobsub", OPT_REMOVED("use --sub-file (pass the .idx file)")},
+ {"xineramascreen", OPT_REMOVED("use --screen (different values)")},
+ {"xy", OPT_REMOVED("use --autofit")},
+ {"zoom", OPT_REMOVED("Inverse available as ``--video-unscaled")},
+ {"media-keys", OPT_REPLACED("input-media-keys")},
+ {"right-alt-gr", OPT_REPLACED("input-right-alt-gr")},
+ {"autosub", OPT_REPLACED("sub-auto")},
+ {"autosub-match", OPT_REPLACED("sub-auto")},
+ {"status-msg", OPT_REPLACED("term-status-msg")},
+ {"idx", OPT_REPLACED("index")},
+ {"forceidx", OPT_REPLACED("index")},
+ {"cache-pause-below", OPT_REMOVED("for 'no', use --no-cache-pause")},
+ {"no-cache-pause-below", OPT_REMOVED("use --no-cache-pause")},
+ {"volstep", OPT_REMOVED("edit input.conf directly instead")},
+ {"fixed-vo", OPT_REMOVED("--fixed-vo=yes is now the default")},
+ {"mkv-subtitle-preroll", OPT_REPLACED("demuxer-mkv-subtitle-preroll")},
+ {"ass-use-margins", OPT_REPLACED("sub-use-margins")},
+ {"media-title", OPT_REPLACED("force-media-title")},
+ {"input-unix-socket", OPT_REPLACED("input-ipc-server")},
+ {"softvol-max", OPT_REPLACED("volume-max")},
+ {"bluray-angle", OPT_REMOVED("this didn't do anything for a few releases")},
+ {"sub-text-font", OPT_REPLACED("sub-font")},
+ {"sub-text-font-size", OPT_REPLACED("sub-font-size")},
+ {"sub-text-color", OPT_REPLACED("sub-color")},
+ {"sub-text-border-color", OPT_REPLACED("sub-border-color")},
+ {"sub-text-shadow-color", OPT_REPLACED("sub-shadow-color")},
+ {"sub-text-back-color", OPT_REPLACED("sub-back-color")},
+ {"sub-text-border-size", OPT_REPLACED("sub-border-size")},
+ {"sub-text-shadow-offset", OPT_REPLACED("sub-shadow-offset")},
+ {"sub-text-spacing", OPT_REPLACED("sub-spacing")},
+ {"sub-text-margin-x", OPT_REPLACED("sub-margin-x")},
+ {"sub-text-margin-y", OPT_REPLACED("sub-margin-y")},
+ {"sub-text-align-x", OPT_REPLACED("sub-align-x")},
+ {"sub-text-align-y", OPT_REPLACED("sub-align-y")},
+ {"sub-text-blur", OPT_REPLACED("sub-blur")},
+ {"sub-text-bold", OPT_REPLACED("sub-bold")},
+ {"sub-text-italic", OPT_REPLACED("sub-italic")},
+ {"ass-line-spacing", OPT_REPLACED("sub-ass-line-spacing")},
+ {"ass-force-margins", OPT_REPLACED("sub-ass-force-margins")},
+ {"ass-vsfilter-aspect-compat", OPT_REPLACED("sub-ass-vsfilter-aspect-compat")},
+ {"ass-vsfilter-color-compat", OPT_REPLACED("sub-ass-vsfilter-color-compat")},
+ {"ass-vsfilter-blur-compat", OPT_REPLACED("sub-ass-vsfilter-blur-compat")},
+ {"ass-force-style", OPT_REPLACED("sub-ass-force-style")},
+ {"ass-styles", OPT_REPLACED("sub-ass-styles")},
+ {"ass-hinting", OPT_REPLACED("sub-ass-hinting")},
+ {"ass-shaper", OPT_REPLACED("sub-ass-shaper")},
+ {"ass-style-override", OPT_REPLACED("sub-ass-style-override")},
+ {"ass-scale-with-window", OPT_REPLACED("sub-ass-scale-with-window")},
+ {"sub-ass-style-override", OPT_REPLACED("sub-ass-override")},
+ {"fs-black-out-screens", OPT_REMOVED(NULL)},
+ {"sub-paths", OPT_REPLACED("sub-file-paths")},
+ {"heartbeat-cmd", OPT_REMOVED("use Lua scripting instead")},
+ {"no-ometadata", OPT_REMOVED("use --no-ocopy-metadata")},
+ {"video-stereo-mode", OPT_REMOVED("removed, try --vf=stereo3d")},
+ {"chapter", OPT_REMOVED("use '--start=#123' '--end=#124' (for chapter 123)")},
+ {"video-aspect", OPT_REPLACED("video-aspect-override")},
+ {"display-fps", OPT_REPLACED("override-display-fps")},
{0}
};
@@ -921,8 +918,6 @@ static const m_option_t mp_opts[] = {
static const struct MPOpts mp_default_opts = {
.use_terminal = 1,
.msg_color = 1,
- .audio_decoders = NULL,
- .video_decoders = NULL,
.softvol_max = 130,
.softvol_volume = 100,
.softvol_mute = 0,
@@ -947,6 +942,7 @@ static const struct MPOpts mp_default_opts = {
.ordered_chapters = 1,
.chapter_merge_threshold = 100,
.chapter_seek_threshold = 5.0,
+ .hr_seek = 2,
.hr_seek_framedrop = 1,
.sync_max_video_change = 1,
.sync_max_audio_change = 0.125,
@@ -963,7 +959,6 @@ static const struct MPOpts mp_default_opts = {
.ab_loop_count = -1,
.edition_id = -1,
.default_max_pts_correction = -1,
- .correct_pts = 1,
.initial_audio_sync = 1,
.frame_dropping = 1,
.term_osd = 2,
@@ -986,15 +981,11 @@ static const struct MPOpts mp_default_opts = {
.audio_output_format = 0, // AF_FORMAT_UNKNOWN
.playback_speed = 1.,
.pitch_correction = 1,
- .movie_aspect = -1.,
- .aspect_method = 2,
.sub_auto = 0,
.audiofile_auto = -1,
.osd_bar_visible = 1,
.screenshot_template = "mpv-shot%n",
.play_dir = 1,
- .video_reverse_size = 1 * 1024 * 1024 * 1024,
- .audio_reverse_size = 64 * 1024 * 1024,
.audio_output_channels = {
.set = 1,
diff --git a/options/options.h b/options/options.h
index cfb6f44813..1eed3184b7 100644
--- a/options/options.h
+++ b/options/options.h
@@ -13,7 +13,7 @@ typedef struct mp_vo_opts {
int snap_window;
int ontop;
int ontop_level;
- int fullscreen;
+ bool fullscreen;
int border;
int fit_border;
int all_workspaces;
@@ -172,12 +172,6 @@ typedef struct MPOpts {
int cursor_autohide_delay;
int cursor_autohide_fs;
- int video_rotate;
-
- char *audio_decoders;
- char *video_decoders;
- char *audio_spdif;
-
struct mp_subtitle_opts *subs_rend;
struct mp_sub_filter_opts *subs_filt;
struct mp_osd_render_opts *osd_rend;
@@ -207,7 +201,6 @@ typedef struct MPOpts {
int use_filedir_conf;
int hls_bitrate;
int edition_id;
- int correct_pts;
int initial_audio_sync;
int video_sync;
double sync_max_video_change;
@@ -266,8 +259,6 @@ typedef struct MPOpts {
int prefetch_open;
char *audio_demuxer_name;
char *sub_demuxer_name;
- int64_t video_reverse_size;
- int64_t audio_reverse_size;
int cache_pause;
int cache_pause_initial;
@@ -277,7 +268,6 @@ typedef struct MPOpts {
char *screenshot_template;
char *screenshot_directory;
- double force_fps;
int index_mode;
struct m_channels audio_output_channels;
@@ -288,8 +278,7 @@ typedef struct MPOpts {
struct m_obj_settings *vf_settings, *vf_defs;
struct m_obj_settings *af_settings, *af_defs;
struct filter_opts *filter_opts;
- float movie_aspect;
- int aspect_method;
+ struct dec_wrapper_opts *dec_wrapper;
char **sub_name;
char **sub_paths;
char **audiofile_paths;
@@ -330,7 +319,6 @@ typedef struct MPOpts {
struct encode_opts *encode_opts;
char *ipc_path;
- char *input_file;
int wingl_dwm_flush;
@@ -373,6 +361,7 @@ extern const struct m_sub_options mp_osd_render_sub_opts;
extern const struct m_sub_options filter_conf;
extern const struct m_sub_options resample_conf;
extern const struct m_sub_options stream_conf;
+extern const struct m_sub_options dec_wrapper_conf;
extern const struct m_sub_options mp_opt_root;
#endif
diff --git a/options/parse_commandline.c b/options/parse_commandline.c
index 783f213053..f11cbaf038 100644
--- a/options/parse_commandline.c
+++ b/options/parse_commandline.c
@@ -29,7 +29,7 @@
#include "common/msg.h"
#include "common/msg_control.h"
#include "m_option.h"
-#include "m_config.h"
+#include "m_config_frontend.h"
#include "options.h"
#include "common/playlist.h"
#include "parse_commandline.h"
@@ -93,9 +93,6 @@ static bool split_opt(struct parse_state *p)
}
p->param = bstr0(p->argv[0]);
p->argv++;
- MP_WARN(p, "The legacy option syntax ('-%.*s value') is deprecated "
- "and dangerous.\nPlease use '--%.*s=value'.\n",
- BSTR_P(p->arg), BSTR_P(p->arg));
}
return true;
diff --git a/options/parse_configfile.h b/options/parse_configfile.h
index f023c20e85..f603125822 100644
--- a/options/parse_configfile.h
+++ b/options/parse_configfile.h
@@ -18,7 +18,7 @@
#ifndef MPLAYER_PARSER_CFG_H
#define MPLAYER_PARSER_CFG_H
-#include "m_config.h"
+#include "m_config_frontend.h"
int m_config_parse_config_file(m_config_t* config, const char *conffile,
char *initial_section, int flags);
diff --git a/osdep/atomic.h b/osdep/atomic.h
index 0c4de4b578..774924afa2 100644
--- a/osdep/atomic.h
+++ b/osdep/atomic.h
@@ -48,8 +48,7 @@ typedef struct { uint64_t v; } mp_atomic_uint64;
#define memory_order_relaxed 1
#define memory_order_seq_cst 2
-
-#define atomic_load_explicit(p, e) atomic_load(p)
+#define memory_order_acq_rel 3
#include <pthread.h>
@@ -99,6 +98,12 @@ extern pthread_mutex_t mp_atomic_mutex;
pthread_mutex_unlock(&mp_atomic_mutex); \
res_; })
+#define atomic_load_explicit(a, b) \
+ atomic_load(a)
+
+#define atomic_exchange_explicit(a, b, c) \
+ atomic_exchange(a, b)
+
#endif /* else HAVE_STDATOMIC */
#endif
diff --git a/osdep/macos/mpv_helper.swift b/osdep/macos/mpv_helper.swift
index 00c34924e3..d01694d736 100644
--- a/osdep/macos/mpv_helper.swift
+++ b/osdep/macos/mpv_helper.swift
@@ -81,7 +81,7 @@ class MPVHelper: LogHelper {
}
func setConfigProperty(fullscreen: Bool) {
- optsPtr.pointee.fullscreen = Int32(fullscreen)
+ optsPtr.pointee.fullscreen = fullscreen
m_config_cache_write_opt(optsCachePtr, UnsafeMutableRawPointer(&optsPtr.pointee.fullscreen))
}
diff --git a/osdep/macos/remote_command_center.swift b/osdep/macos/remote_command_center.swift
index b08a726183..a5dd662737 100644
--- a/osdep/macos/remote_command_center.swift
+++ b/osdep/macos/remote_command_center.swift
@@ -87,14 +87,10 @@ class RemoteCommandCenter: NSObject {
MPRemoteCommandCenter.shared().bookmarkCommand,
]
- let application: Application
-
var mpInfoCenter: MPNowPlayingInfoCenter { get { return MPNowPlayingInfoCenter.default() } }
var isPaused: Bool = false { didSet { updatePlaybackState() } }
- @objc init(app: Application) {
- application = app
-
+ @objc override init() {
super.init()
for cmd in disabledCommands {
@@ -110,8 +106,10 @@ class RemoteCommandCenter: NSObject {
}
}
- if let icon = application.getMPVIcon(), #available(macOS 10.13.2, *) {
- let albumArt = MPMediaItemArtwork(boundsSize:icon.size) { _ in
+ if let app = NSApp as? Application, let icon = app.getMPVIcon(),
+ #available(macOS 10.13.2, *)
+ {
+ let albumArt = MPMediaItemArtwork(boundsSize: icon.size) { _ in
return icon
}
nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArt
@@ -119,6 +117,13 @@ class RemoteCommandCenter: NSObject {
mpInfoCenter.nowPlayingInfo = nowPlayingInfo
mpInfoCenter.playbackState = .playing
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(self.makeCurrent),
+ name: NSApplication.willBecomeActiveNotification,
+ object: nil
+ )
}
@objc func stop() {
@@ -131,7 +136,7 @@ class RemoteCommandCenter: NSObject {
mpInfoCenter.playbackState = .unknown
}
- @objc func makeCurrent() {
+ @objc func makeCurrent(notification: NSNotification) {
mpInfoCenter.playbackState = .paused
mpInfoCenter.playbackState = .playing
updatePlaybackState()
@@ -160,7 +165,7 @@ class RemoteCommandCenter: NSObject {
}
}
- application.handleMPKey(mpKey, withMask: Int32(state));
+ EventsResponder.sharedInstance().handleMPKey(mpKey, withMask: Int32(state))
return .success
}
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index 7d42ca2a41..0002552560 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -44,30 +44,30 @@
#define OPT_BASE_STRUCT struct macos_opts
const struct m_sub_options macos_conf = {
.opts = (const struct m_option[]) {
- OPT_CHOICE("macos-title-bar-appearance", macos_title_bar_appearance, 0,
- ({"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
- {"vibrantLight", 3}, {"vibrantDark", 4},
- {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
- {"vibrantLightHighContrast", 7},
- {"vibrantDarkHighContrast", 8})),
- OPT_CHOICE("macos-title-bar-material", macos_title_bar_material, 0,
- ({"titlebar", 0}, {"selection", 1}, {"menu", 2},
- {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
- {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
- {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
- {"underWindowBackground", 12}, {"underPageBackground", 13},
- {"dark", 14}, {"light", 15}, {"mediumLight", 16},
- {"ultraDark", 17})),
- OPT_COLOR("macos-title-bar-color", macos_title_bar_color, 0),
- OPT_CHOICE_OR_INT("macos-fs-animation-duration",
- macos_fs_animation_duration, 0, 0, 1000,
- ({"default", -1})),
- OPT_FLAG("macos-force-dedicated-gpu", macos_force_dedicated_gpu, 0),
- OPT_CHOICE("cocoa-cb-sw-renderer", cocoa_cb_sw_renderer, 0,
- ({"auto", -1}, {"no", 0}, {"yes", 1})),
- OPT_FLAG("cocoa-cb-10bit-context", cocoa_cb_10bit_context, 0),
- OPT_REMOVED("macos-title-bar-style", "Split into --macos-title-bar-appearance "
- "and --macos-title-bar-material"),
+ {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance,
+ {"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
+ {"vibrantLight", 3}, {"vibrantDark", 4},
+ {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
+ {"vibrantLightHighContrast", 7},
+ {"vibrantDarkHighContrast", 8})},
+ {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material,
+ {"titlebar", 0}, {"selection", 1}, {"menu", 2},
+ {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
+ {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
+ {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
+ {"underWindowBackground", 12}, {"underPageBackground", 13},
+ {"dark", 14}, {"light", 15}, {"mediumLight", 16},
+ {"ultraDark", 17})},
+ {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)},
+ {"macos-fs-animation-duration",
+ OPT_CHOICE(macos_fs_animation_duration, {"default", -1}),
+ M_RANGE(0, 1000)},
+ {"macos-force-dedicated-gpu", OPT_FLAG(macos_force_dedicated_gpu)},
+ {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"cocoa-cb-10bit-context", OPT_FLAG(cocoa_cb_10bit_context)},
+ {"macos-title-bar-style", OPT_REMOVED("Split into --macos-title-bar-appearance "
+ "and --macos-title-bar-material")},
{0}
},
.size = sizeof(struct macos_opts),
@@ -99,15 +99,16 @@ static Application *mpv_shared_app(void)
static void terminate_cocoa_application(void)
{
- [NSApp hide:NSApp];
- [NSApp terminate:NSApp];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp hide:NSApp];
+ [NSApp terminate:NSApp];
+ });
}
@implementation Application
@synthesize menuBar = _menu_bar;
@synthesize openCount = _open_count;
@synthesize cocoaCB = _cocoa_cb;
-@synthesize remoteCommandCenter = _remoteCommandCenter;
- (void)sendEvent:(NSEvent *)event
{
@@ -175,12 +176,6 @@ static const char macosx_icon[] =
if ([self respondsToSelector:@selector(touchBar)])
[(TouchBar *)self.touchBar processEvent:event];
#endif
-#if HAVE_MACOS_MEDIA_PLAYER
- // 10.12.2 runtime availability check
- if ([self respondsToSelector:@selector(touchBar)]) {
- [_remoteCommandCenter processEvent:event];
- }
-#endif
if (_cocoa_cb) {
[_cocoa_cb processEvent:event];
}
@@ -208,11 +203,6 @@ static const char macosx_icon[] =
[_eventsResponder queueCommand:cmd];
}
-- (void)handleMPKey:(int)key withMask:(int)mask
-{
- [_eventsResponder handleMPKey:key withMask:mask];
-}
-
- (void)stopMPV:(char *)cmd
{
if (![_eventsResponder queueCommand:cmd])
@@ -228,11 +218,6 @@ static const char macosx_icon[] =
andEventID:kAEQuitApplication];
}
-- (void)applicationWillBecomeActive:(NSNotification *)notification
-{
- [_remoteCommandCenter makeCurrent];
-}
-
- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
@@ -307,13 +292,6 @@ static void init_cocoa_application(bool regular)
[NSApp setDelegate:NSApp];
[NSApp setMenuBar:[[MenuBar alloc] init]];
-#if HAVE_MACOS_MEDIA_PLAYER
- // 10.12.2 runtime availability check
- if ([NSApp respondsToSelector:@selector(touchBar)]) {
- [NSApp setRemoteCommandCenter:[[RemoteCommandCenter alloc] initWithApp:NSApp]];
- }
-#endif
-
// Will be set to Regular from cocoa_common during UI creation so that we
// don't create an icon when playing audio only files.
[NSApp setActivationPolicy: regular ?
@@ -324,7 +302,9 @@ static void init_cocoa_application(bool regular)
// Because activation policy has just been set to behave like a real
// application, that policy must be reset on exit to prevent, among
// other things, the menubar created here from remaining on screen.
- [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
+ });
});
}
diff --git a/osdep/macosx_application_objc.h b/osdep/macosx_application_objc.h
index 4bd5b55b1a..11959a83ea 100644
--- a/osdep/macosx_application_objc.h
+++ b/osdep/macosx_application_objc.h
@@ -20,7 +20,6 @@
#import "osdep/macosx_menubar_objc.h"
@class CocoaCB;
-@class RemoteCommandCenter;
struct mpv_event;
struct mpv_handle;
@@ -29,7 +28,6 @@ struct mpv_handle;
- (NSImage *)getMPVIcon;
- (void)processEvent:(struct mpv_event *)event;
- (void)queueCommand:(char *)cmd;
-- (void)handleMPKey:(int)key withMask:(int)mask;
- (void)stopMPV:(char *)cmd;
- (void)openFiles:(NSArray *)filenames;
- (void)setMpvHandle:(struct mpv_handle *)ctx;
@@ -39,5 +37,4 @@ struct mpv_handle;
@property(nonatomic, retain) MenuBar *menuBar;
@property(nonatomic, assign) size_t openCount;
@property(nonatomic, retain) CocoaCB *cocoaCB;
-@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter;
@end
diff --git a/osdep/macosx_events.m b/osdep/macosx_events.m
index 3f40e41f6c..e2ae7aa162 100644
--- a/osdep/macosx_events.m
+++ b/osdep/macosx_events.m
@@ -161,6 +161,8 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
@implementation EventsResponder
+@synthesize remoteCommandCenter = _remoteCommandCenter;
+
+ (EventsResponder *)sharedInstance
{
static EventsResponder *responder = nil;
@@ -272,14 +274,18 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
[NSApp processEvent:event];
}
+ if (_remoteCommandCenter) {
+ [_remoteCommandCenter processEvent:event];
+ }
+
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN: {
- #if HAVE_MACOS_COCOA_CB
+#if HAVE_MACOS_COCOA_CB
if ([(Application *)NSApp cocoaCB].isShuttingDown) {
_ctx = nil;
return;
}
- #endif
+#endif
mpv_destroy(_ctx);
_ctx = nil;
break;
@@ -289,16 +295,19 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
- (void)startMediaKeys
{
- if ([(Application *)NSApp remoteCommandCenter]) {
- [[(Application *)NSApp remoteCommandCenter] start];
+#if HAVE_MACOS_MEDIA_PLAYER
+ // 10.12.2 runtime availability check
+ if (_remoteCommandCenter == nil && [NSApp respondsToSelector:@selector(touchBar)]) {
+ _remoteCommandCenter = [[RemoteCommandCenter alloc] init];
}
+#endif
+
+ [_remoteCommandCenter start];
}
- (void)stopMediaKeys
{
- if ([(Application *)NSApp remoteCommandCenter]) {
- [[(Application *)NSApp remoteCommandCenter] stop];
- }
+ [_remoteCommandCenter stop];
}
- (int)mapKeyModifiers:(int)cocoaModifiers
diff --git a/osdep/macosx_events_objc.h b/osdep/macosx_events_objc.h
index efc4e8f7ec..9394fe7160 100644
--- a/osdep/macosx_events_objc.h
+++ b/osdep/macosx_events_objc.h
@@ -20,6 +20,7 @@
#import <Cocoa/Cocoa.h>
#include "osdep/macosx_events.h"
+@class RemoteCommandCenter;
struct input_ctx;
@interface EventsResponder : NSObject
@@ -39,4 +40,6 @@ struct input_ctx;
- (BOOL)handleMPKey:(int)key withMask:(int)mask;
+@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter;
+
@end
diff --git a/osdep/subprocess.c b/osdep/subprocess.c
index 8bb2acd0b7..8a930a60df 100644
--- a/osdep/subprocess.c
+++ b/osdep/subprocess.c
@@ -123,6 +123,12 @@ void mp_subprocess_detached(struct mp_log *log, char **args)
talloc_free(p);
}
+void mp_subprocess2(struct mp_subprocess_opts *opts,
+ struct mp_subprocess_result *res)
+{
+ *res = (struct mp_subprocess_result){.error = MP_SUBPROCESS_EUNSUPPORTED};
+}
+
#endif
const char *mp_subprocess_err_str(int num)
diff --git a/osdep/threads.c b/osdep/threads.c
index 8cd4545c1e..5b164c5179 100644
--- a/osdep/threads.c
+++ b/osdep/threads.c
@@ -19,8 +19,8 @@
#include <errno.h>
#include <pthread.h>
+#include "common/common.h"
#include "config.h"
-
#include "threads.h"
#include "timer.h"
@@ -49,3 +49,29 @@ void mpthread_set_name(const char *name)
pthread_setname_np(tname);
#endif
}
+
+int mp_ptwrap_check(const char *file, int line, int res)
+{
+ if (res && res != ETIMEDOUT) {
+ fprintf(stderr, "%s:%d: internal error: pthread result %d (%s)\n",
+ file, line, res, mp_strerror(res));
+ abort();
+ }
+ return res;
+}
+
+int mp_ptwrap_mutex_init(const char *file, int line, pthread_mutex_t *m,
+ const pthread_mutexattr_t *attr)
+{
+ pthread_mutexattr_t m_attr;
+ if (!attr) {
+ attr = &m_attr;
+ pthread_mutexattr_init(&m_attr);
+ // Force normal mutexes to error checking.
+ pthread_mutexattr_settype(&m_attr, PTHREAD_MUTEX_ERRORCHECK);
+ }
+ int res = mp_ptwrap_check(file, line, (pthread_mutex_init)(m, attr));
+ if (attr == &m_attr)
+ pthread_mutexattr_destroy(&m_attr);
+ return res;
+}
diff --git a/osdep/threads.h b/osdep/threads.h
index 8633618009..1c5dbf8a3e 100644
--- a/osdep/threads.h
+++ b/osdep/threads.h
@@ -10,4 +10,59 @@ int mpthread_mutex_init_recursive(pthread_mutex_t *mutex);
// Set thread name (for debuggers).
void mpthread_set_name(const char *name);
+int mp_ptwrap_check(const char *file, int line, int res);
+int mp_ptwrap_mutex_init(const char *file, int line, pthread_mutex_t *m,
+ const pthread_mutexattr_t *attr);
+
+#ifdef MP_PTHREAD_DEBUG
+
+// pthread debugging wrappers. Technically, this is undefined behavior, because
+// you are not supposed to define any symbols that clash with reserved names.
+// Other than that, they should be fine.
+
+// Note: mpv normally never checks pthread error return values of certain
+// functions that should never fail. It does so because these cases would
+// be undefined behavior anyway (such as double-frees etc.). However,
+// since there are no good pthread debugging tools, these wrappers are
+// provided for the sake of debugging. They crash on unexpected errors.
+//
+// Technically, pthread_cond/mutex_init() can fail with ENOMEM. We don't
+// really respect this for normal/recursive mutex types, as due to the
+// existence of static initializers, no sane implementation could actually
+// require allocating memory.
+
+#define MP_PTWRAP(fn, ...) \
+ mp_ptwrap_check(__FILE__, __LINE__, (fn)(__VA_ARGS__))
+
+// ISO C defines that all standard functions can be macros, except undef'ing
+// them is allowed and must make the "real" definitions available. (Whatever.)
+#undef pthread_cond_init
+#undef pthread_cond_destroy
+#undef pthread_cond_broadcast
+#undef pthread_cond_signal
+#undef pthread_cond_wait
+#undef pthread_cond_timedwait
+#undef pthread_detach
+#undef pthread_join
+#undef pthread_mutex_destroy
+#undef pthread_mutex_lock
+#undef pthread_mutex_unlock
+
+#define pthread_cond_init(...) MP_PTWRAP(pthread_cond_init, __VA_ARGS__)
+#define pthread_cond_destroy(...) MP_PTWRAP(pthread_cond_destroy, __VA_ARGS__)
+#define pthread_cond_broadcast(...) MP_PTWRAP(pthread_cond_broadcast, __VA_ARGS__)
+#define pthread_cond_signal(...) MP_PTWRAP(pthread_cond_signal, __VA_ARGS__)
+#define pthread_cond_wait(...) MP_PTWRAP(pthread_cond_wait, __VA_ARGS__)
+#define pthread_cond_timedwait(...) MP_PTWRAP(pthread_cond_timedwait, __VA_ARGS__)
+#define pthread_detach(...) MP_PTWRAP(pthread_detach, __VA_ARGS__)
+#define pthread_join(...) MP_PTWRAP(pthread_join, __VA_ARGS__)
+#define pthread_mutex_destroy(...) MP_PTWRAP(pthread_mutex_destroy, __VA_ARGS__)
+#define pthread_mutex_lock(...) MP_PTWRAP(pthread_mutex_lock, __VA_ARGS__)
+#define pthread_mutex_unlock(...) MP_PTWRAP(pthread_mutex_unlock, __VA_ARGS__)
+
+#define pthread_mutex_init(...) \
+ mp_ptwrap_mutex_init(__FILE__, __LINE__, __VA_ARGS__)
+
+#endif
+
#endif
diff --git a/osdep/win32/include/pthread.h b/osdep/win32/include/pthread.h
index 5157b8e342..6c87949831 100644
--- a/osdep/win32/include/pthread.h
+++ b/osdep/win32/include/pthread.h
@@ -55,6 +55,7 @@ typedef struct {
#define pthread_mutexattr_init(attr) (*(attr) = 0)
#define pthread_mutexattr_settype(attr, type) (*(attr) = (type))
#define PTHREAD_MUTEX_RECURSIVE 1
+#define PTHREAD_MUTEX_ERRORCHECK 2 // unsupported
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
diff --git a/player/audio.c b/player/audio.c
index fdc07fbf2b..cde444ffb4 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -46,8 +46,6 @@ enum {
AD_WAIT = -4,
};
-// Try to reuse the existing filters to change playback speed. If it works,
-// return true; if filter recreation is needed, return false.
static void update_speed_filters(struct MPContext *mpctx)
{
struct ao_chain *ao_c = mpctx->ao_chain;
@@ -192,7 +190,7 @@ void reset_audio_state(struct MPContext *mpctx)
ao_chain_reset_state(mpctx->ao_chain);
struct track *t = mpctx->ao_chain->track;
if (t && t->dec)
- t->dec->play_dir = mpctx->play_dir;
+ mp_decoder_wrapper_set_play_dir(t->dec, mpctx->play_dir);
}
mpctx->audio_status = mpctx->ao_chain ? STATUS_SYNCING : STATUS_EOF;
mpctx->delay = 0;
@@ -391,7 +389,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
MP_VERBOSE(mpctx, "Falling back to PCM output.\n");
ao_c->spdif_passthrough = false;
ao_c->spdif_failed = true;
- ao_c->track->dec->try_spdif = false;
+ mp_decoder_wrapper_set_spdif_flag(ao_c->track->dec, false);
if (!mp_decoder_wrapper_reinit(ao_c->track->dec))
goto init_error;
reset_audio_state(mpctx);
@@ -443,7 +441,7 @@ int init_audio_decoder(struct MPContext *mpctx, struct track *track)
goto init_error;
if (track->ao_c)
- track->dec->try_spdif = true;
+ mp_decoder_wrapper_set_spdif_flag(track->dec, true);
if (!mp_decoder_wrapper_reinit(track->dec))
goto init_error;
@@ -627,7 +625,8 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
!mp_audio_buffer_samples(mpctx->ao_chain->ao_buffer))
return false; // no audio read yet
- bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF;
+ bool sync_to_video = mpctx->vo_chain && mpctx->video_status != STATUS_EOF &&
+ !mpctx->vo_chain->is_sparse;
double sync_pts = MP_NOPTS_VALUE;
if (sync_to_video) {
@@ -777,7 +776,7 @@ void reload_audio_output(struct MPContext *mpctx)
if (dec && ao_c->spdif_failed) {
ao_c->spdif_passthrough = true;
ao_c->spdif_failed = false;
- dec->try_spdif = true;
+ mp_decoder_wrapper_set_spdif_flag(ao_c->track->dec, true);
if (!mp_decoder_wrapper_reinit(dec)) {
MP_ERR(mpctx, "Error reinitializing audio.\n");
error_on_track(mpctx, ao_c->track);
@@ -837,7 +836,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
}
if (mpctx->vo_chain && ao_c->track && ao_c->track->dec &&
- ao_c->track->dec->pts_reset)
+ mp_decoder_wrapper_get_pts_reset(ao_c->track->dec))
{
MP_WARN(mpctx, "Reset playback due to audio timestamp reset.\n");
reset_playback_state(mpctx);
diff --git a/player/client.c b/player/client.c
index 14ee8fd7ee..0babacf147 100644
--- a/player/client.c
+++ b/player/client.c
@@ -77,6 +77,7 @@ struct mp_client_api {
// used to safely unlock mp_client_api.lock while iterating the list of
// clients.
uint64_t clients_list_change_ts;
+ int64_t id_alloc;
struct mp_custom_protocol *custom_protocols;
int num_custom_protocols;
@@ -102,6 +103,7 @@ struct observe_property {
union m_option_value value;
uint64_t value_ret_ts; // logical timestamp of value returned to user
union m_option_value value_ret;
+ bool waiting_for_hook; // flag for draining old property changes on a hook
};
struct mpv_handle {
@@ -110,6 +112,7 @@ struct mpv_handle {
struct mp_log *log;
struct MPContext *mpctx;
struct mp_client_api *clients;
+ int64_t id;
// -- not thread-safe
struct mpv_event *cur_event;
@@ -140,6 +143,7 @@ struct mpv_handle {
size_t async_counter; // pending other async events
bool choked; // recovering from queue overflow
bool destroying; // pending destruction; no API accesses allowed
+ bool hook_pending; // hook events are returned after draining properties
struct observe_property **properties;
int num_properties;
@@ -221,20 +225,39 @@ bool mp_clients_all_initialized(struct MPContext *mpctx)
return all_ok;
}
+static struct mpv_handle *find_client_id(struct mp_client_api *clients, int64_t id)
+{
+ for (int n = 0; n < clients->num_clients; n++) {
+ if (clients->clients[n]->id == id)
+ return clients->clients[n];
+ }
+ return NULL;
+}
+
static struct mpv_handle *find_client(struct mp_client_api *clients,
const char *name)
{
+ if (name[0] == '@') {
+ char *end;
+ errno = 0;
+ long long int id = strtoll(name + 1, &end, 10);
+ if (errno || end[0])
+ return NULL;
+ return find_client_id(clients, id);
+ }
+
for (int n = 0; n < clients->num_clients; n++) {
if (strcmp(clients->clients[n]->name, name) == 0)
return clients->clients[n];
}
+
return NULL;
}
-bool mp_client_exists(struct MPContext *mpctx, const char *client_name)
+bool mp_client_id_exists(struct MPContext *mpctx, int64_t id)
{
pthread_mutex_lock(&mpctx->clients->lock);
- bool r = find_client(mpctx->clients, client_name);
+ bool r = find_client_id(mpctx->clients, id);
pthread_mutex_unlock(&mpctx->clients->lock);
return r;
}
@@ -269,6 +292,7 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
.log = mp_log_new(client, clients->mpctx->log, nname),
.mpctx = clients->mpctx,
.clients = clients,
+ .id = ++(clients->id_alloc),
.cur_event = talloc_zero(client, struct mpv_event),
.events = talloc_array(client, mpv_event, num_events),
.max_events = num_events,
@@ -306,6 +330,11 @@ const char *mpv_client_name(mpv_handle *ctx)
return ctx->name;
}
+int64_t mpv_client_id(mpv_handle *ctx)
+{
+ return ctx->id;
+}
+
struct mp_log *mp_client_get_log(struct mpv_handle *ctx)
{
return ctx->log;
@@ -674,6 +703,9 @@ static void dup_event_data(struct mpv_event *ev)
ev->data = msg;
break;
}
+ case MPV_EVENT_START_FILE:
+ ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_start_file));
+ break;
case MPV_EVENT_END_FILE:
ev->data = talloc_memdup(NULL, ev->data, sizeof(mpv_event_end_file));
break;
@@ -831,6 +863,18 @@ int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name,
return mp_client_send_event(mpctx, client_name, 0, event, event_data.data);
}
+static bool deprecated_events[] = {
+ [MPV_EVENT_TRACKS_CHANGED] = true,
+ [MPV_EVENT_TRACK_SWITCHED] = true,
+ [MPV_EVENT_IDLE] = true,
+ [MPV_EVENT_PAUSE] = true,
+ [MPV_EVENT_UNPAUSE] = true,
+ [MPV_EVENT_TICK] = true,
+ [MPV_EVENT_SCRIPT_INPUT_DISPATCH] = true,
+ [MPV_EVENT_METADATA_UPDATE] = true,
+ [MPV_EVENT_CHAPTER_CHANGE] = true,
+};
+
int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
{
if (!mpv_event_name(event) || enable < 0 || enable > 1)
@@ -841,12 +885,37 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
pthread_mutex_lock(&ctx->lock);
uint64_t bit = 1ULL << event;
ctx->event_mask = enable ? ctx->event_mask | bit : ctx->event_mask & ~bit;
- if (enable && event == MPV_EVENT_TICK)
- MP_WARN(ctx, "The 'tick' event is deprecated and will be removed.\n");
+ if (enable && event < MP_ARRAY_SIZE(deprecated_events) &&
+ deprecated_events[event])
+ {
+ MP_WARN(ctx, "The '%s' event is deprecated and will be removed.\n",
+ mpv_event_name(event));
+ }
pthread_mutex_unlock(&ctx->lock);
return 0;
}
+// Set waiting_for_hook==true for all possibly pending properties.
+static void set_wait_for_hook_flags(mpv_handle *ctx)
+{
+ for (int n = 0; n < ctx->num_properties; n++) {
+ struct observe_property *prop = ctx->properties[n];
+
+ if (prop->value_ret_ts != prop->change_ts)
+ prop->waiting_for_hook = true;
+ }
+}
+
+// Return whether any property still has waiting_for_hook set.
+static bool check_for_for_hook_flags(mpv_handle *ctx)
+{
+ for (int n = 0; n < ctx->num_properties; n++) {
+ if (ctx->properties[n]->waiting_for_hook)
+ return true;
+ }
+ return false;
+}
+
mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
{
mpv_event *event = ctx->cur_event;
@@ -874,8 +943,24 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
event->event_id = MPV_EVENT_QUEUE_OVERFLOW;
break;
}
- if (ctx->num_events) {
- *event = ctx->events[ctx->first_event];
+ struct mpv_event *ev =
+ ctx->num_events ? &ctx->events[ctx->first_event] : NULL;
+ if (ev && ev->event_id == MPV_EVENT_HOOK) {
+ // Give old property notifications priority over hooks. This is a
+ // guarantee given to clients to simplify their logic. New property
+ // changes after this are treated normally, so
+ if (!ctx->hook_pending) {
+ ctx->hook_pending = true;
+ set_wait_for_hook_flags(ctx);
+ }
+ if (check_for_for_hook_flags(ctx)) {
+ ev = NULL; // delay
+ } else {
+ ctx->hook_pending = false;
+ }
+ }
+ if (ev) {
+ *event = *ev;
ctx->first_event = (ctx->first_event + 1) % ctx->max_events;
ctx->num_events--;
talloc_steal(event, event->data);
@@ -946,7 +1031,9 @@ static bool conv_node_to_format(void *dst, mpv_format dst_fmt, mpv_node *src)
return true;
}
if (dst_fmt == MPV_FORMAT_INT64 && src->format == MPV_FORMAT_DOUBLE) {
- if (src->u.double_ >= INT64_MIN && src->u.double_ <= INT64_MAX) {
+ if (src->u.double_ > (double)INT64_MIN &&
+ src->u.double_ < (double)INT64_MAX)
+ {
*(int64_t *)dst = src->u.double_;
return true;
}
@@ -1141,7 +1228,7 @@ static void async_cmd_fn(void *data)
struct async_cmd_request *req = data;
struct mp_cmd *cmd = req->cmd;
- ta_xset_parent(cmd, NULL);
+ ta_set_parent(cmd, NULL);
req->cmd = NULL;
struct mp_abort_entry *abort = NULL;
@@ -1608,7 +1695,7 @@ static void send_client_property_changes(struct mpv_handle *ctx)
ctx->async_counter -= 1;
prop_unref(prop);
- // Set of observed properties was changed or something similar
+ // Set if observed properties was changed or something similar
// => start over, retry next time.
if (cur_ts != ctx->properties_change_ts || ctx->destroying) {
m_option_free(type, &val);
@@ -1638,10 +1725,16 @@ static void send_client_property_changes(struct mpv_handle *ctx)
changed = true;
}
- if (changed) {
- ctx->new_property_events = true;
- } else if (prop->value_ret_ts == prop->value_ts) {
+ if (prop->waiting_for_hook)
+ ctx->new_property_events = true; // make sure to wakeup
+
+ // Avoid retriggering the change event if the property didn't change,
+ // and the previous value was actually returned to the client.
+ if (!changed && prop->value_ret_ts == prop->value_ts) {
prop->value_ret_ts = prop->change_ts; // no change => no event
+ prop->waiting_for_hook = false;
+ } else {
+ ctx->new_property_events = true;
}
prop->value_ts = prop->change_ts;
@@ -1662,7 +1755,7 @@ void mp_client_send_property_changes(struct MPContext *mpctx)
struct mpv_handle *ctx = clients->clients[n];
pthread_mutex_lock(&ctx->lock);
- if (!ctx->has_pending_properties) {
+ if (!ctx->has_pending_properties || ctx->destroying) {
pthread_mutex_unlock(&ctx->lock);
continue;
}
@@ -1690,7 +1783,8 @@ static bool gen_property_change_event(struct mpv_handle *ctx)
while (1) {
if (ctx->cur_property_index >= ctx->num_properties) {
- if (!ctx->new_property_events || !ctx->num_properties)
+ ctx->new_property_events &= ctx->num_properties > 0;
+ if (!ctx->new_property_events)
break;
ctx->new_property_events = false;
ctx->cur_property_index = 0;
@@ -1698,8 +1792,11 @@ static bool gen_property_change_event(struct mpv_handle *ctx)
struct observe_property *prop = ctx->properties[ctx->cur_property_index++];
- if (prop->value_ret_ts != prop->value_ts) {
+ if (prop->value_ts == prop->change_ts && // not a stale value?
+ prop->value_ret_ts != prop->value_ts) // other value than last time?
+ {
prop->value_ret_ts = prop->value_ts;
+ prop->waiting_for_hook = false;
prop_unref(ctx->cur_property);
ctx->cur_property = prop;
prop->refcount += 1;
@@ -1728,7 +1825,7 @@ int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata,
const char *name, int priority)
{
lock_core(ctx);
- mp_hook_add(ctx->mpctx, ctx->name, name, reply_userdata, priority, false);
+ mp_hook_add(ctx->mpctx, ctx->name, ctx->id, name, reply_userdata, priority);
unlock_core(ctx);
return 0;
}
@@ -1736,7 +1833,7 @@ int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata,
int mpv_hook_continue(mpv_handle *ctx, uint64_t id)
{
lock_core(ctx);
- int r = mp_hook_continue(ctx->mpctx, ctx->name, id);
+ int r = mp_hook_continue(ctx->mpctx, ctx->id, id);
unlock_core(ctx);
return r;
}
@@ -1843,6 +1940,119 @@ unsigned long mpv_client_api_version(void)
return MPV_CLIENT_API_VERSION;
}
+int mpv_event_to_node(mpv_node *dst, mpv_event *event)
+{
+ *dst = (mpv_node){0};
+
+ node_init(dst, MPV_FORMAT_NODE_MAP, NULL);
+ node_map_add_string(dst, "event", mpv_event_name(event->event_id));
+
+ if (event->error < 0)
+ node_map_add_string(dst, "error", mpv_error_string(event->error));
+
+ if (event->reply_userdata)
+ node_map_add_int64(dst, "id", event->reply_userdata);
+
+ switch (event->event_id) {
+
+ case MPV_EVENT_START_FILE: {
+ mpv_event_start_file *esf = event->data;
+
+ node_map_add_int64(dst, "playlist_entry_id", esf->playlist_entry_id);
+ break;
+ }
+
+ case MPV_EVENT_END_FILE: {
+ mpv_event_end_file *eef = event->data;
+
+ const char *reason;
+ switch (eef->reason) {
+ case MPV_END_FILE_REASON_EOF: reason = "eof"; break;
+ case MPV_END_FILE_REASON_STOP: reason = "stop"; break;
+ case MPV_END_FILE_REASON_QUIT: reason = "quit"; break;
+ case MPV_END_FILE_REASON_ERROR: reason = "error"; break;
+ case MPV_END_FILE_REASON_REDIRECT: reason = "redirect"; break;
+ default:
+ reason = "unknown";
+ }
+ node_map_add_string(dst, "reason", reason);
+
+ node_map_add_int64(dst, "playlist_entry_id", eef->playlist_entry_id);
+
+ if (eef->playlist_insert_id) {
+ node_map_add_int64(dst, "playlist_insert_id", eef->playlist_insert_id);
+ node_map_add_int64(dst, "playlist_insert_num_entries",
+ eef->playlist_insert_num_entries);
+ }
+
+ if (eef->reason == MPV_END_FILE_REASON_ERROR)
+ node_map_add_string(dst, "file_error", mpv_error_string(eef->error));
+ break;
+ }
+
+ case MPV_EVENT_LOG_MESSAGE: {
+ mpv_event_log_message *msg = event->data;
+
+ node_map_add_string(dst, "prefix", msg->prefix);
+ node_map_add_string(dst, "level", msg->level);
+ node_map_add_string(dst, "text", msg->text);
+ break;
+ }
+
+ case MPV_EVENT_CLIENT_MESSAGE: {
+ mpv_event_client_message *msg = event->data;
+
+ struct mpv_node *args = node_map_add(dst, "args", MPV_FORMAT_NODE_ARRAY);
+ for (int n = 0; n < msg->num_args; n++) {
+ struct mpv_node *sn = node_array_add(args, MPV_FORMAT_NONE);
+ sn->format = MPV_FORMAT_STRING;
+ sn->u.string = (char *)msg->args[n];
+ }
+ break;
+ }
+
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ mpv_event_property *prop = event->data;
+
+ node_map_add_string(dst, "name", prop->name);
+
+ switch (prop->format) {
+ case MPV_FORMAT_NODE:
+ *node_map_add(dst, "data", MPV_FORMAT_NONE) =
+ *(struct mpv_node *)prop->data;
+ break;
+ case MPV_FORMAT_DOUBLE:
+ node_map_add_double(dst, "data", *(double *)prop->data);
+ break;
+ case MPV_FORMAT_FLAG:
+ node_map_add_flag(dst, "data", *(int *)prop->data);
+ break;
+ case MPV_FORMAT_STRING:
+ node_map_add_string(dst, "data", *(char **)prop->data);
+ break;
+ default: ;
+ }
+ break;
+ }
+
+ case MPV_EVENT_COMMAND_REPLY: {
+ mpv_event_command *cmd = event->data;
+
+ *node_map_add(dst, "result", MPV_FORMAT_NONE) = cmd->result;
+ break;
+ }
+
+ case MPV_EVENT_HOOK: {
+ mpv_event_hook *hook = event->data;
+
+ node_map_add_int64(dst, "hook_id", hook->id);
+ break;
+ }
+
+ }
+ return 0;
+}
+
static const char *const err_table[] = {
[-MPV_ERROR_SUCCESS] = "success",
[-MPV_ERROR_EVENT_QUEUE_FULL] = "event queue full",
diff --git a/player/client.h b/player/client.h
index a8eae2d0c1..ddc568e975 100644
--- a/player/client.h
+++ b/player/client.h
@@ -23,7 +23,7 @@ void mp_shutdown_clients(struct MPContext *mpctx);
bool mp_is_shutting_down(struct MPContext *mpctx);
bool mp_clients_all_initialized(struct MPContext *mpctx);
-bool mp_client_exists(struct MPContext *mpctx, const char *client_name);
+bool mp_client_id_exists(struct MPContext *mpctx, int64_t id);
void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data);
int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
uint64_t reply_userdata, int event, void *data);
diff --git a/player/command.c b/player/command.c
index 37ace97ba6..e1f755d46c 100644
--- a/player/command.c
+++ b/player/command.c
@@ -15,6 +15,7 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <float.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
@@ -26,6 +27,7 @@
#include <pthread.h>
#include <sys/types.h>
+#include <ass/ass.h>
#include <libavutil/avstring.h>
#include <libavutil/common.h>
@@ -50,7 +52,7 @@
#include "sub/dec_sub.h"
#include "options/m_option.h"
#include "options/m_property.h"
-#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "video/out/vo.h"
#include "video/csputils.h"
#include "video/hwdec.h"
@@ -78,8 +80,6 @@ struct command_ctx {
// All properties, terminated with a {0} item.
struct m_property *properties;
- bool is_idle;
-
double last_seek_time;
double last_seek_pts;
double marked_pts;
@@ -118,12 +118,12 @@ struct overlay {
};
struct hook_handler {
- char *client; // client API user name
+ char *client; // client mpv_handle name (for logging)
+ int64_t client_id; // client mpv_handle ID
char *type; // kind of hook, e.g. "on_load"
uint64_t user_id; // user-chosen ID
int priority; // priority for global hook order
int64_t seq; // unique ID, != 0, also for fixed order on equal priorities
- bool legacy; // old cmd based hook API
bool active; // hook is currently in progress (only 1 at a time for now)
};
@@ -161,7 +161,7 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type)
for (int n = 0; n < cmd->num_hooks; n++) {
struct hook_handler *h = cmd->hooks[n];
if (h->active && strcmp(h->type, type) == 0) {
- if (!mp_client_exists(mpctx, h->client)) {
+ if (!mp_client_id_exists(mpctx, h->client_id)) {
MP_WARN(mpctx, "client removed during hook handling\n");
hook_remove(mpctx, h);
break;
@@ -178,29 +178,14 @@ static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h)
h->active = true;
uint64_t reply_id = 0;
- void *data;
- int msg;
- if (h->legacy) {
- mpv_event_client_message *m = talloc_ptrtype(NULL, m);
- *m = (mpv_event_client_message){0};
- MP_TARRAY_APPEND(m, m->args, m->num_args, "hook_run");
- MP_TARRAY_APPEND(m, m->args, m->num_args,
- talloc_asprintf(m, "%llu", (long long)h->user_id));
- MP_TARRAY_APPEND(m, m->args, m->num_args,
- talloc_asprintf(m, "%llu", (long long)h->seq));
- data = m;
- msg = MPV_EVENT_CLIENT_MESSAGE;
- } else {
- mpv_event_hook *m = talloc_ptrtype(NULL, m);
- *m = (mpv_event_hook){
- .name = talloc_strdup(m, h->type),
- .id = h->seq,
- },
- reply_id = h->user_id;
- data = m;
- msg = MPV_EVENT_HOOK;
- }
- int r = mp_client_send_event(mpctx, h->client, reply_id, msg, data);
+ mpv_event_hook *m = talloc_ptrtype(NULL, m);
+ *m = (mpv_event_hook){
+ .name = talloc_strdup(m, h->type),
+ .id = h->seq,
+ },
+ reply_id = h->user_id;
+ char *name = mp_tprintf(22, "@%"PRIi64, h->client_id);
+ int r = mp_client_send_event(mpctx, name, reply_id, MPV_EVENT_HOOK, m);
if (r < 0) {
MP_WARN(mpctx, "Sending hook command failed. Removing hook.\n");
hook_remove(mpctx, h);
@@ -233,13 +218,13 @@ void mp_hook_start(struct MPContext *mpctx, char *type)
}
}
-int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id)
+int mp_hook_continue(struct MPContext *mpctx, int64_t client_id, uint64_t id)
{
struct command_ctx *cmd = mpctx->command_ctx;
for (int n = 0; n < cmd->num_hooks; n++) {
struct hook_handler *h = cmd->hooks[n];
- if (strcmp(h->client, client) == 0 && h->seq == id) {
+ if (h->client_id == client_id && h->seq == id) {
if (!h->active)
break;
h->active = false;
@@ -260,22 +245,19 @@ static int compare_hook(const void *pa, const void *pb)
return (*h1)->seq - (*h2)->seq;
}
-void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name,
- uint64_t user_id, int pri, bool legacy)
+void mp_hook_add(struct MPContext *mpctx, char *client, int64_t client_id,
+ const char *name, uint64_t user_id, int pri)
{
- if (legacy)
- MP_WARN(mpctx, "The old hook API is deprecated! Use the libmpv API.\n");
-
struct command_ctx *cmd = mpctx->command_ctx;
struct hook_handler *h = talloc_ptrtype(cmd, h);
int64_t seq = ++cmd->hook_seq;
*h = (struct hook_handler){
.client = talloc_strdup(h, client),
+ .client_id = client_id,
.type = talloc_strdup(h, name),
.user_id = user_id,
.priority = pri,
.seq = seq,
- .legacy = legacy,
};
MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h);
qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);
@@ -674,7 +656,8 @@ static int mp_property_frame_drop_dec(void *ctx, struct m_property *prop,
if (!dec)
return M_PROPERTY_UNAVAILABLE;
- return m_property_int_ro(action, arg, dec->dropped_frames);
+ return m_property_int_ro(action, arg,
+ mp_decoder_wrapper_get_frames_dropped(dec));
}
static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop,
@@ -753,7 +736,6 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop,
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){
.type = CONF_TYPE_DOUBLE,
- .flags = M_OPT_RANGE,
.min = 0,
.max = 100,
};
@@ -872,7 +854,6 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){
.type = CONF_TYPE_INT,
- .flags = M_OPT_MIN | M_OPT_MAX,
.min = -1,
.max = num - 1,
};
@@ -1062,9 +1043,10 @@ static int mp_property_edition(void *ctx, struct m_property *prop,
struct demuxer *demuxer = mpctx->demuxer;
if (action == M_PROPERTY_GET_CONSTRICTED_TYPE && demuxer) {
+ if (demuxer->num_editions <= 1)
+ return M_PROPERTY_UNAVAILABLE;
*(struct m_option *)arg = (struct m_option){
.type = CONF_TYPE_INT,
- .flags = M_OPT_RANGE,
.min = 0,
.max = demuxer->num_editions - 1,
};
@@ -1329,8 +1311,7 @@ static int mp_property_idle(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- struct command_ctx *cmd = mpctx->command_ctx;
- return m_property_flag_ro(action, arg, cmd->is_idle);
+ return m_property_flag_ro(action, arg, mpctx->stop_play == PT_STOP);
}
static int mp_property_eof_reached(void *ctx, struct m_property *prop,
@@ -1460,6 +1441,8 @@ static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop,
node_map_add_int64(r, "fw-bytes", s.fw_bytes);
if (s.file_cache_bytes >= 0)
node_map_add_int64(r, "file-cache-bytes", s.file_cache_bytes);
+ if (s.bytes_per_second > 0)
+ node_map_add_int64(r, "raw-input-rate", s.bytes_per_second);
if (s.seeking != MP_NOPTS_VALUE)
node_map_add_double(r, "debug-seeking", s.seeking);
node_map_add_int64(r, "debug-low-level-seeks", s.low_level_seeks);
@@ -1570,7 +1553,6 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
case M_PROPERTY_GET_CONSTRICTED_TYPE:
*(struct m_option *)arg = (struct m_option){
.type = CONF_TYPE_FLOAT,
- .flags = M_OPT_RANGE,
.min = 0,
.max = opts->softvol_max,
};
@@ -1609,7 +1591,6 @@ static int mp_property_ao_volume(void *ctx, struct m_property *prop,
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){
.type = CONF_TYPE_FLOAT,
- .flags = M_OPT_RANGE,
.min = 0,
.max = 100,
};
@@ -1753,8 +1734,10 @@ static int mp_property_audio_codec(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
struct track *track = mpctx->current_track[0][STREAM_AUDIO];
- const char *c = track && track->dec ? track->dec->decoder_desc : NULL;
- return m_property_strdup_ro(action, arg, c);
+ char desc[256] = "";
+ if (track && track->dec)
+ mp_decoder_wrapper_get_desc(track->dec, desc, sizeof(desc));
+ return m_property_strdup_ro(action, arg, desc[0] ? desc : NULL);
}
static int property_audiofmt(struct mp_aframe *fmt, int action, void *arg)
@@ -1904,9 +1887,9 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
struct mp_codec_params p =
track->stream ? *track->stream->codec : (struct mp_codec_params){0};
- const char *decoder_desc = NULL;
+ char decoder_desc[256] = {0};
if (track->dec)
- decoder_desc = track->dec->decoder_desc;
+ mp_decoder_wrapper_get_desc(track->dec, decoder_desc, sizeof(decoder_desc));
bool has_rg = track->stream && track->stream->codec->replaygain_data;
struct replaygain_data rg = has_rg ? *track->stream->codec->replaygain_data
@@ -1940,7 +1923,7 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
.unavailable = !track->external_filename},
{"ff-index", SUB_PROP_INT(track->ff_index)},
{"decoder-desc", SUB_PROP_STR(decoder_desc),
- .unavailable = !decoder_desc},
+ .unavailable = !decoder_desc[0]},
{"codec", SUB_PROP_STR(p.codec),
.unavailable = !p.codec},
{"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w},
@@ -2106,8 +2089,10 @@ static int mp_property_video_codec(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
struct track *track = mpctx->current_track[0][STREAM_VIDEO];
- const char *c = track && track->dec ? track->dec->decoder_desc : NULL;
- return m_property_strdup_ro(action, arg, c);
+ char desc[256] = "";
+ if (track && track->dec)
+ mp_decoder_wrapper_get_desc(track->dec, desc, sizeof(desc));
+ return m_property_strdup_ro(action, arg, desc[0] ? desc : NULL);
}
static int property_imgparams(struct mp_image_params p, int action, void *arg)
@@ -2560,7 +2545,7 @@ static int mp_property_aspect(void *ctx, struct m_property *prop,
skip_warn: ;
- float aspect = mpctx->opts->movie_aspect;
+ float aspect = *(float *)opt->data;
if (mpctx->vo_chain && aspect <= 0) {
struct mp_image_params *params = &mpctx->vo_chain->filter->input_params;
if (params && params->p_w > 0 && params->p_h > 0) {
@@ -2581,7 +2566,7 @@ skip_warn: ;
*(struct m_option *)arg = *(opt->opt);
return M_PROPERTY_OK;
case M_PROPERTY_PRINT: {
- if (mpctx->opts->movie_aspect < 0) {
+ if (aspect < 0) {
*(char **)arg = talloc_asprintf(NULL, "%.3f (original)", aspect);
return M_PROPERTY_OK;
}
@@ -2720,34 +2705,67 @@ static int mp_property_sub_end(void *ctx, struct m_property *prop,
return m_property_double_ro(action, arg, end);
}
+static int mp_property_playlist_current_pos(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct playlist *pl = mpctx->playlist;
+
+ switch (action) {
+ case M_PROPERTY_GET: {
+ *(int *)arg = playlist_entry_to_index(pl, pl->current);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET: {
+ pl->current = playlist_entry_from_index(pl, *(int *)arg);
+ mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT};
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+
+static int mp_property_playlist_playing_pos(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct playlist *pl = mpctx->playlist;
+ return m_property_int_ro(action, arg,
+ playlist_entry_to_index(pl, mpctx->playing));
+}
+
static int mp_property_playlist_pos_x(void *ctx, struct m_property *prop,
int action, void *arg, int base)
{
MPContext *mpctx = ctx;
struct playlist *pl = mpctx->playlist;
- if (!pl->num_entries)
- return M_PROPERTY_UNAVAILABLE;
switch (action) {
case M_PROPERTY_GET: {
int pos = playlist_entry_to_index(pl, pl->current);
- if (pos < 0)
- return M_PROPERTY_UNAVAILABLE;
- *(int *)arg = pos + base;
+ *(int *)arg = pos < 0 ? -1 : pos + base;
return M_PROPERTY_OK;
}
case M_PROPERTY_SET: {
int pos = *(int *)arg - base;
- struct playlist_entry *e = playlist_entry_from_index(pl, pos);
- if (!e)
- return M_PROPERTY_ERROR;
- mp_set_playlist_entry(mpctx, e);
+ if (pos >= 0 && playlist_entry_to_index(pl, pl->current) == pos) {
+ MP_WARN(mpctx, "Behavior of %s when writing the same value will "
+ "change (currently restarts, it will stop doing this).\n",
+ prop->name);
+ }
+ mp_set_playlist_entry(mpctx, playlist_entry_from_index(pl, pos));
return M_PROPERTY_OK;
}
- case M_PROPERTY_GET_TYPE: {
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT};
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_CONSTRICTED_TYPE: {
struct m_option opt = {
.type = CONF_TYPE_INT,
- .flags = CONF_RANGE,
.min = base,
.max = playlist_entry_count(pl) - 1 + base,
};
@@ -2785,6 +2803,7 @@ static int get_playlist_entry(int item, int action, void *arg, void *ctx)
{"current", SUB_PROP_FLAG(1), .unavailable = !current},
{"playing", SUB_PROP_FLAG(1), .unavailable = !playing},
{"title", SUB_PROP_STR(e->title), .unavailable = !e->title},
+ {"id", SUB_PROP_INT64(e->id)},
{0}
};
@@ -3024,6 +3043,12 @@ static int mp_property_ffmpeg(void *ctx, struct m_property *prop,
return m_property_strdup_ro(action, arg, av_version_info());
}
+static int mp_property_libass_version(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ return m_property_int64_ro(action, arg, ass_library_version());
+}
+
static int mp_property_alias(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -3129,15 +3154,11 @@ static int mp_property_option_info(void *ctx, struct m_property *prop,
if (def_ptr && opt->type->size > 0)
memcpy(&def, def_ptr, opt->type->size);
- bool has_minmax =
- opt->type == &m_option_type_int ||
- opt->type == &m_option_type_int64 ||
- opt->type == &m_option_type_float ||
- opt->type == &m_option_type_double;
+ bool has_minmax = opt->min < opt->max &&
+ (opt->type->flags & M_OPT_TYPE_USES_RANGE);
char **choices = NULL;
if (opt->type == &m_option_type_choice) {
- has_minmax = true;
struct m_opt_choice_alternatives *alt = opt->priv;
int num = 0;
for ( ; alt->name; alt++)
@@ -3163,9 +3184,9 @@ static int mp_property_option_info(void *ctx, struct m_property *prop,
{"set-locally", SUB_PROP_FLAG(co->is_set_locally)},
{"default-value", *opt, def},
{"min", SUB_PROP_DOUBLE(opt->min),
- .unavailable = !(has_minmax && (opt->flags & M_OPT_MIN))},
+ .unavailable = !(has_minmax && opt->min != DBL_MIN)},
{"max", SUB_PROP_DOUBLE(opt->max),
- .unavailable = !(has_minmax && (opt->flags & M_OPT_MAX))},
+ .unavailable = !(has_minmax && opt->max != DBL_MAX)},
{"choices", .type = {.type = CONF_TYPE_STRING_LIST},
.value = {.string_list = choices}, .unavailable = !choices},
{0}
@@ -3371,6 +3392,8 @@ static const struct m_property mp_properties_base[] = {
{"playlist", mp_property_playlist},
{"playlist-pos", mp_property_playlist_pos},
{"playlist-pos-1", mp_property_playlist_pos_1},
+ {"playlist-current-pos", mp_property_playlist_current_pos},
+ {"playlist-playing-pos", mp_property_playlist_playing_pos},
M_PROPERTY_ALIAS("playlist-count", "playlist/count"),
// Audio
@@ -3465,6 +3488,7 @@ static const struct m_property mp_properties_base[] = {
{"mpv-version", mp_property_version},
{"mpv-configuration", mp_property_configuration},
{"ffmpeg-version", mp_property_ffmpeg},
+ {"libass-version", mp_property_libass_version},
{"options", mp_property_options},
{"file-local-options", mp_property_local_options},
@@ -3530,7 +3554,8 @@ static const char *const *const mp_event_property_change[] = {
E(MP_EVENT_WIN_STATE, "display-names", "display-fps"),
E(MP_EVENT_WIN_STATE2, "display-hidpi-scale"),
E(MP_EVENT_CHANGE_PLAYLIST, "playlist", "playlist-pos", "playlist-pos-1",
- "playlist-count", "playlist/count"),
+ "playlist-count", "playlist/count", "playlist-current-pos",
+ "playlist-playing-pos"),
E(MP_EVENT_CORE_IDLE, "core-idle", "eof-reached"),
};
#undef E
@@ -3782,15 +3807,15 @@ static void show_property_osd(MPContext *mpctx, const char *name, int osd_mode)
struct m_option prop = {0};
mp_property_do(name, M_PROPERTY_GET_CONSTRICTED_TYPE, &prop, mpctx);
- if ((osd_mode & MP_ON_OSD_BAR) && (prop.flags & CONF_RANGE) == CONF_RANGE) {
- if (prop.type == CONF_TYPE_INT) {
+ if ((osd_mode & MP_ON_OSD_BAR)) {
+ if (prop.type == CONF_TYPE_INT && prop.min < prop.max) {
int n = prop.min;
if (disp.osd_progbar)
n = disp.marker;
int i;
if (mp_property_do(name, M_PROPERTY_GET, &i, mpctx) > 0)
set_osd_bar(mpctx, disp.osd_progbar, prop.min, prop.max, n, i);
- } else if (prop.type == CONF_TYPE_FLOAT) {
+ } else if (prop.type == CONF_TYPE_FLOAT && prop.min < prop.max) {
float n = prop.min;
if (disp.osd_progbar)
n = disp.marker;
@@ -4101,6 +4126,7 @@ static void cmd_osd_overlay(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
+ double rc[4] = {0};
struct osd_external_ass ov = {
.owner = cmd->cmd->sender,
@@ -4110,9 +4136,23 @@ static void cmd_osd_overlay(void *p)
.res_x = cmd->args[3].v.i,
.res_y = cmd->args[4].v.i,
.z = cmd->args[5].v.i,
+ .hidden = cmd->args[6].v.i,
+ .out_rc = cmd->args[7].v.i ? rc : NULL,
};
osd_set_external(mpctx->osd, &ov);
+
+ struct mpv_node *res = &cmd->result;
+ node_init(res, MPV_FORMAT_NODE_MAP, NULL);
+
+ // (An empty rc uses INFINITY, avoid in JSON, just leave it unset.)
+ if (rc[0] < rc[2] && rc[1] < rc[3]) {
+ node_map_add_double(res, "x0", rc[0]);
+ node_map_add_double(res, "y0", rc[1]);
+ node_map_add_double(res, "x1", rc[2]);
+ node_map_add_double(res, "y1", rc[3]);
+ }
+
mp_wakeup_core(mpctx);
}
@@ -4136,7 +4176,7 @@ static bool check_property_autorepeat(char *property, struct MPContext *mpctx)
return true;
// This is a heuristic at best.
- if (prop.type == &m_option_type_flag || prop.type == &m_option_type_choice)
+ if (prop.type->flags & M_OPT_TYPE_CHOICE)
return false;
return true;
@@ -4284,7 +4324,7 @@ static void continue_cmd_list(struct cmd_list_ctx *list)
struct mp_cmd *sub = list->parent->args[0].v.p;
list->parent->args[0].v.p = sub->queue_next;
- ta_xset_parent(sub, NULL);
+ ta_set_parent(sub, NULL);
if (sub->flags & MP_ASYNC_CMD) {
// We run it "detached" (fire & forget)
@@ -4729,6 +4769,21 @@ static void cmd_playlist_next_prev(void *p)
mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE;
}
+static void cmd_playlist_play_index(void *p)
+{
+ struct mp_cmd_ctx *cmd = p;
+ struct MPContext *mpctx = cmd->mpctx;
+ struct playlist *pl = mpctx->playlist;
+ int pos = cmd->args[0].v.i;
+
+ if (pos == -2)
+ pos = playlist_entry_to_index(pl, pl->current);
+
+ mp_set_playlist_entry(mpctx, playlist_entry_from_index(pl, pos));
+ if (cmd->on_osd & MP_ON_OSD_MSG)
+ mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE;
+}
+
static void cmd_sub_step_seek(void *p)
{
struct mp_cmd_ctx *cmd = p;
@@ -4832,6 +4887,10 @@ static void cmd_loadfile(void *p)
}
playlist_add(mpctx->playlist, entry);
+ struct mpv_node *res = &cmd->result;
+ node_init(res, MPV_FORMAT_NODE_MAP, NULL);
+ node_map_add_int64(res, "playlist_entry_id", entry->id);
+
if (!append || (append == 2 && !mpctx->playlist->current)) {
if (mpctx->opts->position_save_on_quit) // requested in issue #1148
mp_write_watch_later_conf(mpctx);
@@ -4855,6 +4914,8 @@ static void cmd_loadlist(void *p)
struct playlist_entry *new = pl->current;
if (!append)
playlist_clear(mpctx->playlist);
+ struct playlist_entry *first = playlist_entry_from_index(pl, 0);
+ int num_entries = pl->num_entries;
playlist_append_entries(mpctx->playlist, pl);
talloc_free(pl);
@@ -4864,6 +4925,13 @@ static void cmd_loadlist(void *p)
if (!append && new)
mp_set_playlist_entry(mpctx, new);
+ struct mpv_node *res = &cmd->result;
+ node_init(res, MPV_FORMAT_NODE_MAP, NULL);
+ if (num_entries) {
+ node_map_add_int64(res, "playlist_entry_id", first->id);
+ node_map_add_int64(res, "num_entries", num_entries);
+ }
+
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
mp_wakeup_core(mpctx);
} else {
@@ -4947,8 +5015,10 @@ static void cmd_stop(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
+ int flags = cmd->args[0].v.i;
- playlist_clear(mpctx->playlist);
+ if (!(flags & 1))
+ playlist_clear(mpctx->playlist);
if (mpctx->stop_play != PT_QUIT)
mpctx->stop_play = PT_STOP;
mp_wakeup_core(mpctx);
@@ -5092,7 +5162,7 @@ static void cmd_rescan_external_files(void *p)
if (s && s->is_external)
mp_switch_track(mpctx, STREAM_SUB, s, 0);
- print_track_list(mpctx, "Track list:\n");
+ print_track_list(mpctx, "Track list:");
}
}
@@ -5391,33 +5461,6 @@ static void cmd_write_watch_later_config(void *p)
mp_write_watch_later_conf(mpctx);
}
-static void cmd_hook_add(void *p)
-{
- struct mp_cmd_ctx *cmd = p;
- struct MPContext *mpctx = cmd->mpctx;
-
- if (!cmd->cmd->sender) {
- MP_ERR(mpctx, "Can be used from client API only.\n");
- cmd->success = false;
- return;
- }
- mp_hook_add(mpctx, cmd->cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i,
- cmd->args[2].v.i, true);
-}
-
-static void cmd_hook_ack(void *p)
-{
- struct mp_cmd_ctx *cmd = p;
- struct MPContext *mpctx = cmd->mpctx;
-
- if (!cmd->cmd->sender) {
- MP_ERR(mpctx, "Can be used from client API only.\n");
- cmd->success = false;
- return;
- }
- mp_hook_continue(mpctx, cmd->cmd->sender, cmd->args[0].v.i);
-}
-
static void cmd_mouse(void *p)
{
struct mp_cmd_ctx *cmd = p;
@@ -5497,8 +5540,14 @@ static void cmd_load_script(void *p)
struct MPContext *mpctx = cmd->mpctx;
char *script = cmd->args[0].v.s;
- if (mp_load_user_script(mpctx, script) < 0)
+ int64_t id = mp_load_user_script(mpctx, script);
+ if (id > 0) {
+ struct mpv_node *res = &cmd->result;
+ node_init(res, MPV_FORMAT_NODE_MAP, NULL);
+ node_map_add_int64(res, "client_id", id);
+ } else {
cmd->success = false;
+ }
}
static void cache_dump_poll(struct MPContext *mpctx)
@@ -5612,75 +5661,93 @@ const struct mp_cmd_def mp_cmds[] = {
{ "seek", cmd_seek,
{
- OPT_TIME("target", v.d, 0),
- OPT_FLAGS("flags", v.i, 0,
- ({"relative", 4|0}, {"-", 4|0},
- {"absolute-percent", 4|1},
- {"absolute", 4|2},
- {"relative-percent", 4|3},
- {"keyframes", 32|8},
- {"exact", 32|16}),
- OPTDEF_INT(4|0)),
+ {"target", OPT_TIME(v.d)},
+ {"flags", OPT_FLAGS(v.i,
+ {"relative", 4|0}, {"-", 4|0},
+ {"absolute-percent", 4|1},
+ {"absolute", 4|2},
+ {"relative-percent", 4|3},
+ {"keyframes", 32|8},
+ {"exact", 32|16}),
+ OPTDEF_INT(4|0)},
// backwards compatibility only
- OPT_CHOICE("legacy", v.i, MP_CMD_OPT_ARG,
- ({"unused", 0}, {"default-precise", 0},
- {"keyframes", 32|8},
- {"exact", 32|16})),
+ {"legacy", OPT_CHOICE(v.i,
+ {"unused", 0}, {"default-precise", 0},
+ {"keyframes", 32|8},
+ {"exact", 32|16}),
+ .flags = MP_CMD_OPT_ARG},
},
.allow_auto_repeat = true,
.scalable = true,
},
{ "revert-seek", cmd_revert_seek,
- {OPT_FLAGS("flags", v.i, MP_CMD_OPT_ARG, ({"mark", 1}))},
+ { {"flags", OPT_FLAGS(v.i, {"mark", 1}), .flags = MP_CMD_OPT_ARG} },
},
- { "quit", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) },
+ { "quit", cmd_quit, { {"code", OPT_INT(v.i), .flags = MP_CMD_OPT_ARG} },
.priv = &(const bool){0} },
- { "quit-watch-later", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) },
+ { "quit-watch-later", cmd_quit, { {"code", OPT_INT(v.i),
+ .flags = MP_CMD_OPT_ARG} },
.priv = &(const bool){1} },
- { "stop", cmd_stop, },
+ { "stop", cmd_stop,
+ { {"flags", OPT_FLAGS(v.i, {"keep-playlist", 1}), .flags = MP_CMD_OPT_ARG} }
+ },
{ "frame-step", cmd_frame_step, .allow_auto_repeat = true,
.on_updown = true },
{ "frame-back-step", cmd_frame_back_step, .allow_auto_repeat = true },
{ "playlist-next", cmd_playlist_next_prev,
{
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0},
- {"force", 1})),
+ {"flags", OPT_CHOICE(v.i,
+ {"weak", 0},
+ {"force", 1}),
+ .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){1},
},
{ "playlist-prev", cmd_playlist_next_prev,
{
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0},
- {"force", 1})),
+ {"flags", OPT_CHOICE(v.i,
+ {"weak", 0},
+ {"force", 1}),
+ .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){-1},
},
+ { "playlist-play-index", cmd_playlist_play_index,
+ {
+ {"index", OPT_CHOICE(v.i, {"current", -2}, {"none", -1}),
+ M_RANGE(-1, INT_MAX)},
+ }
+ },
{ "playlist-shuffle", cmd_playlist_shuffle, },
{ "playlist-unshuffle", cmd_playlist_unshuffle, },
- { "sub-step", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) },
+ { "sub-step", cmd_sub_step_seek, { {"skip", OPT_INT(v.i)} },
.allow_auto_repeat = true, .priv = &(const bool){true} },
- { "sub-seek", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) },
+ { "sub-seek", cmd_sub_step_seek, { {"skip", OPT_INT(v.i)} },
.allow_auto_repeat = true, .priv = &(const bool){false} },
- { "print-text", cmd_print_text, { OPT_STRING("text", v.s, 0) },
+ { "print-text", cmd_print_text, { {"text", OPT_STRING(v.s)} },
.is_noisy = true, .allow_auto_repeat = true },
- { "show-text", cmd_show_text, { OPT_STRING("text", v.s, 0),
- OPT_INT("duration", v.i, 0, OPTDEF_INT(-1)),
- OPT_INT("level", v.i, MP_CMD_OPT_ARG), },
+ { "show-text", cmd_show_text,
+ {
+ {"text", OPT_STRING(v.s)},
+ {"duration", OPT_INT(v.i), OPTDEF_INT(-1)},
+ {"level", OPT_INT(v.i), .flags = MP_CMD_OPT_ARG},
+ },
.is_noisy = true, .allow_auto_repeat = true},
- { "expand-text", cmd_expand_text, { OPT_STRING("text", v.s, 0) },
+ { "expand-text", cmd_expand_text, { {"text", OPT_STRING(v.s)} },
.is_noisy = true },
- { "expand-path", cmd_expand_path, { OPT_STRING("text", v.s, 0) },
+ { "expand-path", cmd_expand_path, { {"text", OPT_STRING(v.s)} },
.is_noisy = true },
{ "show-progress", cmd_show_progress, .allow_auto_repeat = true,
.is_noisy = true },
{ "sub-add", cmd_track_add,
{
- OPT_STRING("url", v.s, 0),
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG,
- ({"select", 0}, {"auto", 1}, {"cached", 2})),
- OPT_STRING("title", v.s, MP_CMD_OPT_ARG),
- OPT_STRING("lang", v.s, MP_CMD_OPT_ARG),
+ {"url", OPT_STRING(v.s)},
+ {"flags", OPT_CHOICE(v.i,
+ {"select", 0}, {"auto", 1}, {"cached", 2}),
+ .flags = MP_CMD_OPT_ARG},
+ {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
+ {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){STREAM_SUB},
.spawn_thread = true,
@@ -5689,11 +5756,12 @@ const struct mp_cmd_def mp_cmds[] = {
},
{ "audio-add", cmd_track_add,
{
- OPT_STRING("url", v.s, 0),
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG,
- ({"select", 0}, {"auto", 1}, {"cached", 2})),
- OPT_STRING("title", v.s, MP_CMD_OPT_ARG),
- OPT_STRING("lang", v.s, MP_CMD_OPT_ARG),
+ {"url", OPT_STRING(v.s)},
+ {"flags", OPT_CHOICE(v.i,
+ {"select", 0}, {"auto", 1}, {"cached", 2}),
+ .flags = MP_CMD_OPT_ARG},
+ {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
+ {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){STREAM_AUDIO},
.spawn_thread = true,
@@ -5702,11 +5770,11 @@ const struct mp_cmd_def mp_cmds[] = {
},
{ "video-add", cmd_track_add,
{
- OPT_STRING("url", v.s, 0),
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG,
- ({"select", 0}, {"auto", 1}, {"cached", 2})),
- OPT_STRING("title", v.s, MP_CMD_OPT_ARG),
- OPT_STRING("lang", v.s, MP_CMD_OPT_ARG),
+ {"url", OPT_STRING(v.s)},
+ {"flags", OPT_CHOICE(v.i, {"select", 0}, {"auto", 1}, {"cached", 2}),
+ .flags = MP_CMD_OPT_ARG},
+ {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
+ {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){STREAM_VIDEO},
.spawn_thread = true,
@@ -5714,26 +5782,26 @@ const struct mp_cmd_def mp_cmds[] = {
.abort_on_playback_end = true,
},
- { "sub-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "sub-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_SUB}, },
- { "audio-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "audio-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_AUDIO}, },
- { "video-remove", cmd_track_remove, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "video-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_VIDEO}, },
- { "sub-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "sub-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_SUB},
.spawn_thread = true,
.can_abort = true,
.abort_on_playback_end = true,
},
- { "audio-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "audio-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_AUDIO},
.spawn_thread = true,
.can_abort = true,
.abort_on_playback_end = true,
},
- { "video-reload", cmd_track_reload, { OPT_INT("id", v.i, 0, OPTDEF_INT(-1)) },
+ { "video-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} },
.priv = &(const int){STREAM_VIDEO},
.spawn_thread = true,
.can_abort = true,
@@ -5742,9 +5810,10 @@ const struct mp_cmd_def mp_cmds[] = {
{ "rescan-external-files", cmd_rescan_external_files,
{
- OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG,
- ({"keep-selection", 1},
- {"reselect", 0})),
+ {"flags", OPT_CHOICE(v.i,
+ {"keep-selection", 1},
+ {"reselect", 0}),
+ .flags = MP_CMD_OPT_ARG},
},
.spawn_thread = true,
.can_abort = true,
@@ -5753,121 +5822,126 @@ const struct mp_cmd_def mp_cmds[] = {