diff options
65 files changed, 1096 insertions, 447 deletions
diff --git a/.travis.yml b/.travis.yml index 18946c05e1..0ab0e75f5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -118,13 +118,21 @@ before_install: fi - | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - remove=$(brew list --formula) + if [[ "$TRAVIS_OSX_IMAGE" == "xcode12.2" ]]; then + remove=$(brew list --formula) + else + remove=$(brew list) + fi keep="gettext pcre2 git" install="autoconf automake pkg-config libtool python freetype fribidi little-cms2 luajit libass ffmpeg" for formula in ${keep[@]}; do remove=("${remove[@]/$formula}"); done for formula in ${install[@]}; do remove=("${remove[@]/$formula}"); done brew remove --force $remove --ignore-dependencies - brew remove --cask $(brew list --cask) + if [[ "$TRAVIS_OSX_IMAGE" == "xcode12.2" ]]; then + brew remove $(brew list --cask) + else + brew cask remove $(brew cask list) + fi brew untap homebrew/cask brew update if [[ -n "$CI_HOMEBREW_HASH" ]]; then diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 1f5fda5b02..4a36041595 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -34,6 +34,9 @@ Interface changes which didn't take the menu bar and Dock into account. The new default behaviour includes both. To revert to the old behavior set this to `whole`. + - add an additional optional `albumart` argument to the `video-add` command, + which tells mpv to load the given video as album art. + - undeprecate `--cache-secs` option --- mpv 0.33.0 --- - add `--d3d11-exclusive-fs` flag to enable D3D11 exclusive fullscreen mode when the player enters fullscreen. diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst index d70286647f..1c0b9e146b 100644 --- a/DOCS/man/ao.rst +++ b/DOCS/man/ao.rst @@ -14,7 +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. + where PulseAudio is used. On BSD systems, ``--ao=oss`` is preferred. Available audio output drivers are: @@ -35,6 +35,9 @@ 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 + ``jack`` JACK (Jack Audio Connection Kit) audio output driver. diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 82b570c228..9f77123a6c 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -801,8 +801,11 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). ``audio-reload [<id>]`` Reload the given audio tracks. See ``sub-reload`` command. -``video-add <url> [<flags> [<title> [<lang>]]]`` - Load the given video file. See ``sub-add`` command. +``video-add <url> [<flags> [<title> [<lang> [<albumart>]]]]`` + Load the given video file. See ``sub-add`` command for common options. + + ``albumart`` (``MPV_FORMAT_FLAG``) + If enabled, mpv will load the given video as album art. ``video-remove [<id>]`` Remove the given video track. See ``sub-remove`` command. diff --git a/DOCS/man/libmpv.rst b/DOCS/man/libmpv.rst index 909a2bb447..7f5d2f5c39 100644 --- a/DOCS/man/libmpv.rst +++ b/DOCS/man/libmpv.rst @@ -21,8 +21,8 @@ C PLUGINS You can write C plugins for mpv. These use the libmpv API, although they do not use the libmpv library itself. -Currently, they must be explicitly enabled at build time with -``--enable-cplugins``. They are available on Linux/BSD platforms only. +They are available on Linux/BSD platforms only and enabled by default if the +compiler supports linking with the ``-rdynamic`` flag. C plugins location ------------------ diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 16625fe0c4..14e58b28e5 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -56,7 +56,7 @@ Track Selection behavior tends to change around with each mpv release. The track selection properties will return the option value outside of - playback (as expected), but during playbac, the affective track + playback (as expected), but during playback, the affective track selection is returned. For example, with ``--aid=auto``, the ``aid`` property will suddenly return ``2`` after playback initialization (assuming the file has at least 2 audio tracks, and the second is the @@ -213,7 +213,7 @@ Playback Control Slow down or speed up playback by the factor given as parameter. If ``--audio-pitch-correction`` (on by default) is used, playing with a - speed higher than normal automatically inserts the ``scaletempo`` audio + speed higher than normal automatically inserts the ``scaletempo2`` audio filter. ``--pause`` @@ -392,10 +392,12 @@ Playback Control difference between the two option is that this option performs a seek on loop, instead of reloading the file. - Note that ``--loop-file`` counts the number of times it causes the player to - seek to the beginning of the file, not the number of full playthroughs. This - means ``--loop-file=1`` will end up playing the file twice. Contrast with - ``--loop-playlist``, which counts the number of full playthroughs. + .. note:: + + ``--loop-file`` counts the number of times it causes the player to + seek to the beginning of the file, not the number of full playthroughs. This + means ``--loop-file=1`` will end up playing the file twice. Contrast with + ``--loop-playlist``, which counts the number of full playthroughs. ``--loop`` is an alias for this option. @@ -1482,11 +1484,14 @@ Video This option is disabled if the ``--no-keepaspect`` option is used. ``--video-rotate=<0-359|no>`` - Rotate the video clockwise, in degrees. Currently supports 90° steps only. - If ``no`` is given, the video is never rotated, even if the file has - rotation metadata. (The rotation value is added to the rotation metadata, - which means the value ``0`` would rotate the video according to the - rotation metadata.) + Rotate the video clockwise, in degrees. If ``no`` is given, the video is + never rotated, even if the file has rotation metadata. (The rotation value + is added to the rotation metadata, which means the value ``0`` would rotate + the video according to the rotation metadata.) + + When using hardware decoding without copy-back, only 90° steps work, while + software decoding and hardware decoding methods that copy the video back to + system memory support all values between 0 and 359. ``--video-zoom=<value>`` Adjust the video display scale factor by the given value. The parameter is @@ -1744,7 +1749,7 @@ Audio ``--audio-pitch-correction=<yes|no>`` If this is enabled (default), playing with a speed different from normal - automatically inserts the ``scaletempo`` audio filter. For details, see + automatically inserts the ``scaletempo2`` audio filter. For details, see audio filter section. ``--audio-device=<name>`` @@ -2997,7 +3002,7 @@ Window applied to window/video with size exceeding size of the screen. ``--on-all-workspaces`` - (X11 only) + (X11 and macOS only) Show the video window on all virtual desktops. ``--geometry=<[W[xH]][+-x+-y][/WS]>``, ``--geometry=<x:y>`` @@ -4574,12 +4579,6 @@ 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 @@ -5178,18 +5177,29 @@ The following video options are currently all specific to ``--vo=gpu`` and ``--interpolation-threshold=<0..1,-1>`` Threshold below which frame ratio interpolation gets disabled (default: - ``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``, + ``0.01``). This is calculated as ``abs(disphz/vfps - 1) < threshold``, where ``vfps`` is the speed-adjusted video FPS, and ``disphz`` the display refresh rate. (The speed-adjusted video FPS is roughly equal to the normal video FPS, but with slowdown and speedup applied. This matters if you use ``--video-sync=display-resample`` to make video run synchronously to the display FPS, or if you change the ``speed`` property.) - The default is intended to almost always enable interpolation if the - playback rate is even slightly different from the display refresh rate. But - note that if you use e.g. ``--video-sync=display-vdrop``, small deviations - in the rate can disable interpolation and introduce a discontinuity every - other minute. + The default is intended to enable interpolation in scenarios where + retiming with the ``--video-sync=display-*`` cannot adjust the speed of + the video sufficiently for smooth playback. For example if a video is + 60.00 FPS and your display refresh rate is 59.94 Hz, interpolation will + never be activated, since the mismatch is within 1% of the refresh + rate. The default also handles the scenario when mpv cannot determine the + container FPS, such as during certain live streams, and may dynamically + toggle interpolation on and off. In this scenario, the default would be to + not use interpolation but rather to allow ``--video-sync=display-*`` to + retime the video to match display refresh rate. See + ``--video-sync-max-video-change`` for more information about how mpv + will retime video. + + Also note that if you use e.g. ``--video-sync=display-vdrop``, small + deviations in the rate can disable interpolation and introduce a + discontinuity every other minute. Set this to ``-1`` to disable this logic. @@ -5703,7 +5713,7 @@ The following video options are currently all specific to ``--vo=gpu`` and ``--deband-threshold=<0..4096>`` The debanding filter's cut-off threshold. Higher numbers increase the debanding strength dramatically but progressively diminish image details. - (Default 64) + (Default 32) ``--deband-range=<1..64>`` The debanding filter's initial radius. The radius increases linearly for @@ -6576,6 +6586,9 @@ Miscellaneous multiple of the display FPS, as long as the speed change does not exceed the value set by ``--video-sync-max-video-change``. + See ``--interpolation-threshold`` for how this option affects + interpolation. + This is mostly for testing, and the option may be randomly changed in the future without notice. diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 3797ad26bd..e07360b24d 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -310,7 +310,7 @@ Available mpv-only filters are: ``<stereo-in>`` Set the stereo mode the video is assumed to be encoded in. Use - ``--vf format:stereo-in=help`` to list all available modes. Check with + ``--vf=format:stereo-in=help`` to list all available modes. Check with the ``stereo3d`` filter documentation to see what the names mean. ``<stereo-out>`` @@ -121,9 +121,9 @@ Libass dependencies (when building libass): - gcc or clang, yasm on x86 and x86_64 - fribidi, freetype, fontconfig development headers (for libass) -- harfbuzz (optional, required for correct rendering of combining characters, - particularly for correct rendering of non-English text on OSX, and - Arabic/Indic scripts on any platform) +- harfbuzz (required for correct rendering of combining characters, particularly + for correct rendering of non-English text on OSX, and Arabic/Indic scripts on + any platform) FFmpeg dependencies (when building FFmpeg): diff --git a/TOOLS/appveyor-install.sh b/TOOLS/appveyor-install.sh index 835850abf1..95e4f7b4ed 100755 --- a/TOOLS/appveyor-install.sh +++ b/TOOLS/appveyor-install.sh @@ -24,6 +24,7 @@ pacman -S --noconfirm --needed \ $MINGW_PACKAGE_PREFIX-ninja \ $MINGW_PACKAGE_PREFIX-rubberband \ $MINGW_PACKAGE_PREFIX-shaderc \ + $MINGW_PACKAGE_PREFIX-spirv-cross \ $MINGW_PACKAGE_PREFIX-uchardet \ $MINGW_PACKAGE_PREFIX-vulkan @@ -51,12 +52,3 @@ pacman -Sc --noconfirm --enable-schannel make -j4 install ) - -# Compile SPIRV-Cross -( - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross - - mkdir build && cd build - cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX .. - ninja install -) diff --git a/TOOLS/umpv b/TOOLS/umpv index 762e73a622..2044e3e926 100755 --- a/TOOLS/umpv +++ b/TOOLS/umpv @@ -31,8 +31,6 @@ import os import socket import errno import subprocess -import fcntl -import stat import string files = sys.argv[1:] @@ -52,7 +50,7 @@ def make_abs(filename): if not is_url(filename): return os.path.abspath(filename) return filename -files = [make_abs(f) for f in files] +files = (make_abs(f) for f in files) SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket") diff --git a/appveyor.yml b/appveyor.yml index 92f229aa3a..a385ac0364 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,5 @@ +image: Visual Studio 2019 + branches: only: - master @@ -15,18 +17,8 @@ shallow_clone: true test: off install: - # Work around age/brokenness of Appveyor's msys2 by adding the new - # maintainer's keyring. - - bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" - - bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - - bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - - bash -lc "pacman -U --noconfirm msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" - # Support for Ada and Objective-C was removed from MSYS2. GCC won't update if - # these packages are installed. - - >- - C:\msys64\usr\bin\pacman -R --noconfirm --noprogressbar - mingw-w64-i686-gcc-ada mingw-w64-i686-gcc-objc mingw-w64-x86_64-gcc-ada - mingw-w64-x86_64-gcc-objc + # Disable checking disk space to speed up install time + - C:\msys64\usr\bin\sed -i 's/^CheckSpace/#CheckSpace/g' /etc/pacman.conf # Update core packages - C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20 # Explicitly kill any remaining msys2 processes after core update diff --git a/audio/aframe.c b/audio/aframe.c index fc5d73be2d..c2c0df7c9b 100644 --- a/audio/aframe.c +++ b/audio/aframe.c @@ -635,18 +635,24 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame AVFrame *av_frame = frame->av_frame; if (av_frame->extended_data != av_frame->data) av_freep(&av_frame->extended_data); // sigh - av_frame->extended_data = - av_mallocz_array(planes, sizeof(av_frame->extended_data[0])); - if (!av_frame->extended_data) - abort(); + if (planes > AV_NUM_DATA_POINTERS) { + av_frame->extended_data = + av_mallocz_array(planes, sizeof(av_frame->extended_data[0])); + if (!av_frame->extended_data) + abort(); + } else { + av_frame->extended_data = av_frame->data; + } av_frame->buf[0] = av_buffer_pool_get(pool->avpool); if (!av_frame->buf[0]) return -1; av_frame->linesize[0] = samples * sstride; for (int n = 0; n < planes; n++) av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size; - for (int n = 0; n < MPMIN(planes, AV_NUM_DATA_POINTERS); n++) - av_frame->data[n] = av_frame->extended_data[n]; + if (planes > AV_NUM_DATA_POINTERS) { + for (int n = 0; n < AV_NUM_DATA_POINTERS; n++) + av_frame->data[n] = av_frame->extended_data[n]; + } av_frame->nb_samples = samples; return 0; diff --git a/audio/filter/af_scaletempo2_internals.c b/audio/filter/af_scaletempo2_internals.c index e348cb37a2..5eb0e6b8d9 100644 --- a/audio/filter/af_scaletempo2_internals.c +++ b/audio/filter/af_scaletempo2_internals.c @@ -389,10 +389,8 @@ static int write_completed_frames_to(struct mp_scaletempo2 *p, static bool can_perform_wsola(struct mp_scaletempo2 *p) { - const int search_block_size = p->num_candidate_blocks - + (p->ola_window_size - 1); return p->target_block_index + p->ola_window_size <= p->input_buffer_frames - && p->search_block_index + search_block_size <= p->input_buffer_frames; + && p->search_block_index + p->search_block_size <= p->input_buffer_frames; } // number of frames needed until a wsola iteration can be performed @@ -403,6 +401,14 @@ static int frames_needed(struct mp_scaletempo2 *p) p->search_block_index + p->search_block_size - p->input_buffer_frames)); } +static void resize_input_buffer(struct mp_scaletempo2 *p, int size) +{ + if (size > p->input_buffer_size) { + p->input_buffer_size = size; + p->input_buffer = realloc_2d(p->input_buffer, p->channels, size); + } +} + int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p, uint8_t **planes, int frame_size, bool final) { @@ -411,7 +417,8 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p, int total_fill = final ? needed : read; if (total_fill == 0) return 0; - assert(total_fill + p->input_buffer_frames <= p->input_buffer_size); + int required_size = total_fill + p->input_buffer_frames; + resize_input_buffer(p, required_size); for (int i = 0; i < p->channels; ++i) { memcpy(p->input_buffer[i] + p->input_buffer_frames, @@ -427,11 +434,9 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p, static bool target_is_within_search_region(struct mp_scaletempo2 *p) { - const int search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1); - return p->target_block_index >= p->search_block_index && p->target_block_index + p->ola_window_size - <= p->search_block_index + search_block_size; + <= p->search_block_index + p->search_block_size; } @@ -715,8 +720,7 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate) p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size); p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size); - p->input_buffer_size = 4 * MPMAX(p->ola_window_size, p->search_block_size); - p->input_buffer = realloc_2d(p->input_buffer, p->channels, p->input_buffer_size); + resize_input_buffer(p, 4 * MPMAX(p->ola_window_size, p->search_block_size)); p->input_buffer_frames = 0; p->energy_candidate_blocks = realloc(p->energy_candidate_blocks, diff --git a/audio/out/ao.c b/audio/out/ao.c index 52a38b63be..7c347cb138 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -35,6 +35,7 @@ #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; @@ -71,6 +72,9 @@ 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, diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c new file mode 100644 index 0000000000..11b182e52d --- /dev/null +++ b/audio/out/ao_oss.c @@ -0,0 +1,410 @@ +/* + * OSS audio output driver + * + * Original author: A'rpi + * Support for >2 output channels added 2001-11-25 + * - Steve Davies <steve@daviesfam.org> + * Rozhuk Ivan <rozhuk.im@gmail.com> 2020 + * + * 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 <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/soundcard.h> +#include <sys/stat.h> +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif +#include <sys/types.h> + +#include "config.h" +#include "audio/format.h" +#include "common/msg.h" +#include "options/options.h" +#include "osdep/endian.h" +#include "osdep/io.h" +#include "ao.h" +#include "internal.h" + +#ifndef AFMT_AC3 +#define AFMT_AC3 -1 +#endif + +#define PATH_DEV_DSP "/dev/dsp" + +struct priv { + int dsp_fd; + bool playing; + double bps; /* Bytes per second. */ +}; + +/* 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_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} +}; + +#define MP_WARN_IOCTL_ERR(__ao) \ + MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \ + __FUNCTION__, errno, strerror(errno)) + + +static void uninit(struct ao *ao); + + +static void device_descr_get(size_t dev_idx, char *buf, size_t buf_size) +{ +#if defined(__DragonFly__) || defined(__FreeBSD__) + char dev_path[32]; + size_t tmp = (buf_size - 1); + + snprintf(dev_path, sizeof(dev_path), "dev.pcm.%zu.%%desc", dev_idx); + if (sysctlbyname(dev_path, buf, &tmp, NULL, 0) != 0) { + tmp = 0; + } + buf[tmp] = 0x00; +#elif defined(SOUND_MIXER_INFO) + size_t tmp = 0; + char dev_path[32]; + mixer_info mi; + + snprintf(dev_path, sizeof(dev_path), PATH_DEV_MIXER"%zu", dev_idx); + int fd = open(dev_path, O_RDONLY); + if (ioctl(fd, SOUND_MIXER_INFO, &mi) == 0) { + strncpy(buf, mi.name, buf_size); + tmp = (buf_size - 1); + } + close(fd); + buf[tmp] = 0x00; +#else + buf[0] = 0x00; +#endif +} + +static int format2oss(int format) +{ + for (size_t i = 0; format_table[i][0] != -1; i++) { + if (format_table[i][1] == format) + return format_table[i][0]; + } + return -1; +} + +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; + } + + return (ioctl(p->dsp_fd, SNDCTL_DSP_SETFMT, &oss_format) != -1); +} + +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + struct mp_chmap channels = ao->channels; + audio_buf_info info; + size_t i; + int format, samplerate, nchannels, reqchannels, trig = 0; + int best_sample_formats[AF_FORMAT_COUNT + 1]; + const char *device = ((ao->device) ? ao->device : PATH_DEV_DSP); + + /* Opening device. */ + MP_VERBOSE(ao, "Using '%s' audio device.\n", device); + p->dsp_fd = open(device, (O_WRONLY | O_CLOEXEC)); + if (p->dsp_fd < 0) { + MP_ERR(ao, "Can't open audio device %s: %s.\n", + device, mp_strerror(errno)); + goto err_out; + } + + /* Selecting sound format. */ + format = af_fmt_from_planar(ao->format); + af_get_best_sample_formats(format, best_sample_formats); + for (i = 0; best_sample_formats[i]; i++) { + format = best_sample_formats[i]; + if (try_format(ao, &format)) + break; + } + if (!format) { + MP_ERR(ao, "Can't set sample format.\n"); + goto err_out; + } + MP_VERBOSE(ao, "Sample format: %s\n", af_fmt_to_str(format)); + + /* Channels count. */ + if (af_fmt_is_spdif(format)) { + /* 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 err_out; + } + } else { + struct mp_chmap_sel sel = {0}; + for (i = 0; i < MP_ARRAY_SIZE(oss_layouts); i++) { + mp_chmap_sel_add_map(&sel, &oss_layouts[i]); + } + if (!ao_chmap_sel_adjust(ao, &sel, &channels)) + goto err_out; + nchannels = reqchannels = channels.num; + if (ioctl(p->dsp_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) { + MP_ERR(ao, "Failed to set audio device to %d channels.\n", + reqchannels); + goto err_out_ioctl; + } + if (nchannels != reqchannels) { + /* Update number of channels to OSS suggested value. */ + if (!ao_chmap_sel_get_def(ao, &sel, &channels, nchannels)) + goto err_out; + } + MP_VERBOSE(ao, "Using %d channels (requested: %d).\n", + channels.num, reqchannels); + } + + /* Sample rate. */ + samplerate = ao->samplerate; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SPEED, &samplerate) == -1) + goto err_out_ioctl; + MP_VERBOSE(ao, "Using %d Hz samplerate.\n", samplerate); + + /* Get buffer size. */ + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) + goto err_out_ioctl; + /* See ao.c ao->sstride initializations and get_state(). */ + ao->device_buffer = ((info.fragstotal * info.fragsize) / + af_fmt_to_bytes(format)); + if (!af_fmt_is_planar(format)) { + ao->device_buffer /= channels.num; + } + + /* Do not start playback after data written. */ + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + goto err_out_ioctl; + + /* Update sound params. */ + ao->format = format; + ao->samplerate = samplerate; + ao->channels = channels; + p->bps = (channels.num * samplerate * af_fmt_to_bytes(format)); + p->playing = false; + + return 0; + +err_out_ioctl: + MP_WARN_IOCTL_ERR(ao); +err_out: + uninit(ao); + return -1; +} + +static void uninit(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (p->dsp_fd == -1) + return; + ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL); + close(p->dsp_fd); + p->dsp_fd = -1; + p->playing = false; +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + ao_control_vol_t *vol = (ao_control_vol_t *)arg; + int v; + + if (p->dsp_fd < 0) + return CONTROL_ERROR; + + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; + } + vol->right = ((v & 0xff00) >> 8); + vol->left = (v & 0x00ff); + return CONTROL_OK; + case AOCONTROL_SET_VOLUME: + v = ((int)vol->right << 8) | (int)vol->left; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; + } + return CONTROL_OK; + } + + return CONTROL_UNKNOWN; +} + +static void reset(struct ao *ao) +{ + struct priv *p = ao->priv; + int trig = 0; + + /* Clear buf and do not start playback after data written. */ + p->playing = false; + if (ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL) == -1 || + ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + { + MP_WARN_IOCTL_ERR(ao); + MP_WARN(ao, "Force reinitialize audio device.\n"); + uninit(ao); + init(ao); + } +} + +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + int trig = PCM_ENABLE_OUTPUT; + + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) { + MP_WARN_IOCTL_ERR(ao); + return; + } + p->playing = true; +} + +static bool audio_write(struct ao *ao, void **data, int samples) +{ + struct priv *p = ao->priv; + ssize_t rc; + const size_t size = (samples * ao->sstride); + + if (size == 0) + return true; + + while ((rc = write(p->dsp_fd, data[0], size)) == -1) { + if (errno == EINTR) + continue; + MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n", + errno, strerror(errno)); + p->playing = false; + return false; + } + if ((size_t)rc != size) { + MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n", + size, (size_t)rc); + p->playing = false; + return false; + } + + return true; +} + +static void get_state(struct ao *ao, struct mp_pcm_state *state) +{ + struct priv *p = ao->priv; + audio_buf_info info; + int odelay; + + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1 || + ioctl(p->dsp_fd, SNDCTL_DSP_GETODELAY, &odelay) == -1) + { + MP_WARN_IOCTL_ERR(ao); + p->playing = false; + memset(state, 0x00, sizeof(struct mp_pcm_state)); + state->delay = 0.0; + return; + } + state->free_samples = (info.bytes / ao->sstride); + state->queued_samples = (ao->device_buffer - state->free_samples); + state->delay = (odelay / p->bps); + state->playing = p->playing; +} + +static void list_devs(struct ao *ao, struct ao_device_list *list) +{ + struct stat st; + char dev_path[32] = PATH_DEV_DSP, dev_descr[256] = "Default"; + struct ao_device_desc dev = {.name = dev_path, .desc = dev_descr}; + + if (stat(PATH_DEV_DSP, &st) == 0) { + ao_device_list_add(list, ao, &dev); + } + + /* Auto detect. */ + for (size_t i = 0, fail_cnt = 0; fail_cnt < 8; i ++, fail_cnt ++) { + snprintf(dev_path, sizeof(dev_path), PATH_DEV_DSP"%zu", i); + if (stat(dev_path, &st) != 0) + continue; + device_descr_get(i, dev_descr, sizeof(dev_descr)); + ao_device_list_add(list, ao, &dev); + fail_cnt = 0; /* Reset fail counter. */ + } +} + +const struct ao_driver audio_out_oss = { + .name = "oss", + .description = "OSS/ioctl audio output", + .init = init, + .uninit = uninit, + .control = control, + .reset = reset, + .start = start, + .write = audio_write, + .get_state = get_state, + .list_devs = list_devs, + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .dsp_fd = -1, + .playing = false, + }, +}; diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c index b09d26241e..aa802e77a5 100644 --- a/audio/out/ao_pulse.c +++ b/audio/out/ao_pulse.c @@ -696,9 +696,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) case AOCONTROL_SET_MUTE: case AOCONTROL_SET_VOLUME: { - pa_operation *o; - pa_threaded_mainloop_lock(priv->mainloop); + priv->retval = 0; uint32_t stream_index = pa_stream_get_index(priv->stream); if (cmd == AOCONTROL_SET_VOLUME) { const ao_control_vol_t *vol = arg; @@ -711,27 +710,26 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) volume.values[0] = VOL_MP2PA(vol->left); volume.values[1] = VOL_MP2PA(vol->right); } - o = pa_context_set_sink_input_volume(priv->context, stream_index, - &volume, NULL, NULL); - if (!o) { - pa_threaded_mainloop_unlock(priv->mainloop); + if (!waitop(priv, pa_context_set_sink_input_volume(priv->context, + stream_index, + &volume, + context_success_cb, ao)) || + !priv->retval) { GENERIC_ERR_MSG("pa_context_set_sink_input_volume() failed"); return CONTROL_ERROR; } } else if (cmd == AOCONTROL_SET_MUTE) { const bool *mute = arg; - o = pa_context_set_sink_input_mute(priv->context, stream_index, - *mute, NULL, NULL); - if (!o) { - pa_threaded_mainloop_unlock(priv->mainloop); + if (!waitop(priv, pa_context_set_sink_input_mute(priv->context, + stream_index, + *mute, + context_success_cb, ao)) || + !priv->retval) { GENERIC_ERR_MSG("pa_context_set_sink_input_mute() failed"); return CONTROL_ERROR; } } else abort(); - /* We don't wait for completion here */ - pa_operation_unref(o); - pa_threaded_mainloop_unlock(priv->mainloop); return CONTROL_OK; } diff --git a/common/msg.c b/common/msg.c index 7e271d1b8b..81c7f654f3 100644 --- a/common/msg.c +++ b/common/msg.c @@ -126,6 +126,8 @@ static void update_loglevel(struct mp_log *log) struct mp_log_root *root = log->root; pthread_mutex_lock(&root->lock); log->level = MSGL_STATUS + root->verbose; // default log level + if (root->really_quiet) + log->level = -1; for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) { if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0])) log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]); @@ -143,8 +145,6 @@ static void update_loglevel(struct mp_log *log) if (log->root->stats_file) log->level = MPMAX(log->level, MSGL_STATS); log->level = MPMIN(log->level, log->max_level); - if (root->really_quiet) - log->level = -1; atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter)); pthread_mutex_unlock(&root->lock); } diff --git a/demux/demux.c b/demux/demux.c index 348d665d49..53ad455b46 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -121,8 +121,7 @@ const struct m_sub_options demux_conf = { 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"}, + {"cache-secs", OPT_DOUBLE(min_secs_cache), M_RANGE(0, DBL_MAX)}, {"access-references", OPT_FLAG(access_references)}, {"demuxer-seekable-cache", OPT_CHOICE(seekable_cache, {"auto", -1}, {"no", 0}, {"yes", 1})}, @@ -2827,7 +2826,7 @@ done: return out_pkt; } -void demuxer_help(struct mp_log *log) +int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name) { int i; @@ -2837,6 +2836,9 @@ void demuxer_help(struct mp_log *log) mp_info(log, "%10s %s\n", demuxer_list[i]->name, demuxer_list[i]->desc); } + mp_info(log, "\n"); + + return M_OPT_EXIT; } static const char *d_level(enum demux_check level) diff --git a/demux/demux.h b/demux/demux.h index 80c9a80fc7..ed414f1816 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -293,7 +293,7 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream, void demuxer_refresh_track(struct demuxer *demuxer, struct sh_stream *stream, double ref_pts); -void demuxer_help(struct mp_log *log); +int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name); int demuxer_add_attachment(struct demuxer *demuxer, char *name, char *type, void *data, size_t data_size); diff --git a/demux/demux_mf.c b/demux/demux_mf.c index 161e0cb92d..40f94f4e4e 100644 --- a/demux/demux_mf.c +++ b/demux/demux_mf.c @@ -121,7 +121,8 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename goto exit_mf; } - char *fname = talloc_size(mf, strlen(filename) + 32); + size_t fname_avail = strlen(filename) + 32; + char *fname = talloc_size(mf, fname_avail); #if HAVE_GLOB if (!strchr(filename, '%')) { @@ -148,10 +149,44 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename } #endif + // We're using arbitrary user input as printf format with 1 int argument. + // Any format which uses exactly 1 int argument would be valid, but for + // simplicity we reject all conversion specifiers except %% and simple + // integer specifier: %[.][NUM]d where NUM is 1-3 digits (%.d is valid) + const char *f = filename; + int MAXDIGS = 3, nspec = 0, bad_spec = 0, c; + + while (nspec < 2 && (c = *f++)) { + if (c != '%') + continue; + if (*f != '%') { + nspec++; // conversion specifier which isn't %% + if (*f == '.') + f++; + for (int ndig = 0; mp_isdigit(*f) && ndig < MAXDIGS; ndig++, f++) + /* no-op */; + if (*f != 'd') { + bad_spec++; // not int, or beyond our validation capacity + break; + } + } + // *f is '%' or 'd' + f++; + } + + // nspec==0 (zero specifiers) is rejected because fname wouldn't advance. + if (bad_spec || nspec != 1) { + mp_err(log, "unsupported expr format: '%s'\n", filename); + goto exit_mf; + } + mp_info(log, "search expr: %s\n", filename); while (error_count < 5) { - sprintf(fname, filename, count++); + if (snprintf(fname, fname_avail, filename, count++) >= fname_avail) { + mp_err(log, "format result too long: '%s'\n", filename); + goto exit_mf; + } if (!mp_path_exists(fname)) { error_count++; mp_verbose(log, "file not found: '%s'\n", fname); @@ -254,6 +289,8 @@ static const struct { { "jls", "ljpeg" }, { "thm", "mjpeg" }, { "db", "mjpeg" }, + { "pcd", "photocd" }, + { "pfm", "pfm" }, { "pcx", "pcx" }, { "png", "png" }, { "pns", "png" }, diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c index a5394dd35a..5dd3a4adff 100644 --- a/filters/f_auto_filters.c +++ b/filters/f_auto_filters.c @@ -329,9 +329,9 @@ static void aspeed_process(struct mp_filter *f) if (req_filter) { if (req_filter == 1) { - MP_VERBOSE(f, "adding scaletempo\n"); + MP_VERBOSE(f, "adding scaletempo2\n"); p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, - "scaletempo", NULL); + "scaletempo2", NULL); } else if (req_filter == 2) { MP_VERBOSE(f, "adding drop\n"); p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, diff --git a/filters/f_auto_filters.h b/filters/f_auto_filters.h index 98043c9301..f315084158 100644 --- a/filters/f_auto_filters.h +++ b/filters/f_auto_filters.h @@ -9,5 +9,5 @@ struct mp_filter *mp_deint_create(struct mp_filter *parent); // Rotate according to mp_image.rotate and VO capabilities. struct mp_filter *mp_autorotate_create(struct mp_filter *parent); -// Insert a filter that inserts scaletempo depending on speed settings. +// Insert a filter that inserts scaletempo2 depending on speed settings. struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent); diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c index 397c3a0a98..9be8743e53 100644 --- a/filters/f_decoder_wrapper.c +++ b/filters/f_decoder_wrapper.c @@ -110,16 +110,19 @@ struct dec_wrapper_opts { 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); +static int decoder_list_help(struct mp_log *log, const m_option_t *opt, + struct bstr name); 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)}, + {"ad", OPT_STRING(audio_decoders), + .help = decoder_list_help}, + {"vd", OPT_STRING(video_decoders), + .help = decoder_list_help}, + {"audio-spdif", OPT_STRING(audio_spdif), + .help = decoder_list_help}, {"video-rotate", OPT_CHOICE(video_rotate, {"no", -1}), .flags = UPDATE_IMGPAR, M_RANGE(0, 359)}, {"video-aspect-override", OPT_ASPECT(movie_aspect), @@ -232,11 +235,9 @@ struct priv { 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) +static int decoder_list_help(struct mp_log *log, const m_option_t *opt, + struct bstr name) { - 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); diff --git a/options/m_option.c b/options/m_option.c index 4d222df500..4c199967b3 100644 --- a/options/m_option.c +++ b/options/m_option.c @@ -61,6 +61,31 @@ const char m_option_path_separator = OPTION_PATH_SEPARATOR; #define OPT_INT_MAX(opt, T, Tm) ((opt)->min < (opt)->max \ ? ((opt)->max >= (double)(Tm) ? (Tm) : (T)((opt)->max)) : (Tm)) +int m_option_parse(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param, void *dst) +{ + int r = M_OPT_INVALID; + if (bstr_equals0(param, "help") && opt->help) { + r = opt->help(log, opt, name); + if (r < 0) + return r; + } + + r = opt->type->parse(log, opt, name, param, dst); + if (r < 0) + return r; + + if (opt->validate) { + r = opt->validate(log, opt, name, dst); + if (r < 0) { + if (opt->type->free) + opt->type->free(dst); + return r; + } + } + return 1; +} + char *m_option_strerror(int code) { switch (code) { @@ -1192,13 +1217,6 @@ const m_option_type_t m_option_type_aspect = { static int parse_str(struct mp_log *log, const m_option_t *opt, struct bstr name, struct bstr param, void *dst) { - m_opt_string_validate_fn validate = opt->priv; - if (validate) { - int r = validate(log, opt, name, param); - if (r < 0) - return r; - } - if (dst) { talloc_free(VAL(dst)); VAL(dst) = bstrdup0(NULL, param); diff --git a/options/m_option.h b/options/m_option.h index afd86eaf96..322f120a06 100644 --- a/options/m_option.h +++ b/options/m_option.h @@ -189,9 +189,15 @@ struct m_opt_choice_alternatives { const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices, int value); -// For OPT_STRING_VALIDATE(). Behaves like m_option_type.parse(). +// Validator function signatures. Required to properly type the param value. +typedef int (*m_opt_generic_validate_fn)(struct mp_log *log, const m_option_t *opt, + struct bstr name, void *value); + typedef int (*m_opt_string_validate_fn)(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); +typedef int (*m_opt_int_validate_fn)(struct mp_log *log, const m_option_t *opt, + struct bstr name, const int *value); + // m_option.priv points to this if OPT_SUBSTRUCT is used struct m_sub_options { @@ -270,6 +276,9 @@ struct m_option_type { // Parse the data from a string. /** It is the only required function, all others can be NULL. + * Generally should not be called directly outside of the options module, + * but instead through \ref m_option_parse which calls additional option + * specific callbacks during the process. * * \param log for outputting parser error or help messages * \param opt The option that is parsed. @@ -384,6 +393,12 @@ struct m_option { // Print a warning when this option is used (for options with no direct // replacement.) const char *deprecation_message; + + // Optional function that validates a param value for this option. + m_opt_generic_validate_fn validate; + + // Optional function that displays help. Will replace type-specific help. + int (*help)(struct mp_log *log, const m_option_t *opt, struct bstr name); }; char *format_file_size(int64_t size); @@ -490,12 +505,13 @@ char *format_file_size(int64_t size); char *m_option_strerror(int code); -// Helper to parse options, see \ref m_option_type::parse. -static inline int m_option_parse(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param, void *dst) -{ - return opt->type->parse(log, opt, name, param, dst); -} +// Base function to parse options. Includes calling help and validation +// callbacks. Only when this functionality is for some reason required to not +// happen should the parse function pointer be utilized by itself. +// +// See \ref m_option_type::parse. +int m_option_parse(struct mp_log *log, const m_option_t *opt, + struct bstr name, struct bstr param, void *dst); // Helper to print options, see \ref m_option_type::print. static inline char *m_option_print(const m_option_t *opt, const void *val_ptr) @@ -661,9 +677,15 @@ extern const char m_option_path_separator; #define OPT_CHANNELS(field) \ OPT_TYPED_FIELD(m_option_type_channels, struct m_channels, field) +#define OPT_INT_VALIDATE(field, validate_fn) \ + OPT_TYPED_FIELD(m_option_type_int, int, field), \ + .validate = (m_opt_generic_validate_fn) \ + MP_EXPECT_TYPE(m_opt_int_validate_fn, validate_fn) + #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) + .validate = (m_opt_generic_validate_fn) \ + MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn) #define M_CHOICES(...) \ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ __VA_ARGS__, {0}} diff --git a/options/options.c b/options/options.c index 8aadc87ab9..2a168da4e5 100644 --- a/options/options.c +++ b/options/options.c @@ -46,6 +46,7 @@ #include "player/core.h" #include "player/command.h" #include "stream/stream.h" +#include "demux/demux.h" #if HAVE_DRM #include "video/out/drm_common.h" @@ -514,9 +515,9 @@ static const m_option_t mp_opts[] = { #endif // demuxer.c - select audio/sub file/demuxer - {"demuxer", OPT_STRING(demuxer_name)}, - {"audio-demuxer", OPT_STRING(audio_demuxer_name)}, - {"sub-demuxer", OPT_STRING(sub_demuxer_name)}, + {"demuxer", OPT_STRING(demuxer_name), .help = demuxer_help}, + {"audio-demuxer", OPT_STRING(audio_demuxer_name), .help = demuxer_help}, + {"sub-demuxer", OPT_STRING(sub_demuxer_name), .help = demuxer_help}, {"demuxer-thread", OPT_FLAG(demuxer_thread)}, {"demuxer-termination-timeout", OPT_DOUBLE(demux_termination_timeout)}, {"demuxer-cache-wait", OPT_FLAG(demuxer_cache_wait)}, diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m index 11fdda5e53..6fefcb97aa 100644 --- a/osdep/macosx_menubar.m +++ b/osdep/macosx_menubar.m @@ -218,6 +218,13 @@ @"target" : self, @"cmd" : @"cycle ontop" }], + [NSMutableDictionary dictionaryWithDictionary:@{ + @"name" : @"Toggle Visibility on All Workspaces", + @"action" : @"cmd:", + @"key" : @"", + @"target" : self, + @"cmd" : @"cycle on-all-workspaces" + }], #if HAVE_MACOS_TOUCHBAR @{ @"name": @"separator" }, [NSMutableDictionary dictionaryWithDictionary:@{ diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h index 0aa5a052bb..a03b68c0e6 100644 --- a/osdep/macosx_touchbar.h +++ b/osdep/macosx_touchbar.h @@ -41,5 +41,6 @@ struct mpv_event; @property(nonatomic, retain) NSDictionary *touchbarItems; @property(nonatomic, assign) double duration; @property(nonatomic, assign) double position; +@property(nonatomic, assign) int pause; @end diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m index c189e32354..ccce8f773d 100644 --- a/osdep/macosx_touchbar.m +++ b/osdep/macosx_touchbar.m @@ -24,6 +24,7 @@ @synthesize touchbarItems = _touchbar_items; @synthesize duration = _duration; @synthesize position = _position; +@synthesize pause = _pause; - (id)init { @@ -86,58 +87,25 @@ @"name": @"Time Left" }] }; - } - return self; -} - --(void)processEvent:(struct mpv_event *)event -{ - switch (event->event_id) { - case MPV_EVENT_END_FILE: { - self.position = 0; - self.duration = 0; - break; - } - case MPV_EVENT_PROPERTY_CHANGE: { - [self handlePropertyChange:(mpv_event_property *)event->data]; - break; - } - } -} - --(void)handlePropertyChange:(struct mpv_event_property *)property -{ - NSString *name = [NSString stringWithUTF8String:property->name]; - mpv_format format = property->format; - if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) { - self.position = *(double *)property->data; - self.position = self.position < 0 ? 0 : self.position; - [self updateTouchBarTimeItems]; - } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) { - self.duration = *(double *)property->data; - [self updateTouchBarTimeItems]; - } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) { - NSButton *playButton = self.touchbarItems[play][@"view"]; - if (*(int *)property->data) { - playButton.image = self.touchbarItems[play][@"imageAlt"]; - } else { - playButton.image = self.touchbarItems[play][@"image"]; - } + [self addObserver:self forKeyPath:@"visible" options:0 context:nil]; } + return self; } - (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) { - NSSliderTouchBarItem *tbItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier]; - tbItem.slider.minValue = 0.0f; - tbItem.slider.maxValue = 100.0f; - tbItem.target = self; - tbItem.action = @selector(seekbarChanged:); + NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; + NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)]; + slider.minValue = 0.0f; + slider.maxValue = 100.0f; + tbItem.view = slider; tbItem.customizationLabel = self.touchbarItems[identifier][@"name"]; - [self.touchbarItems[identifier] setObject:tbItem.slider forKey:@"view"]; + [self.touchbarItems[identifier] setObject:slider forKey:@"view"]; + [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"]; + [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil]; return tbItem; } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) { NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -146,6 +114,8 @@ tbItem.view = tbButton; tbItem.customizationLabel = self.touchbarItems[identifier][@"name"]; [self.touchbarItems[identifier] setObject:tbButton forKey:@"view"]; + [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"]; + [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil]; return tbItem; } else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) { NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; @@ -154,12 +124,124 @@ tbItem.view = tbText; tbItem.customizationLabel = self.touchbarItems[identifier][@"name"]; [self.touchbarItems[identifier] setObject:tbText forKey:@"view"]; + [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"]; + [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil]; return tbItem; } return nil; } +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary<NSKeyValueChangeKey,id> *)change + context:(void *)context { + if ([keyPath isEqualToString:@"visible"]) { + NSNumber *visible = [object valueForKey:@"visible"]; + if (visible.boolValue) { + [self updateTouchBarTimeItems]; + [self updatePlayButton]; + } + } +} + +- (void)updateTouchBarTimeItems +{ + if (!self.isVisible) + return; + + [self updateSlider]; + [self updateTimeLeft]; + [self updateCurrentPosition]; +} + +- (void)updateSlider +{ + NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"]; + if (!tbItem.visible) + return; + + NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"]; + + if (self.duration <= 0) { + seekSlider.enabled = NO; + seekSlider.doubleValue = 0; + } else { + seekSlider.enabled = YES; + if (!seekSlider.highlighted) + seekSlider.doubleValue = (self.position/self.duration)*100; + } +} + +- (void)updateTimeLeft +{ + NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"]; + if (!tbItem.visible) + return; + + NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"]; + + [self removeConstraintForIdentifier:timeLeft]; + if (self.duration <= 0) { + timeLeftItem.stringValue = @""; + } else { + int left = (int)(floor(self.duration)-floor(self.position)); + NSString *leftFormat = [self formatTime:left]; + NSString *durFormat = [self formatTime:self.duration]; + timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat]; + [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat] + forIdentifier:timeLeft]; + } +} + +- (void)updateCurrentPosition +{ + NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"]; + if (!tbItem.visible) + return; + + NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"]; + NSString *posFormat = [self formatTime:(int)floor(self.position)]; + curPosItem.stringValue = posFormat; + + [self removeConstraintForIdentifier:currentPosition]; + if (self.duration <= 0) { + [self applyConstraintFromString:[self formatTime:self.position] + forIdentifier:currentPosition]; + } else { + NSString *durFormat = [self formatTime:self.duration]; + [self applyConstraintFromString:durFormat forIdentifier:currentPosition]; + } +} + +- (void)updatePlayButton +{ + NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"]; + if (!self.isVisible || !tbItem.visible) + return; + + NSButton *playButton = self.touchbarItems[play][@"view"]; + if (self.pause) { + playButton.image = self.touchbarItems[play][@"imageAlt"]; + } else { + playButton.image = self.touchbarItems[play][@"image"]; + } +} + +- (void)buttonAction:(NSButton *)sender +{ + NSString *identifier = [self getIdentifierFromView:sender]; + [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]]; +} + +- (void)seekbarChanged:(NSSlider *)slider +{ + NSString *identifier = [self getIdentifierFromView:slider]; + NSString *seek = [NSString stringWithFormat: + self.touchbarItems[identifier][@"cmd"], slider.doubleValue]; + [self.app queueCommand:(char *)[seek UTF8String]]; +} + - (NSString *)formatTime:(int)time { int seconds = time % 60; @@ -204,48 +286,6 @@ } } -- (void)updateTouchBarTimeItemConstrains -{ - [self removeConstraintForIdentifier:currentPosition]; - [self removeConstraintForIdentifier:timeLeft]; - - if (self.duration <= 0) { - [self applyConstraintFromString:[self formatTime:self.position] - forIdentifier:currentPosition]; - } else { - NSString *durFormat = [self formatTime:self.duration]; - - [self applyConstraintFromString:durFormat forIdentifier:currentPosition]; - [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat] - forIdentifier:timeLeft]; - } -} - -- (void)updateTouchBarTimeItems -{ - NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"]; - NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"]; - NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"]; - - if (self.duration <= 0) { - seekSlider.enabled = NO; - seekSlider.doubleValue = 0; - timeLeftItem.stringValue = @""; - } - else { - seekSlider.enabled = YES; - if (!seekSlider.highlighted) - seekSlider.doubleValue = (self.position/self.duration)*100; - int left = (int)(floor(self.duration)-floor(self.position)); - NSString *leftFormat = [self formatTime:left]; - timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat]; - } - NSString *posFormat = [self formatTime:(int)floor(self.position)]; - curPosItem.stringValue = posFormat; - - [self updateTouchBarTimeItemConstrains]; -} - - (NSString *)getIdentifierFromView:(id)view { NSString *identifier; @@ -255,18 +295,40 @@ return identifier; } -- (void)buttonAction:(NSButton *)sender +- (void)processEvent:(struct mpv_event *)event { - NSString *identifier = [self getIdentifierFromView:sender]; - [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]]; + switch (event->event_id) { + case MPV_EVENT_END_FILE: { + self.position = 0; + self.duration = 0; + break; + } + case MPV_EVENT_PROPERTY_CHANGE: { + [self handlePropertyChange:(mpv_event_property *)event->data]; + break; + } + } } -- (void)seekbarChanged:(NSSliderTouchBarItem *)sender +- (void)handlePropertyChange:(struct mpv_event_property *)property { - NSString *identifier = [self getIdentifierFromView:sender.slider]; - NSString *seek = [NSString stringWithFormat: - self.touchbarItems[identifier][@"cmd"], sender.slider.doubleValue]; - [self.app queueCommand:(char *)[seek UTF8String]]; + NSString *name = [NSString stringWithUTF8String:property->name]; + mpv_format format = property->format; + + if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) { + double newPosition = *(double *)property->data; + newPosition = newPosition < 0 ? 0 : newPosition; + if ((int)(floor(newPosition) - floor(self.position)) != 0) { + self.position = newPosition; + [self updateTouchBarTimeItems]; + } + } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) { + self.duration = *(double *)property->data; + [self updateTouchBarTimeItems]; + } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) { + self.pause = *(int *)property->data; + [self updatePlayButton]; + } } @end diff --git a/player/audio.c b/player/audio.c index 50c0faaf09..bf35bfb02a 100644 --- a/player/audio.c +++ b/player/audio.c @@ -516,7 +516,8 @@ void reinit_audio_chain(struct MPContext *mpctx) struct track *track = NULL; track = mpctx->current_track[0][STREAM_AUDIO]; if (!track || !track->stream) { - uninit_audio_out(mpctx); + if (!mpctx->encode_lavc_ctx) + uninit_audio_out(mpctx); error_on_track(mpctx, track); return; } diff --git a/player/command.c b/player/command.c index 098a11d24f..e210963447 100644 --- a/player/command.c +++ b/player/command.c @@ -3917,6 +3917,7 @@ static const struct property_osd_display { {"taskbar-progress", "Progress in taskbar"}, {"snap-window", "Snap to screen edges"}, {"ontop", "Stay on top"}, + {"on-all-workspaces", "Visibility on all workspaces"}, {"border", "Border"}, {"framedrop", "Framedrop"}, {"deinterlace", "Deinterlace"}, @@ -5240,6 +5241,8 @@ static void cmd_track_add(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; int type = *(int *)cmd->priv; + bool is_albumart = type == STREAM_VIDEO && + cmd->args[4].v.i; if (mpctx->stop_play) { cmd->success = false; @@ -5259,7 +5262,7 @@ static void cmd_track_add(void *p) } } int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, - cmd->abort->cancel); + cmd->abort->cancel, is_albumart); if (first < 0) { cmd->success = false; return; @@ -5322,8 +5325,10 @@ static void cmd_track_reload(void *p) if (t && t->is_external && t->external_filename) { char *filename = talloc_strdup(NULL, t->external_filename); + bool is_albumart = t->attached_picture; mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel); + nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel, + is_albumart); talloc_free(filename); } @@ -6070,6 +6075,7 @@ const struct mp_cmd_def mp_cmds[] = { .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}, + {"albumart", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG}, }, .priv = &(const int){STREAM_VIDEO}, .spawn_thread = true, @@ -6471,6 +6477,25 @@ static void update_priority(struct MPContext *mpctx) #endif } +static void update_track_switch(struct MPContext *mpctx, int order, int type) +{ + if (!mpctx->playback_initialized) + return; + + int tid = mpctx->opts->stream_id[order][type]; + struct track *track; + if (tid == -1) { + // If "auto" reset to default track selection + track = select_default_track(mpctx, order, type); + mark_track_selection(mpctx, order, type, -1); + } else { + track = mp_track_by_tid(mpctx, type, tid); + } + mp_switch_track_n(mpctx, order, type, track, (tid == -1) ? 0 : FLAG_MARK_SELECTION); + print_track_list(mpctx, "Track switched:"); + mp_wakeup_core(mpctx); +} + void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, bool self_update) { @@ -6629,15 +6654,8 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, for (int type = 0; type < STREAM_TYPE_COUNT; type++) { for (int order = 0; order < num_ptracks[type]; order++) { - if (opt_ptr == &opts->stream_id[order][type] && - mpctx->playback_initialized) - { - struct track *track = - mp_track_by_tid(mpctx, type, opts->stream_id[order][type]); - mp_switch_track_n(mpctx, order, type, track, FLAG_MARK_SELECTION); - print_track_list(mpctx, "Track switched:"); - mp_wakeup_core(mpctx); - } + if (opt_ptr == &opts->stream_id[order][type]) + update_track_switch(mpctx, order, type); } } diff --git a/player/core.h b/player/core.h index f2f0a15a00..fcb513bed1 100644 --- a/player/core.h +++ b/player/core.h @@ -528,7 +528,8 @@ void mp_abort_trigger_locked(struct MPContext *mpctx, struct mp_abort_entry *abort); void uninit_player(struct MPContext *mpctx, unsigned int mask); int mp_add_external_file(struct MPContext *mpctx, char *filename, - enum stream_type filter, struct mp_cancel *cancel); + enum stream_type filter, struct mp_cancel *cancel, + bool cover_art); void mark_track_selection(struct MPContext *mpctx, int order, enum stream_type type, int value); #define FLAG_MARK_SELECTION 1 diff --git a/player/loadfile.c b/player/loadfile.c index 058be92c83..6032cf6193 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -758,7 +758,8 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track) // cancel will generally be used to abort the loading process, but on success // the demuxer is changed to be slaved to mpctx->playback_abort instead. int mp_add_external_file(struct MPContext *mpctx, char *filename, - enum stream_type filter, struct mp_cancel *cancel) + enum stream_type filter, struct mp_cancel *cancel, + bool cover_art) { struct MPOpts *opts = mpctx->opts; if (!filename || mp_cancel_test(cancel)) @@ -832,8 +833,8 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename, t->external_filename = talloc_strdup(t, filename); t->no_default = sh->type != filter; t->no_auto_select = t->no_default; - // filter==STREAM_VIDEO always means cover art. - t->attached_picture = t->type == STREAM_VIDEO && filter == STREAM_VIDEO; + // if we found video, and we are loading cover art, flag as such. + t->attached_picture = t->type == STREAM_VIDEO && cover_art; if (first_num < 0 && (filter == STREAM_TYPE_COUNT || sh->type == filter)) first_num = mpctx->num_tracks - 1; } @@ -858,7 +859,9 @@ static void open_external_files(struct MPContext *mpctx, char **files, files = mp_dup_str_array(tmp, files); for (int n = 0; files && files[n]; n++) - mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort); + // when given filter is set to video, we are loading up cover art + mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort, + filter == STREAM_VIDEO); talloc_free(tmp); } @@ -897,15 +900,16 @@ void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel) goto skip; if (e->type == STREAM_VIDEO && (sc[STREAM_VIDEO] || !sc[STREAM_AUDIO])) goto skip; - int first = mp_add_external_file(mpctx, e->fname, e->type, cancel); + + // when given filter is set to video, we are loading up cover art + int first = mp_add_external_file(mpctx, e->fname, e->type, cancel, + e->type == STREAM_VIDEO); if (first < 0) goto skip; for (int n = first; n < mpctx->num_tracks; n++) { struct track *t = mpctx->tracks[n]; t->auto_loaded = true; - t->attached_picture = - t->type == STREAM_VIDEO && e->type == STREAM_VIDEO; if (!t->lang) t->lang = talloc_strdup(t, e->lang); } diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 6054480e9c..6aaf7eef5e 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -426,6 +426,10 @@ local function append_filters(s, prop, prefix) n = n .. " (disabled)" end + if f.label ~= nil then + n = "@" .. f.label .. ": " .. n + end + local p = {} for key,value in pairs(f.params) do p[#p+1] = key .. "=" .. value diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index e9a7b3aac7..586d4ea874 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -507,7 +507,7 @@ local function add_single_video(json) if requested_formats then for _, track in pairs(requested_formats) do - max_bitrate = track.tbr > max_bitrate and + max_bitrate = (track.tbr and track.tbr > max_bitrate) and track.tbr or max_bitrate end elseif json.tbr then diff --git a/player/main.c b/player/main.c index 71f5d14b53..58be00b6d8 100644 --- a/player/main.c +++ b/player/main.c @@ -54,7 +54,6 @@ #include "input/input.h" #include "audio/out/ao.h" -#include "demux/demux.h" #include "misc/thread_tools.h" #include "sub/osd.h" #include "test/tests.h" @@ -202,13 +201,6 @@ static bool handle_help_options(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct mp_log *log = mpctx->log; - if ((opts->demuxer_name && strcmp(opts->demuxer_name, "help") == 0) || - (opts->audio_demuxer_name && strcmp(opts->audio_demuxer_name, "help") == 0) || - (opts->sub_demuxer_name && strcmp(opts->sub_demuxer_name, "help") == 0)) { - demuxer_help(log); - MP_INFO(mpctx, "\n"); - return true; - } if (opts->ao_opts->audio_device && strcmp(opts->ao_opts->audio_device, "help") == 0) { diff --git a/player/scripting.c b/player/scripting.c index 24e2931539..3db879778c 100644 --- a/player/scripting.c +++ b/player/scripting.c @@ -174,9 +174,10 @@ static int64_t mp_load_script(struct MPContext *mpctx, const char *fname) }; talloc_free(tmp); + fname = NULL; // might have been freed so don't touch anymore if (!arg->client) { - MP_ERR(mpctx, "Failed to create client for script: %s\n", fname); + MP_ERR(mpctx, "Failed to create client for script: %s\n", arg->filename); talloc_free(arg); return -1; } @@ -185,7 +186,7 @@ static int64_t mp_load_script(struct MPContext *mpctx, const char *fname) arg->log = mp_client_get_log(arg->client); int64_t id = mpv_client_id(arg->client); - MP_DBG(arg, "Loading %s %s...\n", backend->name, fname); + MP_DBG(arg, "Loading %s %s...\n", backend->name, arg->filename); if (backend->no_thread) { run_script(arg); diff --git a/stream/stream.h b/stream/stream.h index 2116fdde27..423ba12d91 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -109,7 +109,7 @@ typedef struct stream_info_st { // opts is set from ->opts int (*open)(struct stream *st); // Alternative to open(). Only either open() or open2() can be set. - int (*open2)(struct stream *st, struct stream_open_args *args); + int (*open2)(struct stream *st, const struct stream_open_args *args); const char *const *protocols; bool can_write; // correctly checks for READ/WRITE modes bool local_fs; // supports STREAM_LOCAL_FS_ONLY diff --git a/stream/stream_concat.c b/stream/stream_concat.c index 21f04f9d62..d06bd4a782 100644 --- a/stream/stream_concat.c +++ b/stream/stream_concat.c @@ -99,7 +99,7 @@ static int combine_origin(int cur, int new) return new; // including cur==0 } -static int open2(struct stream *stream, struct stream_open_args *args) +static int open2(struct stream *stream, const struct stream_open_args *args) { struct priv *p = talloc_zero(stream, struct priv); stream->priv = p; diff --git a/stream/stream_file.c b/stream/stream_file.c index d649ff4bdf..612d4bee38 100644 --- a/stream/stream_file.c +++ b/stream/stream_file.c @@ -246,7 +246,7 @@ static bool check_stream_network(int fd) } #endif -static int open_f(stream_t *stream, struct stream_open_args *args) +static int open_f(stream_t *stream, const struct stream_open_args *args) { struct priv *p = talloc_ptrtype(stream, p); *p = (struct priv) { diff --git a/stream/stream_lavf.c b/stream/stream_lavf.c index e95dbc0b43..27a948d5e5 100644 --- a/stream/stream_lavf.c +++ b/stream/stream_lavf.c @@ -270,7 +270,7 @@ static int open_f(stream_t *stream) for (int i = 0; i < sizeof(prefix) / sizeof(prefix[0]); i++) if (!strncmp(filename, prefix[i], strlen(prefix[i]))) filename += strlen(prefix[i]); - if (!strncmp(filename, "rtsp:", 5)) { + if (!strncmp(filename, "rtsp:", 5) || !strncmp(filename, "rtsps:", 6)) { /* This is handled as a special demuxer, without a separate * stream layer. demux_lavf will do all the real work. Note * that libavformat doesn't even provide a protocol entry for @@ -411,9 +411,9 @@ const stream_info_t stream_info_ffmpeg = { .name = "ffmpeg", .open = open_f, .protocols = (const char *const[]){ - "rtmp", "rtsp", "http", "https", "mms", "mmst", "mmsh", "mmshttp", "rtp", - "httpproxy", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srt", "srtp", - "gopher", "data", + "rtmp", "rtsp", "rtsps", "http", "https", "mms", "mmst", "mmsh", "mmshttp", + "rtp", "httpproxy", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srt", + "srtp", "gopher", "gophers", "data", NULL }, .can_write = true, .stream_origin = STREAM_ORIGIN_NET, diff --git a/stream/stream_memory.c b/stream/stream_memory.c index 7383ff4d6f..e4696a73ad 100644 --- a/stream/stream_memory.c +++ b/stream/stream_memory.c @@ -44,7 +44,7 @@ static int64_t get_size(stream_t *s) return p->data.len; } -static int open2(stream_t *stream, struct stream_open_args *args) +static int open2(stream_t *stream, const struct stream_open_args *args) { stream->fill_buffer = fill_buffer; stream->seek = seek; diff --git a/stream/stream_mf.c b/stream/stream_mf.c index 69a6dce58d..4c871c4334 100644 --- a/stream/stream_mf.c +++ b/stream/stream_mf.c @@ -39,4 +39,5 @@ const stream_info_t stream_info_mf = { .name = "mf", .open = mf_stream_open, .protocols = (const char*const[]){ "mf", NULL }, + .stream_origin = STREAM_ORIGIN_FS, }; diff --git a/stream/stream_slice.c b/stream/stream_slice.c index d7d0a6bf05..c0dbeeb2af 100644 --- a/stream/stream_slice.c +++ b/stream/stream_slice.c @@ -136,7 +136,7 @@ static int parse_slice_range(stream_t *stream) return STREAM_OK; } -static int open2(struct stream *stream, struct stream_open_args *args) +static int open2(struct stream *stream, const struct stream_open_args *args) { struct priv *p = talloc_zero(stream, struct priv); stream->priv = p; @@ -151,8 +151,9 @@ static int open2(struct stream *stream, struct stream_open_args *args) return parse_ret; } - args->url = stream->path; - int inner_ret = stream_create_with_args(args, &p->inner); + struct stream_open_args args2 = *args; + args2.url = stream->path; + int inner_ret = stream_create_with_args(&args2, &p->inner); if (inner_ret != STREAM_OK) { return inner_ret; } diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 15eab18bf9..460334b371 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -63,8 +63,8 @@ static void uninit_avctx(struct mp_filter *vd); static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags); static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx, const enum AVPixelFormat *pix_fmt); -static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); +static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt, + struct bstr name); #define HWDEC_DELAY_QUEUE_COUNT 2 @@ -117,7 +117,8 @@ const struct m_sub_options vd_lavc_conf = { {"no", INT_MAX}, {"yes", 1}), M_RANGE(1, INT_MAX)}, {"vd-lavc-o", OPT_KEYVALUELIST(avopts)}, {"vd-lavc-dr", OPT_FLAG(dr)}, - {"hwdec", OPT_STRING_VALIDATE(hwdec_api, hwdec_validate_opt), + {"hwdec", OPT_STRING(hwdec_api), + .help = hwdec_opt_help, .flags = M_OPT_OPTIONAL_PARAM | UPDATE_HWDEC}, {"hwdec-codecs", OPT_STRING(hwdec_codecs)}, {"hwdec-image-format", OPT_IMAGEFORMAT(hwdec_image_format)}, @@ -533,33 +534,30 @@ static void select_and_set_hwdec(struct mp_filter *vd) } } -static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) +static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt, + struct bstr name) { - if (bstr_equals0(param, "help")) { - struct hwdec_info *hwdecs = NULL; - int num_hwdecs = 0; - add_all_hwdec_methods(&hwdecs, &num_hwdecs); + struct hwdec_info *hwdecs = NULL; + int num_hwdecs = 0; + add_all_hwdec_methods(&hwdecs, &num_hwdecs); - mp_info(log, "Valid values (with alternative full names):\n"); + mp_info(log, "Valid values (with alternative full names):\n"); - for (int n = 0; n < num_hwdecs; n++) { - struct hwdec_info *hwdec = &hwdecs[n]; + for (int n = 0; n < num_hwdecs; n++) { + struct hwdec_info *hwdec = &hwdecs[n]; - mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name); - } + mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name); + } - talloc_free(hwdecs); + talloc_free(hwdecs); - mp_info(log, " auto (yes '')\n"); - mp_info(log, " no\n"); - mp_info(log, " auto-safe\n"); - mp_info(log, " auto-copy\n"); - mp_info(log, " auto-copy-safe\n"); + mp_info(log, " auto (yes '')\n"); + mp_info(log, " no\n"); + mp_info(log, " auto-safe\n"); + mp_info(log, " auto-copy\n"); + mp_info(log, " auto-copy-safe\n"); - return M_OPT_EXIT; - } - return 0; + return M_OPT_EXIT; } static void force_fallback(struct mp_filter *vd) @@ -661,7 +659,9 @@ static void init_avctx(struct mp_filter *vd) if (!ctx->use_hwdec && ctx->vo && lavc_param->dr) { avctx->opaque = vd; avctx->get_buffer2 = get_buffer2_direct; +#if LIBAVCODEC_VERSION_MAJOR < 60 avctx->thread_safe_callbacks = 1; +#endif } avctx->flags |= lavc_param->bitexact ? AV_CODEC_FLAG_BITEXACT : 0; diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift index 8dd76f4bc5..dd0738f7e3 100644 --- a/video/out/cocoa_cb_common.swift +++ b/video/out/cocoa_cb_common.swift @@ -132,11 +132,11 @@ class CocoaCB: Common { } override func windowSetToFullScreen() { - layer?.update() + layer?.update(force: true) } override func windowSetToWindow() { - layer?.update() + layer?.update(force: true) } override func windowDidUpdateFrame() { diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c index b77e328b70..57feb27530 100644 --- a/video/out/d3d11/context.c +++ b/video/out/d3d11/context.c @@ -28,7 +28,7 @@ static int d3d11_validate_adapter(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); struct d3d11_opts { int feature_level; @@ -61,7 +61,7 @@ const struct m_sub_options d3d11_conf = { {"d3d11-flip", OPT_FLAG(flip)}, {"d3d11-sync-interval", OPT_INT(sync_interval), M_RANGE(0, 4)}, {"d3d11-adapter", OPT_STRING_VALIDATE(adapter_name, - d3d11_validate_adapter)}, + d3d11_validate_adapter)}, {"d3d11-output-format", OPT_CHOICE(output_format, {"auto", DXGI_FORMAT_UNKNOWN}, {"rgba8", DXGI_FORMAT_R8G8B8A8_UNORM}, @@ -111,8 +111,9 @@ struct priv { static int d3d11_validate_adapter(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); bool help = bstr_equals0(param, "help"); bool adapter_matched = false; struct bstr listing = { 0 }; diff --git a/video/out/drm_common.c b/video/out/drm_common.c index 64c84ca315..727221b6b1 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -54,13 +54,15 @@ static int vt_switcher_pipe[2]; -static int drm_validate_connector_opt( - struct mp_log *log, const struct m_option *opt, struct bstr name, - struct bstr param); +static int drm_connector_opt_help( + struct mp_log *log, const struct m_option *opt, struct bstr name); + +static int drm_mode_opt_help( + struct mp_log *log, const struct m_option *opt, struct bstr name); static int drm_validate_mode_opt( struct mp_log *log, const struct m_option *opt, struct bstr name, - struct bstr param); + const char **value); static void kms_show_available_modes( struct mp_log *log, const drmModeConnector *connector); @@ -71,10 +73,10 @@ static double mode_get_Hz(const drmModeModeInfo *mode); #define OPT_BASE_STRUCT struct drm_opts const struct m_sub_options drm_conf = { .opts = (const struct m_option[]) { - {"drm-connector", OPT_STRING_VALIDATE(drm_connector_spec, - drm_validate_connector_opt)}, - {"drm-mode", OPT_STRING_VALIDATE(drm_mode_spec, - drm_validate_mode_opt)}, + {"drm-connector", OPT_STRING(drm_connector_spec), + .help = drm_connector_opt_help}, + {"drm-mode", OPT_STRING_VALIDATE(drm_mode_spec, drm_validate_mode_opt), + .help = drm_mode_opt_help}, {"drm-atomic", OPT_CHOICE(drm_atomic, {"no", 0}, {"auto", 1})}, {"drm-draw-plane", OPT_CHOICE(drm_draw_plane, {"primary", DRM_OPTS_PRIMARY_PLANE}, @@ -747,31 +749,28 @@ double kms_get_display_fps(const struct kms *kms) return mode_get_Hz(&kms->mode.mode); } -static int drm_validate_connector_opt(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) +static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) { - if (bstr_equals0(param, "help")) { - kms_show_available_cards_and_connectors(log); - return M_OPT_EXIT; - } - return 1; + kms_show_available_cards_and_connectors(log); + return M_OPT_EXIT; } -static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) +static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) { - if (bstr_equals0(param, "help")) { - kms_show_available_cards_connectors_and_modes(log); - return M_OPT_EXIT; - } + kms_show_available_cards_connectors_and_modes(log); + return M_OPT_EXIT; +} - char *spec = bstrto0(NULL, param); - if (!parse_mode_spec(spec, NULL)) { +static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, + struct bstr name, const char **value) +{ + const char *param = *value; + if (!parse_mode_spec(param, NULL)) { mp_fatal(log, "Invalid value for option drm-mode. Must be a positive number, a string of the format WxH[@R] or 'help'\n"); - talloc_free(spec); return M_OPT_INVALID; } - talloc_free(spec); return 1; } diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c index 39696fbd83..afbca2b2f4 100644 --- a/video/out/gpu/context.c +++ b/video/out/gpu/context.c @@ -110,16 +110,20 @@ static const struct ra_ctx_fns *contexts[] = { #endif }; +int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) +{ + mp_info(log, "GPU APIs (contexts):\n"); + mp_info(log, " auto (autodetect)\n"); + for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++) + mp_info(log, " %s (%s)\n", contexts[n]->type, contexts[n]->name); + return M_OPT_EXIT; +} + int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { - if (bstr_equals0(param, "help")) { - mp_info(log, "GPU APIs (contexts):\n"); - mp_info(log, " auto (autodetect)\n"); - for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++) - mp_info(log, " %s (%s)\n", contexts[n]->type, contexts[n]->name); - return M_OPT_EXIT; - } + struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) return 1; for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) { @@ -129,16 +133,20 @@ int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt, return M_OPT_INVALID; } +int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) +{ + mp_info(log, "GPU contexts (APIs):\n"); + mp_info(log, " auto (autodetect)\n"); + for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++) + mp_info(log, " %s (%s)\n", contexts[n]->name, contexts[n]->type); + return M_OPT_EXIT; +} + int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { - if (bstr_equals0(param, "help")) { - mp_info(log, "GPU contexts (APIs):\n"); - mp_info(log, " auto (autodetect)\n"); - for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++) - mp_info(log, " %s (%s)\n", contexts[n]->name, contexts[n]->type); - return M_OPT_EXIT; - } + struct bstr param = bstr0(*value); if (bstr_equals0(param, "auto")) return 1; for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) { diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index 8c35eb0fc0..ca71150f54 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -100,7 +100,11 @@ struct ra_ctx *ra_ctx_create(struct vo *vo, const char *context_type, void ra_ctx_destroy(struct ra_ctx **ctx); struct m_option; +int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt, + struct bstr name); int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); +int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt, + struct bstr name); int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c index db75c64b05..4fb6240651 100644 --- a/video/out/gpu/hwdec.c +++ b/video/out/gpu/hwdec.c @@ -106,8 +106,9 @@ struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log, } int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); bool help = bstr_equals0(param, "help"); if (help) mp_info(log, "Available hwdecs:\n"); diff --git a/video/out/gpu/hwdec.h b/video/out/gpu/hwdec.h index 3a1ae3e2ff..050a358c74 100644 --- a/video/out/gpu/hwdec.h +++ b/video/out/gpu/hwdec.h @@ -109,7 +109,7 @@ struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log, bool is_auto); int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); void ra_hwdec_uninit(struct ra_hwdec *hwdec); diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c index 0f3a0bf646..894506973b 100644 --- a/video/out/gpu/lcms.c +++ b/video/out/gpu/lcms.c @@ -67,8 +67,9 @@ static bool parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3) } static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); int p1, p2, p3; char s[20]; snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 2aae3171e6..597286a550 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -317,7 +317,7 @@ static const struct gl_video_opts gl_video_opts_def = { }, .scaler_resizes_only = 1, .scaler_lut_size = 6, - .interpolation_threshold = 0.0001, + .interpolation_threshold = 0.01, .alpha_mode = ALPHA_BLEND_TILES, .background = {0, 0, 0, 255}, .gamma = 1.0f, @@ -337,13 +337,13 @@ static const struct gl_video_opts gl_video_opts_def = { }; static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); static int validate_window_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param); + struct bstr name, const char **value); #define OPT_BASE_STRUCT struct gl_video_opts @@ -957,9 +957,6 @@ static void init_video(struct gl_video *p) params.w, params.h); plane->tex = ra_tex_create(p->ra, ¶ms); - if (!plane->tex) - abort(); // shit happens - p->use_integer_conversion |= format->ctype == RA_CTYPE_UINT; } } @@ -3588,6 +3585,10 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t timer_pool_start(p->upload_timer); for (int n = 0; n < p->plane_count; n++) { struct texplane *plane = &vimg->planes[n]; + if (!plane->tex) { + timer_pool_stop(p->upload_timer); + goto error; + } struct ra_tex_upload_params params = { .tex = plane->tex, @@ -4088,8 +4089,9 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo) } static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); char s[20] = {0}; int r = 1; bool tscale = bstr_equals0(name, "tscale"); @@ -4120,8 +4122,9 @@ static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt, } static int validate_window_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); char s[20] = {0}; int r = 1; if (bstr_equals0(param, "help")) { @@ -4145,8 +4148,9 @@ static int validate_window_opt(struct mp_log *log, const m_option_t *opt, } static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); char s[20] = {0}; int r = 1; if (bstr_equals0(param, "help")) { diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c index 7073185eaf..91c9aac865 100644 --- a/video/out/gpu/video_shaders.c +++ b/video/out/gpu/video_shaders.c @@ -946,7 +946,7 @@ struct deband_opts { const struct deband_opts deband_opts_def = { .iterations = 1, - .threshold = 64.0, + .threshold = 32.0, .range = 16.0, .grain = 48.0, }; diff --git a/video/out/hwdec/hwdec_vaapi_vk.c b/video/out/hwdec/hwdec_vaapi_vk.c index 1cee9e86b9..afb81be82a 100644 --- a/video/out/hwdec/hwdec_vaapi_vk.c +++ b/video/out/hwdec/hwdec_vaapi_vk.c @@ -43,6 +43,7 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper) int fd = p->desc.objects[id].fd; uint32_t size = p->desc.objects[id].size; uint32_t offset = p->desc.layers[n].offset[0]; + uint32_t pitch = p->desc.layers[n].pitch[0]; #if PL_API_VER >= 88 // AMD drivers do not return the size in the surface description, so we @@ -85,6 +86,9 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper) #if PL_API_VER >= 88 .drm_format_mod = p->desc.objects[id].drm_format_modifier, #endif +#if PL_API_VER >= 106 + .stride_w = pitch, +#endif }, }; diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift index 6c3d0fcfb3..7ede81cbda 100644 --- a/video/out/mac/common.swift +++ b/video/out/mac/common.swift @@ -100,6 +100,7 @@ class Common: NSObject { } window.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) + window.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces)) window.keepAspect = Bool(mpv.opts.keepaspect_window) window.title = title window.border = Bool(mpv.opts.border) @@ -547,6 +548,10 @@ class Common: NSObject { DispatchQueue.main.async { self.window?.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level)) } + case MPVHelper.getPointer(&mpv.optsPtr.pointee.all_workspaces): + DispatchQueue.main.async { + self.window?.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces)) + } case MPVHelper.getPointer(&mpv.optsPtr.pointee.keepaspect_window): DispatchQueue.main.async { self.window?.keepAspect = Bool(mpv.opts.keepaspect_window) diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift index a418f2ce75..8b6c5f0518 100644 --- a/video/out/mac/window.swift +++ b/video/out/mac/window.swift @@ -30,7 +30,7 @@ class Window: NSWindow, NSWindowDelegate { var isInFullscreen: Bool = false var isAnimating: Bool = false var isMoving: Bool = false - var forceTargetScreen: Bool = false + var previousStyleMask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable] var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame ?? NSRect(x: 0, y: 0, width: 160, height: 90)) } } var framePixel: NSRect { get { return convertToBacking(frame) } } @@ -61,6 +61,7 @@ class Window: NSWindow, NSWindowDelegate { set { let responder = firstResponder let windowTitle = title + previousStyleMask = super.styleMask super.styleMask = newValue makeFirstResponder(responder) title = windowTitle @@ -229,7 +230,14 @@ class Window: NSWindow, NSWindowDelegate { func setToFullScreen() { guard let targetFrame = targetScreen?.frame else { return } - styleMask.insert(.fullScreen) + + if #available(macOS 11.0, *) { + styleMask = .borderless + common.titleBar?.hide(0.0) + } else { + styleMask.insert(.fullScreen) + } + NSApp.presentationOptions = [.autoHideMenuBar, .autoHideDock] setFrame(targetFrame, display: true) endAnimation() @@ -240,10 +248,17 @@ class Window: NSWindow, NSWindowDelegate { func setToWindow() { guard let tScreen = targetScreen else { return } + + if #available(macOS 11.0, *) { + styleMask = previousStyleMask + common.titleBar?.hide(0.0) + } else { + styleMask.remove(.fullScreen) + } + let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen) NSApp.presentationOptions = [] setFrame(newFrame, display: true) - styleMask.remove(.fullScreen) endAnimation() isInFullscreen = false mpv?.setOption(fullscreen: isInFullscreen) @@ -278,6 +293,14 @@ class Window: NSWindow, NSWindowDelegate { } } + func setOnAllWorkspaces(_ state: Bool) { + if state { + collectionBehavior.insert(.canJoinAllSpaces) + } else { + collectionBehavior.remove(.canJoinAllSpaces) + } + } + func setMinimized(_ stateWanted: Bool) { if isMiniaturized == stateWanted { return } diff --git a/video/out/placebo/ra_pl.c b/video/out/placebo/ra_pl.c index 8244acff26..09ff742d38 100644 --- a/video/out/placebo/ra_pl.c +++ b/video/out/placebo/ra_pl.c @@ -22,9 +22,7 @@ const struct pl_gpu *ra_pl_get(const struct ra *ra) return ra->fns == &ra_fns_pl ? get_gpu(ra) : NULL; } -#if PL_API_VER >= 60 static struct pl_timer *get_active_timer(const struct ra *ra); -#endif struct ra *ra_create_pl(const struct pl_gpu *gpu, struct mp_log *log) { @@ -163,32 +161,6 @@ static struct ra_tex *tex_create_pl(struct ra *ra, const struct ra_tex_params *params) { const struct pl_gpu *gpu = get_gpu(ra); - - // Check size limits - bool ok = false; - switch (params->dimensions) { - case 1: - ok = params->w <= gpu->limits.max_tex_1d_dim; - break; - - case 2: - ok = params->w <= gpu->limits.max_tex_2d_dim && - params->h <= gpu->limits.max_tex_2d_dim; - break; - - case 3: - ok = params->w <= gpu->limits.max_tex_2d_dim && - params->h <= gpu->limits.max_tex_2d_dim && - params->d <= gpu->limits.max_tex_2d_dim; - break; - }; - - if (!ok) { - MP_ERR(ra, "Texture size %dx%dx%d exceeds dimension limits!\n", - params->w, params->h, params->d); - return NULL; - } - const struct pl_tex *pltex = pl_tex_create(gpu, &(struct pl_tex_params) { .w = params->w, .h = params->dimensions >= 2 ? params->h : 0, @@ -217,6 +189,10 @@ static struct ra_tex *tex_create_pl(struct ra *ra, return NULL; } + // Keep track of these, so we can correctly bind them later + ratex->params.src_repeat = params->src_repeat; + ratex->params.src_linear = params->src_linear; + return ratex; } @@ -238,9 +214,7 @@ static bool tex_upload_pl(struct ra *ra, const struct ra_tex_upload_params *para .buf = params->buf ? params->buf->priv : NULL, .buf_offset = params->buf_offset, .ptr = (void *) params->src, -#if PL_API_VER >= 60 .timer = get_active_timer(ra), -#endif }; const struct pl_buf *staging = NULL; @@ -293,9 +267,7 @@ static bool tex_download_pl(struct ra *ra, struct ra_tex_download_params *params .tex = tex, .ptr = params->dst, .stride_w = params->stride / texel_size, -#if PL_API_VER >= 60 .timer = get_active_timer(ra), -#endif }; uint8_t *staging = NULL; @@ -328,19 +300,7 @@ static struct ra_buf *buf_create_pl(struct ra *ra, [RA_BUF_TYPE_SHARED_MEMORY] = 0, }; - const struct pl_gpu *gpu = get_gpu(ra); - size_t max_size[] = { - [PL_BUF_TEX_TRANSFER] = gpu->limits.max_xfer_size, - [PL_BUF_UNIFORM] = gpu->limits.max_ubo_size, - [PL_BUF_STORAGE] = gpu->limits.max_ssbo_size, - }; - - if (params->size > max_size[buf_type[params->type]]) { - MP_ERR(ra, "Buffer size %zu exceeds size limits!\n", params->size); - return NULL; - } - - const struct pl_buf *plbuf = pl_buf_create(gpu, &(struct pl_buf_params) { + const struct pl_buf *plbuf = pl_buf_create(get_gpu(ra), &(struct pl_buf_params) { .type = buf_type[params->type], .size = params->size, .host_mapped = params->host_mapped, @@ -693,8 +653,6 @@ static void renderpass_run_pl(struct ra *ra, pl_pass_run(get_gpu(ra), &pl_params); } -#if PL_API_VER >= 60 - struct ra_timer_pl { // Because libpplacebo only supports one operation per timer, we need // to use multiple pl_timers to sum up multiple passes/transfers @@ -766,8 +724,6 @@ static struct pl_timer *get_active_timer(const struct ra *ra) return t->timers[t->idx_timers++]; } -#endif // PL_API_VER >= 60 - static struct ra_fns ra_fns_pl = { .destroy = destroy_ra_pl, .tex_create = tex_create_pl, @@ -786,11 +742,9 @@ static struct ra_fns ra_fns_pl = { .renderpass_create = renderpass_create_pl, .renderpass_destroy = renderpass_destroy_pl, .renderpass_run = renderpass_run_pl, -#if PL_API_VER >= 60 .timer_create = timer_create_pl, .timer_destroy = timer_destroy_pl, .timer_start = timer_start_pl, .timer_stop = timer_stop_pl, -#endif }; diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c index 5d28a30054..fe17e344ec 100644 --- a/video/out/vo_gpu.c +++ b/video/out/vo_gpu.c @@ -321,8 +321,12 @@ err_out: #define OPT_BASE_STRUCT struct gpu_priv static const m_option_t options[] = { - {"gpu-context", OPT_STRING_VALIDATE(context_name, ra_ctx_validate_context)}, - {"gpu-api", OPT_STRING_VALIDATE(context_type, ra_ctx_validate_api)}, + {"gpu-context", + OPT_STRING_VALIDATE(context_name, ra_ctx_validate_context), + .help = ra_ctx_context_help}, + {"gpu-api", + OPT_STRING_VALIDATE(context_type, ra_ctx_validate_api), + .help = ra_ctx_api_help}, {"gpu-debug", OPT_FLAG(opts.debug)}, {"gpu-sw", OPT_FLAG(opts.allow_sw)}, {0} diff --git a/video/out/vo_wlshm.c b/video/out/vo_wlshm.c index 8319475f72..a71f9507fc 100644 --- a/video/out/vo_wlshm.c +++ b/video/out/vo_wlshm.c @@ -23,6 +23,7 @@ #include <libswscale/swscale.h> +#include "osdep/endian.h" #include "sub/osd.h" #include "video/fmt-conversion.h" #include "video/mp_image.h" @@ -184,7 +185,7 @@ static int resize(struct vo *vo) vo->dheight = height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); p->sws->dst = (struct mp_image_params) { - .imgfmt = IMGFMT_BGR0, + .imgfmt = MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB), .w = width, .h = height, .p_w = 1, diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c index 3518d3efd2..9bdeb14d9b 100644 --- a/video/out/vulkan/context.c +++ b/video/out/vulkan/context.c @@ -31,8 +31,9 @@ struct vulkan_opts { }; static int vk_validate_dev(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) + struct bstr name, const char **value) { + struct bstr param = bstr0(*value); int ret = M_OPT_INVALID; VkResult res; diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index fe9c0b531c..df9321fca2 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -71,7 +71,8 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = { static int spawn_cursor(struct vo_wayland_state *wl) { - if (wl->allocated_cursor_scale == wl->scaling) /* Reuse if size is identical */ + /* Reuse if size is identical */ + if (!wl->pointer || wl->allocated_cursor_scale == wl->scaling) return 0; else if (wl->cursor_theme) wl_cursor_theme_destroy(wl->cursor_theme); @@ -268,6 +269,7 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, mp_input_put_key(wl->vo->input_ctx, button | state | mpmod); if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && + (!wl->vo_opts->fullscreen) && (!wl->vo_opts->window_maximized) && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN)) { uint32_t edges; // Implement an edge resize zone if there are no decorations @@ -603,6 +605,44 @@ static const struct wl_seat_listener seat_listener = { seat_handle_caps, }; +static void greatest_common_divisor(struct vo_wayland_state *wl, int a, int b) { + // euclidean algorithm + int larger; + int smaller; + if (a > b) { + larger = a; + smaller = b; + } else { + larger = b; + smaller = a; + } + int remainder = larger - smaller * floor(larger/smaller); + if (remainder == 0) { + wl->gcd = smaller; + } else { + greatest_common_divisor(wl, smaller, remainder); + } +} + +static void set_geometry(struct vo_wayland_state *wl) +{ + struct vo *vo = wl->vo; + + struct vo_win_geometry geo; + struct mp_rect screenrc = wl->current_output->geometry; + vo_calc_window_geometry(vo, &screenrc, &geo); + vo_apply_window_geometry(vo, &geo); + + greatest_common_divisor(wl, vo->dwidth, vo->dheight); + wl->reduced_width = vo->dwidth / wl->gcd; + wl->reduced_height = vo->dheight / wl->gcd; + + wl->vdparams.x0 = 0; + wl->vdparams.y0 = 0; + wl->vdparams.x1 = vo->dwidth / wl->scaling; + wl->vdparams.y1 = vo->dheight / wl->scaling; +} + static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, @@ -637,6 +677,7 @@ static void output_handle_mode(void *data, struct wl_output *wl_output, static void output_handle_done(void* data, struct wl_output *wl_output) { struct vo_wayland_output *o = data; + struct vo_wayland_state *wl = o->wl; o->geometry.x1 += o->geometry.x0; o->geometry.y1 += o->geometry.y0; @@ -648,8 +689,21 @@ static void output_handle_done(void* data, struct wl_output *wl_output) "\tHz: %f\n", o->make, o->model, o->id, o->geometry.x0, o->geometry.y0, mp_rect_w(o->geometry), o->phys_width, mp_rect_h(o->geometry), o->phys_height, o->scale, o->refresh_rate); + + /* If we satisfy this conditional, something about the current + * output must have changed (resolution, scale, etc). All window + * geometry and scaling should be recalculated. */ + if (wl->current_output && wl->current_output->output == wl_output) { + wl->scaling = wl->current_output->scale; + spawn_cursor(wl); + set_geometry(wl); + wl->window_size = wl->vdparams; + if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) + wl->geometry = wl->window_size; + wl->pending_vo_events |= VO_EVENT_RESIZE; + } - o->wl->pending_vo_events |= VO_EVENT_WIN_STATE; + wl->pending_vo_events |= VO_EVENT_WIN_STATE; } static void output_handle_scale(void* data, struct wl_output *wl_output, @@ -793,44 +847,6 @@ static const struct wl_data_device_listener data_device_listener = { data_device_handle_selection, }; -static void greatest_common_divisor(struct vo_wayland_state *wl, int a, int b) { - // euclidean algorithm - int larger; - int smaller; - if (a > b) { - larger = a; - smaller = b; - } else { - larger = b; - smaller = a; - } - int remainder = larger - smaller * floor(larger/smaller); - if (remainder == 0) { - wl->gcd = smaller; - } else { - greatest_common_divisor(wl, smaller, remainder); - } -} - -static void set_geometry(struct vo_wayland_state *wl) -{ - struct vo *vo = wl->vo; - - struct vo_win_geometry geo; - struct mp_rect screenrc = wl->current_output->geometry; - vo_calc_window_geometry(vo, &screenrc, &geo); - vo_apply_window_geometry(vo, &geo); - - greatest_common_divisor(wl, vo->dwidth, vo->dheight); - wl->reduced_width = vo->dwidth / wl->gcd; - wl->reduced_height = vo->dheight / wl->gcd; - - wl->vdparams.x0 = 0; - wl->vdparams.y0 = 0; - wl->vdparams.x1 = vo->dwidth / wl->scaling; - wl->vdparams.y1 = vo->dheight / wl->scaling; -} - static void rescale_geometry_dimensions(struct vo_wayland_state *wl, double factor) { wl->vdparams.x1 *= factor; @@ -871,7 +887,9 @@ static void surface_handle_enter(void *data, struct wl_surface *wl_surface, if (wl->scaling != wl->current_output->scale && wl->vo_opts->hidpi_window_scale) { double factor = (double)wl->scaling / wl->current_output->scale; wl->scaling = wl->current_output->scale; + spawn_cursor(wl); rescale_geometry_dimensions(wl, factor); + wl->pending_vo_events |= VO_EVENT_DPI; } if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) @@ -1569,6 +1587,7 @@ int vo_wayland_reconfig(struct vo *vo) wl->window_size = wl->vdparams; wl->geometry = wl->window_size; wl_display_roundtrip(wl->display); + wl->pending_vo_events |= VO_EVENT_DPI; } wl->pending_vo_events |= VO_EVENT_RESIZE; @@ -1729,6 +1748,12 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) *(double *)arg = wl->current_output->refresh_rate; return VO_TRUE; } + case VOCTRL_GET_HIDPI_SCALE: { + if (!wl->scaling) + return VO_NOTAVAIL; + *(double *)arg = wl->scaling; + return VO_TRUE; + } case VOCTRL_UPDATE_WINDOW_TITLE: return update_window_title(wl, (const char *)arg); case VOCTRL_SET_CURSOR_VISIBILITY: @@ -422,6 +422,11 @@ audio_output_features = [ 'deps': 'sdl2', 'func': check_true, }, { + 'name': '--oss-audio', + 'desc': 'OSSv4 audio output', + 'func': check_statement(['sys/soundcard.h'], 'int x = SNDCTL_DSP_SETPLAYVOL'), + 'deps': 'posix && gpl', + }, { 'name': '--pulse', 'desc': 'PulseAudio audio output', 'func': check_pkg_config('libpulse', '>= 1.0') @@ -725,7 +730,7 @@ video_output_features = [ }, { 'name': '--libplacebo', 'desc': 'libplacebo support', - 'func': check_pkg_config('libplacebo >= 1.18.0'), + 'func': check_pkg_config('libplacebo >= 2.72.0'), }, { 'name': '--vulkan', 'desc': 'Vulkan context support', diff --git a/wscript_build.py b/wscript_build.py index 14c254e1ec..a12cbf19e1 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -244,6 +244,7 @@ def build(ctx): ( "audio/out/ao_null.c" ), ( "audio/out/ao_openal.c", "openal" ), ( "audio/out/ao_opensles.c", "opensles" ), + ( "audio/out/ao_oss.c", "oss-audio" ), ( "audio/out/ao_pcm.c" ), ( "audio/out/ao_pulse.c", "pulse" ), ( "audio/out/ao_sdl.c", "sdl2-audio" ), |